mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 20:03:13 +08:00
3d3dfdad76
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
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { useTabStore } from '../../stores/tabStore'
|
|
import { EmptySession } from '../../pages/EmptySession'
|
|
import { ActiveSession } from '../../pages/ActiveSession'
|
|
import { ScheduledTasks } from '../../pages/ScheduledTasks'
|
|
import { Settings } from '../../pages/Settings'
|
|
import { TerminalSettings } from '../../pages/TerminalSettings'
|
|
|
|
export function ContentRouter() {
|
|
const activeTabId = useTabStore((s) => s.activeTabId)
|
|
const tabs = useTabStore((s) => s.tabs)
|
|
const activeTabType = tabs.find((t) => t.sessionId === activeTabId)?.type
|
|
const terminalTabs = tabs.filter((tab) => tab.type === 'terminal')
|
|
|
|
let page: ReactNode = null
|
|
if (!activeTabId || !activeTabType) {
|
|
page = <EmptySession />
|
|
} else if (activeTabType === 'settings') {
|
|
page = <Settings />
|
|
} else if (activeTabType === 'scheduled') {
|
|
page = <ScheduledTasks />
|
|
} else if (activeTabType !== 'terminal') {
|
|
page = <ActiveSession />
|
|
}
|
|
|
|
return (
|
|
<div className="relative min-h-0 flex-1 overflow-hidden">
|
|
{page && (
|
|
<div className="absolute inset-0 z-10 flex min-h-0 flex-col overflow-hidden">
|
|
{page}
|
|
</div>
|
|
)}
|
|
{terminalTabs.map((tab) => {
|
|
const active = tab.sessionId === activeTabId
|
|
const visible = activeTabType === 'terminal' && active
|
|
return (
|
|
<div
|
|
key={tab.sessionId}
|
|
aria-hidden={!visible}
|
|
data-testid={`terminal-tab-panel-${tab.sessionId}`}
|
|
className={`absolute inset-0 flex min-h-0 flex-col overflow-hidden ${
|
|
visible ? 'z-20 opacity-100' : 'pointer-events-none z-0 opacity-0'
|
|
}`}
|
|
>
|
|
<TerminalSettings
|
|
active={active}
|
|
workspace
|
|
testId={`terminal-host-${tab.sessionId}`}
|
|
onNewTerminal={() => useTabStore.getState().openTerminalTab()}
|
|
/>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|