feat: multi-session parallel tabs with instant switching and split pane#189
feat: multi-session parallel tabs with instant switching and split pane#189
Conversation
… split pane Introduce a Zustand-based session tab system that enables users to work with multiple chat sessions in parallel: - Session Tab Bar: draggable tabs at the top of the chat area for instant switching between open sessions, with provider icon, title, and status. - Session Snapshots: save/restore full chat state (messages, scroll pos, loading status) in memory so tab switches are instant without re-fetching. - Background Status Indicators: three-state model on tabs and sidebar — amber spinner while a session is actively processing, green dot when completed but unread, no icon once viewed. - Split Pane: side-by-side view of two sessions with a resizable divider, toggled from the tab bar context menu. - WebSocket Multiplexing: background sessions receive real-time updates (token counts, completion events) routed through the existing WS channel. New files: src/types/sessionTabs.ts src/stores/useSessionTabsStore.ts src/components/chat/view/subcomponents/SessionTabBar.tsx src/components/chat/view/subcomponents/SplitPaneContainer.tsx Modified files: AppContent.tsx — sync tab store active ID with app routing useChatSessionState.ts — snapshot save/restore on session switch useChatRealtimeHandlers.ts — background session status updates MainContent.tsx — mount SessionTabBar + SplitPaneContainer SidebarSessionItem.tsx — loading/unread indicators useProjectsState.ts — addTab on session select/navigate package.json — add zustand dependency Made-with: Cursor
…tion - SplitPaneContainer: keep all ChatInterface instances mounted (CSS display:none for hidden tabs) — eliminates API reload on every tab switch - SessionTab: add stable `tabKey` UUID used as React key so instances survive session ID replacement (new-session-* → real ID) - useSessionTabsStore.setActiveTab: always clear backgroundStatus on activation (previous conditional left stale entries) - useChatSessionState: scope localStorage keys by session ID to prevent cross-session message bleed - useChatRealtimeHandlers: guard session-aborted handler so only the owning instance handles it; cleanup session-scoped localStorage on claude/codex-complete - MainContent: remove useChatTabs React-state machinery in favour of Zustand store; keep SessionTabBar + SplitPaneContainer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Review notesCI is green and I like the "all tabs stay mounted" idea for instant switching. But there are a few things to resolve before merging — I verified each against the branch source. 1. Relationship with the already-merged #174 / #181 is unclearThe merge base of this PR is This PR effectively replaces #174’s architecture:
But the old files ( Please either:
2. Accessibility / keyboard regressions from #181 are not carried overOn
The new 3. Unrelated
|
Summary
ChatInterfaceinstances stay mounted simultaneously via CSSdisplay:none/position:absolutetoggling; no unmount/remount/API reload on every tab switchSessionTabBar) with drag-to-reorder, close button, background loading indicator, and unread dot; hides when only one tab is openchat_messages_${project}_${sessionId}) to prevent cross-tab message bleedtabKeyUUID assigned once; used as Reactkeyso theChatInterfaceinstance survives thenew-session-*→ real-ID replacement without remountingsession-abortedguard — only theChatInterfacethat owns the aborted session handles the event (prevents all mounted instances from each appending an "interrupted" message)Architecture
Test plan
new-session-*tab is replaced with real session ID without remounting🤖 Generated with Claude Code