feat(desktop): client + backend version buttons and remote backend update flow - #40781
feat(desktop): client + backend version buttons and remote backend update flow#40781yoniebans wants to merge 16 commits into
Conversation
🔎 Lint report:
|
Add a best-effort `commits` list (sha/summary/author/at) to the update-check response for git/pip installs that are behind upstream, so the desktop's remote update overlay can show what's changed before applying. Additive and non-breaking: existing consumers (legacy dashboard, tests using subset assertions) ignore the new field. Leaves the shared check_for_updates() int contract untouched — commits come from a separate best-effort git call.
…mote In remote thin-client mode the Electron client and the backend it connects to are separate installs that drift independently. The status bar previously showed only the client version, hiding skew (e.g. client 0.15.1 talking to backend 0.16.0 looked fine). Add a pure resolveVersionBar() helper (unit-tested) that, gated on connection.mode === 'remote', renders both 'client vX · backend vY' from the desktop appVersion and StatusResponse.version, and flags skew. Local mode is byte-identical to before. Wire it into the status-bar version item.
In remote mode, checkUpdates()/applyUpdates() branch on connection.mode and drive the existing updates overlay from the connected backend instead of the local Electron git bridge: - checkUpdates -> GET /api/hermes/update/check, mapped onto DesktopUpdateStatus (behind, commits, supported=can_apply, message). The overlay renders the commit list as 'what's changed' and shows guidance (not Install) when the backend install can't self-apply (docker/nix). - applyUpdates -> POST /api/hermes/update (the proven command-center path), polling the action to completion and handling the expected mid-update connection drop as the restart phase. Local mode is unchanged. Adds checkHermesUpdate() to hermes.ts and a BackendUpdateCheckResponse type.
…eam}
The behind-count (banner._check_via_local_git) measures HEAD..origin/main, but
_recent_upstream_commits logged HEAD..@{upstream}. On a feature-branch checkout
@{upstream} is the branch's own tip (0 commits), so the changelog came back
empty while behind>0 — the overlay then showed generic filler instead of what
changed. Pin the commit range to origin/main so count and changelog agree.
Verified against a checkout 11 behind origin/main: now returns 11 commits.
…log copy
The updates overlay showed generic 'New update available / improvements and
fixes' with no indication of whether it was updating the client or the backend.
In remote mode it now reads 'Backend update available' and names the connected
backend, and when there's no commit changelog (e.g. pip/non-git backend) it
degrades to honest 'release notes aren't available for this install type' copy
instead of filler.
Copy selection extracted to a pure resolveUpdateCopy() helper (unit-tested);
threads target ('client'|'backend') from connection.mode through the overlay.
The status bar merged both versions into one pill with a single click target, so there was no way to tell which artifact an update acted on — and the apply path was overloaded by connection mode. Separate them: - store: independent client (checkUpdates/applyUpdates) and backend (checkBackendUpdates/applyBackendUpdate) flows with their own status/apply atoms; openUpdateOverlayFor(target) drives the overlay. - status bar: two buttons — client vX (always) and backend vY (+N) (remote only), each with its own behind-count, opening the overlay for its target. - overlay: reads the active target's atoms; install/check route per target. Removes the version-bar merge helper (no longer merging the two versions).
Two follow-ups from testing the two-button bar:
- The background poller and focus handler only checked the client, so the
backend behind-count and changelog stayed empty until the user opened the
overlay — and the overlay's first render then hit the empty-commits fallback
('Improvements and fixes') instead of the real changelog. Check the backend
alongside the client on poller start, interval, and focus so its state is
ready before the button is clicked.
- Order the status bar client-first, backend-second.
The poller starts at mount, before the gateway connects, so its initial checkBackendUpdates() ran while mode was still unset and no-op'd via the remote-mode guard — leaving the backend button empty until the user clicked it. Subscribe to $connection and re-check the backend when mode resolves to remote.
1b25dae to
7c3b703
Compare
HERMES_DESKTOP_REMOTE_URL forces a remote connection but never writes connection.json, so the gateway panel read mode/url from persisted config and mislabelled an env-remote session as local with no url.
…tarts The backend Install path set stage:'restart' and stopped — in remote mode no boot-progress events arrive to carry the overlay to done, so it sat on the restarting spinner until a manual reload while the backend had already come back. Poll the backend until it answers again, then clear the overlay and refresh the backend status. Target-aware applying copy explains the remote restart + auto-reconnect instead of the local-updater-window wording. Also switch the apply poll sleeps from window.setTimeout to globalThis.setTimeout so the flow is exercisable off the renderer.
…o-return Three rough edges in the remote backend apply flow: - On success the overlay dropped to IDLE, briefly re-rendering the pre-install 'update available' view and then the generic 'you're all set' before settling. Close the overlay outright once the backend is confirmed back instead of bouncing through the idle view. - If the backend never came back (a failed restart), the flow still reported success. waitForBackendReturn now returns whether the backend answered; finishBackendApply surfaces an error when it didn't. - The up-to-date copy said 'you're running the latest version', conflating client and backend. Backend target now reads 'the backend is running the latest version' — the client's own version is a separate pill.
…returns The no-return error said 'Backend updated but did not come back online' — but once the connection drops the client can't know the update's exit code, only that it was started and the backend is unreachable. Reword to not overclaim: the update may not have completed.
Two independent reviewers flagged that applyBackendUpdate's in-progress and error messages were inline English while the rest of the update overlay is i18n'd. Move them into updates.applyStatus (preparing/pulling/restarting/ notAvailable/failed/noReturn) across en, ja, zh, zh-hant + types.
|
Merged via #42181 — your 14 commits were cherry-picked onto current The only change from your branch: corrected the deployment note. There's no Thanks! |
Summary
When the gateway is switched to a remote backend (the GUI on one machine, the backend on another), the status bar collapsed a two-artifact reality into one version badge — and "update" was ambiguous. The badge read the local client clone's version, so updating the remote backend never moved it, and the overlay had no way to show what the backend was behind by. #40017 added the skew warning; this adds the update surface it points at.
The desktop and the remote backend are independently updatable. They deserve independent version buttons.
Changes
Backend —
/api/hermes/update/checkreturns a newcommitsfield for git/pip installs that are behind: a list of{sha, summary, author, at}measuredHEAD..origin/main(same ref the behind-count uses). Additive; existing consumers ignore it.Desktop — when the gateway is pointed at a remote backend, the status bar shows two buttons:
(+N)behind-count.Each opens the update overlay scoped to its target, rendering that target's commit changelog (grouped: Fixed / Improved / Other). A local gateway is unchanged — a single version pill. The store splits into independent client/backend update atoms so the two surfaces never share state; the backend button is gated on
connection.mode === 'remote'.Backend Install flow (apply path). Clicking Install on the backend overlay calls the pre-existing
POST /api/hermes/update(unchanged), which runshermes updateon the backend host. The remote-update UX was built out around it:Env-override fix (
a5e5f28b4) is intentionally included: the remote feature is unreachable viaHERMES_DESKTOP_REMOTE_URLwithout it (the Gateway panel mislabelled the connection and locked its controls). Kept in this PR because the feature is unusable in env-override remote mode otherwise.Deployment note
When the gateway points at a remote backend, the backend Install restarts the dashboard, so the dashboard must be able to come back on its own. Run it under a supervisor with restart-on-stop — systemd
Restart=always— which is what the new "wait for backend to return" flow expects. An unsupervised bare process will not restart after the update: the connection stays down until the dashboard is manually restarted. Note the bundledtemplates/hermes-dashboard.serviceusesRestart=on-failure, which is NOT sufficient (a SIGTERM stop is "clean", so on-failure skips the relaunch) — useRestart=alwaysfor a remote-backend deployment.Notes
HEAD..origin/main), else a feature-branch backend shows an empty changelog while the count is non-zero — handled here.Test plan
tsc -bclean.TestUpdateCheckEndpoint(6).updates.test.ts+update-copy.test.ts(13).(+0)) and failure path (backend doesn't return → honest error, retry works, pill stays accurate).Known follow-ups (separate issues, not blocking)
mainbranch silently switches it tomain(hermes updatedefault-branch behavior; pre-existing). Worth a guard.