mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
4b1536be2c
Two reported console bugs, one shared root: a pane can outlive its session, and nothing brought the two back together. Reconnect: an interactive pane whose stream died (ws closed/evicted elsewhere, node restart, re-home) could never reconnect while its tab existed — openPane() on an existing pane was focus-only, the controller's connect() is one-shot, and its 5s recovery loop re-dialed the SAME node forever (infinite 404 polling through the console proxy). The only workaround was closing the tab before resuming. - createInteractivePane now tracks terminal failure: 3 consecutive CLOSED recovery beats -> give up (stream closed, timers + any pending history load invalidated, status bar "Disconnected", opts.onDead fired once). host.onStreamOpen (new hook) resets the counter; isDead()/markDead()/base join the controller surface; onLogin ignores a dead controller — revive owns recovery, so a deliberately closed session is never resurrected by a timer. - PaneManager.openPane fires pane.onReopen(extra) when it targets an ALREADY-OPEN pane — the explicit-intent signal (saved-list resume, rail row, child link) that activate() can't carry (hooks no-op on the active pane, and onActivate also fires on plain tab switches). getPane() added for cross-cutting lifecycle signals. - The shell paints a click-to-reconnect banner on give-up — and immediately on Tier-1 ws_closed via the new TS_SHELL.notifySessionClosed seam (the console keeps the tab, unlike the standalone's auto-close, so the conversation stays readable). Reopen/banner-click revives: tear down the dead controller, re-resolve through the origin-first POST /open lane, rebuild. The forced resolve skips BOTH beginConnect fast paths (a stale Tier-1 row must not bypass /open) while a live node leads the hint chain (an origin-first /open then reuses a genuinely-live session instead of loading a duplicate on the old meta node). The standalone lane POSTs its local /open on revive too — /events 404s on an unloaded ws. - Coordinator parity: the factory exposes reconnect() (acts only on a missing/CLOSED stream; OPEN is healthy, CONNECTING is already being worked) and the pane's onReopen drives it — the saved-list resume POSTs /open before openPane, so a fresh stream is all it needs. Tab menu: a node-proxied interactive pane's dropdown gated every verb on classic globals that only exist in ui/static/app.js, so the console got a nearly-empty menu whose one surviving verb (Export) hit the console origin and 404'd. convTabMenu gains a base-aware fallback lane: verbs POST against the pane's OWN transport base (controller's exact base -> persisted node hint -> live Tier-1 node; a verb is omitted while no base is resolvable — never aimed at the wrong origin). Close/Delete confirm first (window.confirm, the coordinator precedent) and treat 404 as intent-satisfied (nothing left to stop/delete -> drop the tab). exportWorkstreamDownload takes the base. The standalone keeps its globals lane (incl. Fork) byte-identical, and an empty verb section no longer renders a leading separator. Verified: 189 JS-pin tests; two headless-Chrome live-DOM harnesses driving the real modules — console 16/16 (connect -> ws_closed -> banner -> reopen revives on a new node with the fresh hint -> give-up stops retrying -> live-node-led resolve), standalone 10/10 (globals menu intact, revive POSTs /open exactly once, no cluster resolve).