fix(desktop): say "Reconnecting" while the client is auto-reconnecting - #5029
fix(desktop): say "Reconnecting" while the client is auto-reconnecting#5029sunnnsolar wants to merge 1 commit into
Conversation
The sidebar relay card renders "Can't reach the relay" / "Click to connect" for every degraded state, including `reconnecting` — the state the relay client sets while its own backoff loop is actively retrying. So a transient drop that the app is already recovering from unassisted is presented as a dead connection the user has to go fix by hand. The card had no way to tell the two apart: it only receives `isReconnectPending`, which tracks a *manual* reconnect the user asked for via `useReconnectRelay`, and is false during the background loop. `ConnectionState` already distinguishes `reconnecting` from `disconnected`/`stalled`; the card just never saw it. Thread that through as `isAutoReconnecting` and use it for the no-user-action-needed case only: reconnecting -> "Reconnecting" + spinner, role=status disconnected / stalled -> unchanged alarm copy, role=alert The action button stays enabled while auto-reconnecting so anyone who does not want to wait out the backoff can still force an attempt. `stalled` deliberately keeps the alarm copy: the socket is open but no frames are arriving, so the client is not making progress on its own. Also point `relay-connectivity.spec.ts`'s local `settle()` at the shared `waitForAnimations` helper. The card now renders a looping spinner in this state, and a looping animation's `.finished` never resolves, so the old unbounded `Promise.allSettled(getAnimations())` hung until Playwright aborted the evaluate. The shared helper races the wait against a ceiling for exactly this reason. Verified: desktop `pnpm check`, `pnpm test` (3923 pass), and the `relay-connectivity` + `sidebar-relay-card` smoke specs (6 pass). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: James Risbey <james@sunnn.co>
|
Follow-up on verification: I ran the full 813 passed, 8 failed, 1 skipped. None of the failures are from this change — I confirmed by checking out
The failures are the same pre-existing set on both sides — Worth flagging separately: The specs this PR actually touches — |
Problem
The sidebar relay card renders "Can't reach the relay" / "Click to connect" for every degraded connection state — including
reconnecting, which is the state the relay client sets while its own backoff loop is actively retrying.So a transient drop the app is already recovering from without help is presented as a dead connection the user has to go fix by hand. Clicking does not make it recover any faster.
The card had no way to tell the two cases apart. It only receives
isReconnectPending, which tracks a manual reconnect the user asked for viauseReconnectRelay— that flag isfalsefor the whole duration of the background loop.ConnectionStatealready distinguishesreconnectingfromdisconnected/stalled; the card simply never saw it.What this changes
Threads the existing state through as
isAutoReconnectingand uses it only for the no-user-action-needed case:reconnectingrole=alertrole=statusdisconnected,stalledTwo deliberate choices:
stalledkeeps the alarm copy. The socket is open but no frames are arriving, so the client is not making progress on its own — that one genuinely warrants the user. Onlyreconnectingis downgraded.roledrops fromalerttostatusfor the auto-reconnecting case so screen readers do not announce a self-healing blip as an assertive alarm.Why it matters
On a hosted community I measured 420 reconnects over 6 days across the agent sessions on one machine. 44% of the gaps between them cluster in a 15m30s–20m band (median 17.1 min) with a hard floor at 15m30s — the shape of a proxy/LB max connection age, not instability. The overwhelming majority recover on the first attempt in under a second.
useRelayConnectionalready debounces degraded states by 2s, so the sub-second ones never paint. The ones that do paint are still usually recovering unassisted within a few seconds — and for that whole window the sidebar is telling the user the app is broken and they need to intervene.Test changes
relay-connectivity.spec.tstests 01, 02 and 06 drivereconnectingand asserted the alarm copy; they now assert the reconnecting copy andnot.toContainText("Click to connect"). Thestalledassertions insidebar-relay-card.spec.tsare untouched and still pass.That file's local
settle()helper now delegates to the sharedwaitForAnimations. The card renders a looping spinner in this state, and a looping animation's.finishednever resolves — the old unboundedPromise.allSettled(document.getAnimations())hung until Playwright aborted the evaluate. The shared helper races the wait against a ceiling for exactly this reason (and its doc comment calls out spinners specifically).Verification
pnpm check— cleanpnpm test— 3923 pass, 0 failpnpm exec playwright test --project=smoke relay-connectivity.spec.ts sidebar-relay-card.spec.ts— 6 passedI do not have a screenshot in this PR:
scripts/post-screenshots.shpushes to a branch on this repo, which I cannot write to from a fork. Happy to attach one if a maintainer would rather see it.