Skip to content

Settings, System: one update state, derived in one place, that follows the updater - #318

Merged
nambok merged 1 commit into
mainfrom
update-page-one-state
Nov 5, 2026
Merged

nambok merged 1 commit into
mainfrom
update-page-one-state

Conversation

@nambok

@nambok nambok commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Version card showed contradictory states at the same time. On a live install running v0.8.32:

  • It said "Checking v0.8.39" even though v0.8.39's change set was already prepared.
  • It said "v0.8.37 needs your OK" for an older update that the newer one had replaced.
  • It said "Preparing" for a change set that was already waiting for an admin's OK, while a footer said "Ready to update".
  • It offered "Review and apply" for a change set someone had already executed in the AWS console.

Now one function picks exactly one state, and the card renders only that state. The updater half is in the platform PR.

Research: how mature self-update UIs present this

  • AWS CloudFormation change sets. After a change set runs, "CloudFormation deletes any additional change sets that are associated with the stack". Only one proposal stays live. Running a change set takes you to the Events view to follow it (docs).
  • GitLab self-managed. Upgrades wait until background migrations show "Finished". The admin page shows Queued, Finalizing and Failed tabs. When there is no reliable number it says "Progress unavailable" instead of making one up (docs).
  • Chrome. The status is one line, and there is one action, "Relaunch" (help).
  • macOS Software Update, Windows Update, GitHub Enterprise Server, Home Assistant, Grafana. These come from the research in Settings, System: one update status, and Install health as its own section #312. Each shows one status sentence and a "Check for updates" button with a last-checked time. Health lives on its own surface. A failure names the version, where you are now, and one next step.
  • Discourse. Its upgrade progress has been reported as unreliable while the app restarts (thread). It is a reminder to read progress from the system doing the work, not from a cached field.
  • Vercel deployments. Each deploy has one state at a time, never two.

From these, the page follows these principles:

  1. One primary status and one primary action.
  2. Honest progress: named steps and when each started, never a made-up percentage.
  3. The truth comes from the system doing the work (CloudFormation, CodeBuild), not from a stored copy.
  4. A state that can go stale has a time bound and a way out.
  5. Something that needs the admin says what and why, here: the permission list goes in the confirm dialog.
  6. Health is never worded as an update state.
  7. The page follows a change by itself until it ends.

Changes

  • src/lib/update-state.ts maps the version route (and the preflight, for install checks that stop an update) to one state. The states are:
    • applying
    • preparing
    • waiting for your OK (to confirm, apply, or for an AWS admin)
    • checking the release
    • a check that stalled
    • a refused model
    • the last try failed
    • the updater can't be reached
    • an install check blocks the update
    • available
    • not listed yet, can't reach GitHub, not checked yet, development build, or up to date
  • CloudFormation's execution status wins over the stored status. A change set that is executing or has run shows as "Applying", never as "Review and apply". When someone applied it in the AWS console, the page says so.
  • Older updater payloads. If an older updater left an older update as pending, the newest prepared update wins.
  • Time bound on the release check. A release check pending for more than 30 minutes shows as stalled, with its reason and a Check again button. It never says "Checking" forever.
  • Polling. The page reads the version route again every 10 s while checking or preparing, every 15 s while applying, and every 30 s while waiting for an OK (that OK can be given in the console). It moves from preparing to the OK to applying to done without a reload.
  • Progress. Steps read "Check the release", "Prepare the change" and "Apply it", with the current one marked. Each shows who started it and when.
  • History. It lists a superseded update as "Replaced".
  • The Uninstall section is one card. Before, the data choice sat in a box styled with border-border and text-text-secondary. Neither token exists, so the border was drawn in the text colour, which shows as a white outline in dark mode. The choice is now a row of the card, separated by the card's own dividers. The button reads Open the uninstall guide, with an external-link icon and the note "Opens the guide in a new tab. Nothing is deleted from here." Typing the install's name is still required to turn on "Delete everything".

Verification

  • npm run lint, npm run typecheck, npm run check:openapi and npm run build all pass.
  • e2e/update-state-derive.spec.ts, new, runs without a browser. It feeds the derivation the observed payloads:
    • an older pending update, plus a newer prepared one, plus a stale pending check
    • executed in the console while the entry still said review
    • applied in the console
    • pending beyond the time bound
    • a superseded history row
    • the polling intervals
  • e2e/update-states.spec.ts, extended, renders:
    • both observed payloads (one status, no "Review and apply" for the old update, no stale "Checking")
    • the reconciled payload (v0.8.39 needs your OK, with the permission list in the dialog and history showing "Replaced")
    • executed in the console
    • applied in the console
    • checking, then available after one poll
    • a stalled check with Check again
    • preparing, then the OK, then applying, then updated, with no reload (Playwright clock)
    • the Uninstall card in dark mode
  • Test runs, all chromium against the static build:
    • update-states and update-state-derive: 41 passed
    • selfhost, models-switch, hybrid, settings-pages, no-sideways-scroll and readable-text: passed, except one models-switch test whose fixture had a check pending since October. That fixture now starts the check "just now" and the test passes.
  • Desktop and phone screenshots of every state are in E2E_SHOTS_DIR.

…s the updater

The Version card showed contradictory states at once: "Checking v0.8.39"
for a release whose change set was already prepared, "v0.8.37 needs your
OK" for an older update the newer one had replaced, and "Review and apply"
for a change set someone had already executed in the AWS console.

lib/update-state.ts now maps the version route (and the preflight, for
install checks that stop an update) to exactly one state: applying,
preparing, waiting for your OK, checking the release, a check that
stalled, a refused model, the last try that failed, the updater or an
install check in the way, an update available, or the quiet states. The
card renders only that state.

- CloudFormation's execution status beats the stored status: a change set
  that is executing or executed is "Applying", never "Review and apply",
  and one applied in the AWS console says so.
- With an older updater's payload, the newest prepared update wins over
  an older one left as pending.
- A release check pending for more than 30 minutes reads as stalled, with
  its reason and Check again, never as "Checking" forever.
- The page polls while something is happening (checking and preparing
  every 10 s, applying every 15 s, waiting for an OK every 30 s), so it
  moves from preparing to the OK to applying to done without a reload.
- Each update shows its three steps (check the release, prepare the
  change, apply it) and where it is, instead of a made-up percentage.
- History lists a replaced update as "Replaced".
- Uninstall is one card: the data choice is a row, not a box with an
  undefined border token (it drew in the text colour), and the button
  reads "Open the uninstall guide", with an external-link icon and a note
  that nothing is deleted from here.

Tests: update-state-derive.spec.ts feeds the derivation the observed
payloads; update-states.spec.ts renders them, the poll transitions, the
stalled check and the Uninstall card in dark mode.
@nambok
nambok merged commit 892794a into main Nov 5, 2026
10 checks passed
@nambok
nambok deleted the update-page-one-state branch November 5, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant