feat(chat): reconnect to running sessions from any tab/browser (T-52) - #11
Merged
Conversation
Adds `/c/:sessionId` deep-link routing so the active chat session is addressable by URL instead of living only in in-memory zustand state: - `useSessionRoute` (new) keeps the URL and the store's `activeId` in sync in both directions. Reloading the tab, using "reopen closed tab", or pasting the URL into a second browser now reconnects to the same session and picks up its live WS event stream (session handles already supported multiple concurrent subscribers server-side; the gap was purely client-side session addressing). - A bare `/` restores whatever session was last active via a `localStorage`-backed `activeId` default, so a brand-new tab (not just a restored one) reconnects too. - `resumeIfKnown` in the store auto-resumes a session from disk when a `subscribe` attempt finds it not currently running in this server process (idle-reaped, or the server restarted) but it still exists on disk — covers "the agent should keep running in the background even after I close its tab" without silently leaving the tab stuck on a blank screen. No server/protocol changes: multi-subscriber fan-out and idle-reap grace already existed in the bridge; this closes the client-side reconnect gap only.
jaesbit
added a commit
that referenced
this pull request
Jul 3, 2026
…anban workspace UX (T-17/18/19/27/38-52) (#12) Fills in the [Unreleased] changelog gap left by twelve merged PRs (#1-#11 plus the unnumbered T-19/abort-shortcut/dispose-collision fixes) that shipped functionality without a doc pass: - Session launch bundle: atomic model+Plan Mode at creation, Settings -> Workspaces per-cwd model default, native task priority, Send to agent. - Goal Mode: /goal composer command, autonomous lifecycle, Plan Mode mutual exclusion. - Kanban workspace color markers + header workspace filter. - Multi-tab/multi-browser session continuity: /c/:sessionId deep links, transparent resume-on-reconnect, live sessions_changed broadcast. - Chat polish: pinned todos panel, scroll-to-bottom button. - Workspace picker: arbitrary path + folder browse dialog. - Configurable stop-streaming keyboard shortcut. - Fixed: orphaned session handle on resume/create collision. Touches CHANGELOG.md (new Unreleased section), README.md (What you get), docs/tui-parity.md (deck-only list + multi-session row), docs/architecture.md (broadcast frames + DB tables), docs/slash-commands.md (/goal section).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
T-52: closing a session's tab and reopening it doesn't reconnect to the running agent. The agent should keep running in the background regardless, and opening the same session from a second browser should live-sync both views over the shared WebSocket without killing either.
Root cause
Server-side was already correct:
InProcessAgentBridgesupports multiple concurrent WS subscribers per session (Active.subscribers: Set<string>), fans outsession_events to all of them, and only reaps a session after it's been idle and has zero subscribers foridleTimeoutMs(never mid-turn). The gap was entirely client-side: the active session id (activeId) lived only in in-memory zustand state with no URL orlocalStoragebacking, so any full page load (tab close/reopen, reload, a second browser) always started atactiveId: undefinedand landed on the empty picker — even while the agent kept running server-side.Change
/c/:sessionIdroute +useSessionRoute()hook keeps the URL and the store'sactiveIdin sync in both directions (URL to store on load/back-forward, store to URL on any other session change). Deep-linking to a session now works, and switching between two different sessions no longer gets clobbered by a stalelocalStoragedefault (caught this in manual verification — the store-to-URL mirror effect needed to wait for the URL-to-store adopt effect to run at least once, otherwise alocalStorage-restoredactiveIdcould stomp a legitimate/different/c/:idlink before it was ever adopted)./restores the last-active session via a newlocalStorage-backed default, so a brand-new tab (not just a restored one) reconnects too.resumeIfKnownin the store: when asubscribeattempt comes back"session not active"(idle-reaped, or the server restarted) for a session that still exists on disk, transparently resumes it instead of leaving the tab stuck on a blank screen. Falls back to clearingactiveId(back to the picker) only if the session genuinely doesn't exist.No server or protocol changes — the multi-subscriber fan-out and idle-reap grace already existed; this closes the client-side reconnect gap only.
Verification
bun run --filter '@omp-deck/web' typecheck— clean.cd apps/web && bun test— 139 pass.cd apps/server && bun test— 212 pass (unaffected, no server changes).bun run --filter '@omp-deck/web' build— clean production build./c/:idloads the session directly (no picker, no 404)./c/:iddeep links adopts the new session correctly./redirects to the last-active session vialocalStorage./.Out of scope
Idle-reap timeout policy itself is unchanged (still disposes a session after it's genuinely idle with zero subscribers) — that's expected server-side resource hygiene, not the reported bug; the fix is that reconnecting to it is now transparent.