Tested:
- cd desktop && bun run test -- --run src/pages/TraceList.test.tsx
- bun test src/server/__tests__/trace-capture.test.ts
- bun run check:desktop
- bun run check:server
Scope-risk: broad
Users can be typing in the docked terminal when they hide the panel or promote it into a tab. The old component-owned lifecycle treated those UI moves as terminal teardown and spawned a fresh shell afterward.
This moves terminal ownership into a small runtime registry keyed by panel or tab identity, keeps docked terminals mounted while hidden, and transfers the runtime id to the terminal tab when promoted. Closing the owning tab or session still releases the PTY.
Constraint: Keep native PTY behavior unchanged and fix this in the desktop React lifecycle layer.
Rejected: Persist terminal tabs through localStorage | runtime PTYs are process-local and should not be restored after app restart.
Confidence: high
Scope-risk: moderate
Directive: Do not tie terminal process lifetime to panel visibility; only explicit owning-surface close/restart should destroy it.
Tested: cd desktop && bun run test -- src/pages/TerminalSettings.test.tsx src/pages/ActiveSession.test.tsx src/components/layout/ContentRouter.test.tsx src/stores/tabStore.test.ts
Tested: bun run check:desktop
Not-tested: Manual Tauri window PTY smoke.
Tab switches into a long session paid for everything that was already paid for
the last time the user was on that session. Specifically the per-message height
and metric maps were cleared on every switch, so the new commit could only feed
estimated heights into buildVirtualTranscriptWindow and then chase them with
ResizeObserver callbacks. The first commit also ran getBranchableMessageTargets
and getCompletedTurnTargets — two extra O(N) walks over the messages array —
synchronously, and scrollToBottom('auto') was called inside the switch's
useLayoutEffect, which (on JSDOM and depending on layout state) added a
scrollHeight read to that critical path.
Introduces virtualHeightCache, a small module-level LRU keyed by sessionId that
holds the height and metric maps. MessageList now reads the prior maps on
switch instead of clearing them, so revisiting a session uses real measured
heights from the previous visit and skips the estimate→measure-correction
cascade. tabStore.closeTab calls dropSession so closed tabs don't leak.
The two O(N) branch / completed-turn computations now read from
useDeferredValue(messages), so they run as a low-priority follow-up render
after the first paint instead of blocking the switch commit. The switch's
useLayoutEffect now writes the bottom sentinel directly instead of calling
scrollToBottom, keeping the layout-read path out of the synchronous switch.
Tested: 731/731 desktop vitest suites pass, including 5 new virtualHeightCache
LRU/isolation/restore cases
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Open session tabs can outlive the title value they were created with, while the sidebar and session detail view read from the refreshed session list. Refresh existing tab titles when a session tab is reopened and after session list refreshes so the top tab bar cannot keep a stale generated title.
Constraint: Sidebar filtering and session refresh should not leave already-open tabs with stale labels.
Rejected: Only rely on WebSocket title update events | restored tabs and already-open tabs can miss later session-list corrections.
Confidence: high
Scope-risk: narrow
Directive: Keep session tab titles derived from current session list data when available.
Tested: cd desktop && bun run test -- --run src/stores/tabStore.test.ts src/stores/sessionStore.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run
Tested: agent-browser smoke on http://127.0.0.1:1420/ confirmed top tabs match sidebar and session detail titles.
Bring the detached worktree terminal improvements into the local main line so desktop sessions can open a bottom terminal in the current project directory and still promote it to a full tab when needed.
Constraint: Local main already contains additional desktop fixes after the worktree base
Confidence: high
Scope-risk: moderate
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: bun run quality:pr is blocked by existing branch policy requiring allow-cli-core-change approval
Desktop terminal access should behave like an IDE: active project sessions open a bottom panel in the session working directory while keeping a full terminal tab available for dedicated use. The panel has constrained resizing and cleanup so session tab state remains isolated, and terminal guidance points users to the bundled claude-haha command for extension setup.
Constraint: Desktop bundles the user-facing CLI as claude-haha while claude-sidecar remains internal
Rejected: Always opening a standalone terminal tab | loses the current project context and diverges from common IDE behavior
Rejected: Exposing claude-sidecar in terminal guidance | it is an internal launcher, not the supportable user command
Confidence: high
Scope-risk: moderate
Directive: Keep bottom terminals keyed by session id and pass session workDir/projectPath into spawned terminals
Tested: bun run check:desktop
Tested: git diff --check
Tested: Computer Use E2E against built macOS app during implementation
Not-tested: bun run quality:pr is blocked by existing branch policy requiring allow-cli-core-change approval
The desktop composer now treats draft and not-yet-running sessions as pending
until a real CLI context snapshot exists. Context inspection has a lightweight
context-only path so the composer and /context panel do not wait on unrelated
usage or MCP status work, and desktop boot no longer blocks the first shell
render on tab restoration.
Constraint: Empty composer state has no live CLI context to inspect until a session starts
Rejected: Display 0% before live context exists | it implied one user message consumed the fixed prompt/tool baseline
Confidence: high
Scope-risk: moderate
Directive: Do not show numeric context usage without a real context snapshot
Tested: cd desktop && bun run test -- pages.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: bun run check:server
Tested: Browser reload at http://127.0.0.1:5174 with cleared open-tabs showed pending context and no /inspection request
The desktop terminal already supported independent PTY sessions, but it only lived inside settings. This change promotes it to a first-class tab workflow so users can open multiple host terminals without leaving the chat-oriented desktop layout.
Constraint: Tauri terminal sessions are process-backed and must stay mounted while switching tabs.
Rejected: Reuse the settings terminal as a navigated page only | it cannot support multiple independent terminal tabs.
Rejected: Hide inactive xterm panes with display none | xterm lost visible output after tab switches during E2E.
Confidence: high
Scope-risk: moderate
Directive: Keep inactive terminal panes mounted and avoid display none unless xterm repaint behavior is reverified.
Tested: bun run test src/components/layout/ContentRouter.test.tsx src/components/layout/Sidebar.test.tsx src/components/layout/TabBar.test.tsx src/pages/TerminalSettings.test.tsx
Tested: bun run lint
Tested: bun run build
Tested: ./scripts/build-macos-arm64.sh
Tested: Computer Use E2E against build-artifacts/macos-arm64 app for multiple terminals, command output retention, tab switching, and terminal cleanup
Not-tested: Intel macOS package
Instead of close tab → delete session → create session → open tab (which
causes visual flicker and tab jumping), create the new session first then
replace the tab's sessionId in-place via a new replaceTabSession method.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Tab drag-and-drop reordering with visual drop indicator
- Add moveTab action to tabStore with localStorage persistence
- Context menu: add "Close Left" and "Close All" options
- Add i18n translations for new menu items (en/zh)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Settings and Scheduled Tasks now open as special tabs in the TabBar,
matching IDE conventions (closeable, icon-prefixed, persistent)
- New Session directly creates a session with the current workDir
- ChatInput shows DirectoryPicker before first message, locks after
- Remove connection status from StatusBar (unnecessary noise)
- ContentRouter routes by tab type instead of activeView
- Settings page no longer has a back-button header
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>