fix(terminal): report a failed reconnect instead of silently repainting #73
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/reconnect-feedback"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Targets
fix/reconnect(#71), notmain— it builds on that fix. Merge #71 first and this retargets tomaincleanly.Follow-up to the caveat raised in #71.
The bug
A reconnect the server rejects (workspace stopped → 409, vault locked → 401) left the overlay reading exactly what it read before the click:
disconnected. Before and after were byte-identical, so a rejected attempt was indistinguishable from a click that never landed — the button read as broken twice over, first because it genuinely was (#71), then because nothing it did was observable.The fix
The overlay now carries the attempt's outcome:
disconnectedreconnecting…(button disabled)reconnect failed+the workspace may not be runningWhy the hint is hedged rather than naming the cause. The server rejects with a real status and body (
upgrade.tsreject()—401 locked,409 container <state>), but a browser exposes neither to JS: a rejected handshake surfaces asCloseEventcode1006with an empty reason. Verified in-browser rather than assumed:So naming a cause would be a guess. Reporting the real reason requires a separate probe of worker state — deliberately out of scope; happy to do it as a follow-up if you want the overlay to say "workspace is stopped" outright.
Two decisions worth reviewing
One 4-state
linkreplacing thedisconnectedboolean, not two more booleans.connectingandfailedare mutually exclusive; three booleans would encode 8 states of which half are nonsense. It starts'up', so a fresh pane still shows no overlay while its first socket opens — unchanged behaviour.The transition is derived from each socket's own
openedflag, not from the currentlink. A rejected handshake fires bothonerrorandonclose. A state-derived transition (link === 'connecting' ? 'failed' : 'down') clobbers'failed'back to'down'on the second event and reprints "disconnected" — reintroducing the exact bug. Theopenedflag makes it order-independent and idempotent. The recorded transition below is what proves it.The disabled style is scoped to the overlay.
app.cssstyles only.btn.primary:disabled; the Reconnect button is.btn sm, so disabling it without a style would leave it looking clickable but inert — the very failure this overlay exists to report. Adding.btn:disabledglobally would silently restyle every other plain disabled button (e.g.Workers.svelte:182), so the rule stays scoped.Verification
The suite is
environment: 'node'with no DOM, so this is browser-verified. Transitions recorded with aMutationObserveragainst a stopped workspace:disconnected(no regression).reconnecting… → reconnect failed.opacity: 0.4,cursor: not-allowed.Noted, not touched
app.csshas no.btn:disabledrule at all — only.btn.primary:disabled. So any plain.btnelsewhere in the app that gets disabled is inert-but-looks-enabled. Out of scope here; worth a look separately.