Plan — pre-flight /simple/ agreement check

Status:ready-for-dc-review
Date:2026-05-20
Branch:master
Spec:docs/superpowers/specs/2026-05-20-update-ux-pre-flight-simple-index.html

Goal

Make the tray's "Update available" promise truthful: only surface a new version when uv can actually install it. Probe both /pypi/json AND /simple/ in version_check.py, treat the lesser as the installable latest.

Architecture

Add one function (get_latest_from_simple_index) that queries /simple/<pkg>/ via PEP 691 JSON. Modify check_version_cached to consult both endpoints. outdated only fires when both agree. Cache schema bumps to store both versions. Tray UI surface unchanged.

Tech stack

File map

FileChange
jacked/version_check.pyAdd filename regex + _parse_version_tuple helper + get_latest_from_simple_index; modify check_version_cached to consume both endpoints + new cache schema
tests/unit/test_version_check.py9 new tests (per spec)
jacked/__init__.pyVersion bump 0.45.3 → 0.45.4

Tasks

Task 1: Extract _parse_version_tuple helper

The existing is_newer has a nested parse closure. Extract it as a module-level helper so it can be reused by the new max-version logic.

Files:

Task 2: Add get_latest_from_simple_index

Files: jacked/version_check.py, tests/unit/test_version_check.py

Task 3: Modify check_version_cached to consult both endpoints

Files: jacked/version_check.py, tests/unit/test_version_check.py

Task 4: Bump version, commit, push, release

Test plan

Risks & mitigations

RiskMitigation
PEP 691 JSON not enabled at PyPI for some mirrorFalls into the network-error path → returns None → outdated=false. Conservative.
Filename regex misses an exotic distribution formatVersions parsed from filenames — if regex misses ALL files, returns None → outdated=false.
min() of two version strings — wrong if one parses to empty tupleUse _parse_version_tuple consistently; empty tuple sorts as the smallest, so an unparseable side never wins as "max" and the lesser of (parseable, empty) is empty → installable=current → outdated=false.
Old cache invalidation churns through one extra PyPI fetch per user on upgradeAcceptable — happens once per user; no operational issue. PyPI tolerates this easily.
Discovery slower by ~100-200ms (one extra HTTP call)Cache absorbs all but first poll. First poll runs in tray's background thread.

Open questions