Offer the rollback, and call it Downgrade

Picking an old commit from the phone detaches HEAD, so every rollback is
parked by construction -- and a parked checkout withheld the commit
comparison entirely. The card answered the move with "up to date" and
nothing to press, which left a rollback unfinishable from the phone.

The withholding was right for one direction only. A checkout parked past
what was built is somebody looking, and nagging about it is nagging about
a decision. A checkout parked *behind* it is somebody rolling back, and
there the point of the move is to get that commit built and installed.

So `git::is_ancestor` asks which of the two commits is the later one --
one extra process, and only where they already differ -- and
`Freshness::BuiltAhead` says the build is. It is stale like `Behind`, so
the card leaves "Up to date" and both Update buttons come alive;
`updateWord` then says Downgrade, at both scales and by one rule, the way
`buildWord` does. Nothing else about the press differs, which is what was
asked for. The row says "newer than the checkout" rather than "out of
date", which is the wrong direction.

Two things fell out of it. `UpdateButton` asks about the direction before
comparing file times, since what is on disk stays older than the phone's
copy right up until the press rebuilds it -- otherwise a card with a
rollback waiting says "Reinstall". And a project-wide Update now does the
build-and-install half alone when there is nothing to pull *from* -- a
parked checkout, a branch tracking nothing, or `gitPull` off -- rather
than failing on a pull it was never going to make, which is what a
downgrade press used to hit.

Verified end to end against a throwaway checkout driven through a scratch
server: `current` on the built commit, `builtAhead` once parked back on
the one before it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuRzBWJuEGaWmMhDuX3X3p
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-06 22:57:55 -04:00
1 parent c41c36f25d
commit 283b971186
5 files changed
+287 -52

No files matched your search

+37 -14
View File
@@ -473,18 +473,41 @@ mutable at runtime from the phone.
parked further back than the window gets its own commit appended, so
the picker never has nothing to show for where it is.
And `freshness` takes a `parked` flag, reporting a differing commit as
**unknown** rather than `Behind` while the checkout is detached: every
way it reads as behind while parked is a build that failed, a
declaration waiting to be accepted, or a component with no build step,
and each of those already says so on the same card beside the button
for it -- so "out of date" there is either redundant or a nag about a
decision somebody made. Unknown rather than current, because nothing
measured the output to be what was wanted. `component_is_stale` is
deliberately *not* changed: Update and Rebuild still rebuild a parked
checkout, which is the whole point of parking one. The flag is passed
down from `describe` rather than read per component, since git has
already been asked for that project's status once and `/manifest` is
fetched on every open, resume and Refresh.
**unknown** rather than `Behind` while the checkout is detached *past*
what was built: every way it reads as behind while parked is a build
that failed, a declaration waiting to be accepted, or a component with
no build step, and each of those already says so on the same card
beside the button for it -- so "out of date" there is either redundant
or a nag about a decision somebody made. Unknown rather than current,
because nothing measured the output to be what was wanted.
`component_is_stale` is deliberately *not* changed: Update and Rebuild
still rebuild a parked checkout, which is the whole point of parking
one. The flag is passed down from `describe` rather than read per
component, since git has already been asked for that project's status
once and `/manifest` is fetched on every open, resume and Refresh.
- **The other direction is reported, and its word is Downgrade.** A
checkout parked *behind* its build is somebody rolling back, and
withholding that was the whole of why a rollback could not be finished
from the phone: picking an old commit detaches HEAD, so every rollback
is parked by construction, and the card answered the move with "up to
date" and nothing to press (Bryan, 2026-09-06). `git::is_ancestor`
asks which of the two commits is the later one -- one extra process,
and only where they already differ -- and `Freshness::BuiltAhead` says
the build is. It is stale like `Behind`, so the card leaves "Up to
date" and both Update buttons come alive; `updateWord` then says
**Downgrade**, at both scales and by one rule, the way `buildWord`
does. Nothing else about the press differs, which is what Bryan asked
for. The row says "newer than the checkout" rather than "out of date",
which is the wrong direction.
Two things fell out of it. `UpdateButton` asks about the direction
*before* comparing file times, since what is on disk is older than
this phone's copy right up until the press rebuilds it -- otherwise a
card with a rollback waiting says "Reinstall". And a project-wide
Update now does the build-and-install half alone when there is nothing
to pull *from* -- a parked checkout, a branch tracking nothing, or
`gitPull` off -- rather than failing on a pull it was never going to
make, which is what a downgrade press used to hit.
- **Unrelated histories are the one pull failure the phone may override.**
A checkout sharing no commit with its upstream has no fast-forward and
@@ -622,8 +645,8 @@ mutable at runtime from the phone.
only difference lived in a config file on the other machine. That
silence cost two rounds of "why isn't it flagging anything" that no
amount of looking at the phone could answer. It now names the state:
`behind`, `neverBuilt` and `otherMode` are measured and have something
to do about them; `uncommitted`, `notBuiltHere`, `noCheckout` and
`behind`, `builtAhead`, `neverBuilt` and `otherMode` are measured and
have something to do about them; `uncommitted`, `notBuiltHere`, `noCheckout` and
`parked` are the ways of not knowing, and each says so in the row in
the ordinary text colour -- words, not a colour, because "we could not
check" is a difference in kind from "there is something here".