| 1 | import { lazy, Suspense, useMemo, useState, useLayoutEffect, type CSSProperties } from "react"; |
| 2 | import { DockNavigation } from "./dockNavigation"; |
| 3 | import { useFileNavigationRuntime } from "../app-runtime/useFileNavigationRuntime"; |
| 4 | import { useActiveRemoteRef } from "../app-runtime/useActiveRemoteRef"; |
| 5 | import { fileNavigationKey } from "../lib/fileNavigationOwner"; |
| 6 | import { useActivityBarStore } from "../store/activityBar"; |
| 7 | import { ShellExpandProvider } from "../lib/shellExpand"; |
| 8 | import { RemoteNavigationContext } from "../lib/remoteNavigationCommands"; |
| 9 | import { UpdaterProvider } from "../lib/useUpdater"; |
| 10 | import type { State } from "../lib/useController"; |
| 11 | import type { TabMeta } from "../lib/types"; |
| 12 | import type { RemoteSessionApi } from "../lib/useRemoteSession"; |
| 13 | import type { Translator } from "../lib/i18n"; |
| 14 | import type { useAppRuntimeAdapter } from "../app-runtime/useAppRuntimeAdapter"; |
| 15 | import type { useNavigationSurface } from "../lib/useNavigationSurface"; |
| 16 | import type { useAppShellStores } from "../app-runtime/useAppShellStores"; |
| 17 | import type { useAppSessionComposition } from "../app-runtime/useAppSessionComposition"; |
| 18 | import type { useAppNavigationComposition } from "../app-runtime/useAppNavigationComposition"; |
| 19 | import type { useSessionDraftSurface } from "../app-runtime/useSessionDraftSurface"; |
| 20 | import { creationHeroVisible } from "./draftPresentation"; |
| 21 | import type { HistoryViewState } from "../app-runtime/historyViewProjection"; |
| 22 | import { ShellHotkeys, TextSizeHotkeys } from "./HotkeyRegistrations"; |
| 23 | import { WindowChromeLifecycle } from "../app-runtime/WindowChromeLifecycle"; |
| 24 | import { StartupGateLifecycle } from "../app-runtime/StartupGateLifecycle"; |
| 25 | import { AppRuntimeEffects } from "../app-runtime/AppRuntimeEffects"; |
| 26 | import { ThemeBackground } from "../components/ThemeBackground"; |
| 27 | import { useTopicbarHeightVar } from "../lib/useTopicbarHeightVar"; |
| 28 | import { SidebarRegion } from "./SidebarRegion"; |
| 29 | import { TopicbarRegion } from "./TopicbarRegion"; |
| 30 | import { buildTopicbarView, TopicbarActionsStack } from "./TopicbarActionsStack"; |
| 31 | import { DraftTopicbarActions } from "./DraftTopicbarActions"; |
| 32 | import { DockToggleButton } from "./DockToggleButton"; |
| 33 | import { LauncherToggleButton } from "./LauncherToggleButton"; |
| 34 | import { SessionStatusBanners } from "./SessionStatusBanners"; |
| 35 | import { ChatPaneRegion } from "./ChatPaneRegion"; |
| 36 | import { DecisionFooterRegion } from "./DecisionFooterRegion"; |
| 37 | import { WorkspaceDockRegion } from "./WorkspaceDockRegion"; |
| 38 | import { AppBottomRegions } from "./AppBottomRegions"; |
| 39 | import { AppOverlayHost } from "./AppOverlayHost"; |
| 40 | import { buildAppShellClassNames, buildSessionStatusBannerProps, buildSidebarRegionProps } from "./chromeRegionBuilders"; |
| 41 | import { buildBottomRegionsProps, buildWorkspaceDockProps } from "./dockRegionBuilders"; |
| 42 | import { buildOverlayHostProps } from "./overlayBuilders"; |
| 43 | import { buildComposerSurface, buildDecisionFooterSurface, buildFooterTodo, buildFooterUndo } from "./decisionFooterBuilders"; |
| 44 | |
| 45 | const WindowsWindowControls = lazy(() => import("./WindowsWindowControls").then((module) => ({ default: module.WindowsWindowControls }))); |
| 46 | const DockLauncher = lazy(() => import("../components/DockLauncher").then((module) => ({ default: module.DockLauncher }))); |
| 47 | |
| 48 | const WORKSPACE_RESIZER_WIDTH = 8; |
| 49 | const SHOW_CONTEXT_DOCK = true; |
| 50 | |
| 51 | type Runtime = ReturnType<typeof useAppRuntimeAdapter>; |
| 52 | type Shell = ReturnType<typeof useAppShellStores>; |
| 53 | type SessionComposition = ReturnType<typeof useAppSessionComposition>; |
| 54 | type NavigationComposition = ReturnType<typeof useAppNavigationComposition>; |
| 55 | type LiveStore = Runtime["snapshot"]["liveStore"]; |
| 56 | |
| 57 | export type AppRuntimeViewProps = { |
| 58 | core: { |
| 59 | state: State; |
| 60 | activeTab: TabMeta | undefined; |
| 61 | activeTabId: string | undefined; |
| 62 | liveStore: LiveStore; |
| 63 | remoteSurfaceActive: boolean; |
| 64 | remoteSession: RemoteSessionApi; |
| 65 | remoteComposerReady: boolean; |
| 66 | remoteCancel: (queuedItemIDs?: string[]) => Promise<import("../lib/inboxCancel").CancelOutcome>; |
| 67 | surface: ReturnType<typeof useNavigationSurface>; |
| 68 | t: Translator; |
| 69 | locale: string; |
| 70 | onOpenLink: (url: string) => void; |
| 71 | }; |
| 72 | shell: Shell; |
| 73 | session: SessionComposition; |
| 74 | navigation: NavigationComposition; |
| 75 | runtime: Runtime; |
| 76 | draft: ReturnType<typeof useSessionDraftSurface>; |
| 77 | local: { |
| 78 | tasksOpen: false | "session" | "all"; |
| 79 | setTasksOpen: React.Dispatch<React.SetStateAction<false | "session" | "all">>; |
| 80 | sidebarImDetailConnectionId: string; |
| 81 | setSidebarImDetailConnectionId: React.Dispatch<React.SetStateAction<string>>; |
| 82 | tabRevealSignal: number; |
| 83 | histView: HistoryViewState | null; |
| 84 | projectRevision: number; |
| 85 | dockRefreshKey: number; |
| 86 | composerFileRefRefreshKey: string; |
| 87 | refreshComposerFileRefs: () => void; |
| 88 | terminalContentVisible: boolean; |
| 89 | terminalFitEnabled: boolean; |
| 90 | prefetchTerminalPanel: () => void; |
| 91 | }; |
| 92 | }; |
| 93 | |
| 94 | /** |
| 95 | * Pure assembly of the App shell tree: every region receives its props from |
| 96 | * the session/navigation composition bags and the caller's stores. No hooks |
| 97 | * beyond value memoization live here; ownership stays in the compositions. |
| 98 | */ |
| 99 | export function AppRuntimeView(props: AppRuntimeViewProps) { |
| 100 | const [dockNavigation] = useState(() => new DockNavigation()); |
| 101 | const fileNavigation = useFileNavigationRuntime(); |
| 102 | useLayoutEffect(() => dockNavigation.attach(), [dockNavigation]); |
| 103 | useLayoutEffect(() => useActivityBarStore.subscribe(state => dockNavigation.reconcile(state.tabs.map(tab => tab.id))), [dockNavigation]); |
| 104 | // The open dock tabs are the set of live records: closing a tab, or switching |
| 105 | // to a project whose tab list has none of them, ends those records. A session |
| 106 | // change inside one project keeps the dock and only rebinds its credentials. |
| 107 | useLayoutEffect(() => { |
| 108 | const retain = () => fileNavigation.retain( |
| 109 | useActivityBarStore.getState().tabs.map(tab => fileNavigationKey({ sessionTabId: "", dockTabId: tab.id })), |
| 110 | ); |
| 111 | retain(); |
| 112 | return useActivityBarStore.subscribe(retain); |
| 113 | }, [fileNavigation]); |
| 114 | useTopicbarHeightVar(); |
| 115 | const { core, shell, session, navigation, runtime, local, draft } = props; |
| 116 | const { state, activeTab, activeTabId, t, locale } = core; |
| 117 | const { windowsFramelessChrome, mainWindowMaximised } = shell; |
| 118 | const draftActive = Boolean(draft.surface); |
| 119 | const activeRemoteRef = useActiveRemoteRef(draftActive ? undefined : activeTab); |
| 120 | const { |
| 121 | conversationView, visibleRuntimeState, sidebarImDetailConnection, |
| 122 | surfaceWorkspacePanelRenderable, surfaceWorkspacePanelGridOpen, surfaceWorkspacePanelOverlay, terminalSurfaceOpen, |
| 123 | controllerReady, decisionSurface, visibleDecisionSurface, composerSurfaceHidden, |
| 124 | shellGeometry, appRef, layoutRef, footerHeight, footerRef, |
| 125 | } = session; |
| 126 | const { chromeCommands, navigationCommands } = navigation; |
| 127 | const runtimeTransitioning = core.surface.transitioning; |
| 128 | const presentationTransitioning = runtimeTransitioning && core.remoteSurfaceActive; |
| 129 | const browserPreviewChrome = navigation.browserPreviewChrome; |
| 130 | const workspaceContextProject = Boolean( |
| 131 | activeTab?.remote || (activeTab?.scope === "project" && activeTab.workspaceRoot), |
| 132 | ); |
| 133 | const workspaceContextRoot = workspaceContextProject |
| 134 | ? (activeTab?.workspaceRoot ?? state.meta?.workspaceRoot ?? state.meta?.cwd ?? "") |
| 135 | : ""; |
| 136 | |
| 137 | const workbenchChromeHidden = true; |
| 138 | const sidebarClassName = [ |
| 139 | "sidebar", |
| 140 | shell.sidebarCollapsed ? "sidebar--collapsed" : "", |
| 141 | "sidebar--workbench", |
| 142 | ].filter(Boolean).join(" "); |
| 143 | const startupSplashHold = !activeTabId && state.meta?.ready !== true && !state.meta?.startupErr; |
| 144 | |
| 145 | const layoutStyle = useMemo( |
| 146 | () => |
| 147 | ({ |
| 148 | "--sidebar-expanded-width": `${shellGeometry.sidebarRenderWidth}px`, |
| 149 | "--chat-min-width": `${shellGeometry.chatReservedWidth}px`, |
| 150 | "--workspace-width": `${shellGeometry.workspacePanelRenderWidth}px`, |
| 151 | "--workspace-resizer-width": `${WORKSPACE_RESIZER_WIDTH}px`, |
| 152 | "--terminal-height": `${!draftActive && terminalSurfaceOpen ? shell.liveTerminalHeight ?? shellGeometry.terminalRenderHeight : 0}px`, |
| 153 | }) as CSSProperties, |
| 154 | [draftActive, shellGeometry.chatReservedWidth, shell.liveTerminalHeight, shellGeometry.sidebarRenderWidth, shellGeometry.terminalRenderHeight, terminalSurfaceOpen, shellGeometry.workspacePanelRenderWidth], |
| 155 | ); |
| 156 | |
| 157 | const shellClassNames = buildAppShellClassNames({ |
| 158 | platform: shell.desktopPlatform, |
| 159 | windowsFrameless: windowsFramelessChrome, |
| 160 | browserPreview: browserPreviewChrome, |
| 161 | imDetailActive: Boolean(sidebarImDetailConnection), |
| 162 | sidebarCollapsed: shell.sidebarCollapsed, |
| 163 | sidebarResizing: shell.sidebarResizing, |
| 164 | dockGridOpen: !draftActive && surfaceWorkspacePanelGridOpen, |
| 165 | dockOverlay: !draftActive && surfaceWorkspacePanelOverlay, |
| 166 | terminalOpen: !draftActive && terminalSurfaceOpen, |
| 167 | terminalResizing: shell.terminalResizing, |
| 168 | dockOpen: !draftActive && shell.workspacePanelOpen, |
| 169 | dockMaximized: shell.workspacePanelMaximized, |
| 170 | dockResizing: shell.workspacePanelResizing, |
| 171 | }); |
| 172 | const footerTodo = buildFooterTodo({ |
| 173 | show: session.todoPanel.showTodos, |
| 174 | identity: session.todoPanel.scopedTodoBatch, |
| 175 | todos: session.todoPanel.todos, |
| 176 | running: visibleRuntimeState.running, |
| 177 | pendingPrompt: visibleRuntimeState.pendingPrompt, |
| 178 | continueReady: Boolean(activeTabId && !activeTab?.readOnly && (core.remoteSurfaceActive ? core.remoteComposerReady : controllerReady)), |
| 179 | onContinue: session.todoPanel.handleTodoContinue, |
| 180 | onDismiss: session.todoPanel.dismissTodos, |
| 181 | }); |
| 182 | const footerUndo = buildFooterUndo({ rewindState: session.sessionUndo.rewindState, activeTabId, onUndo: session.sessionUndo.handleUndoRewind }); |
| 183 | const decisionFooterSurface = buildDecisionFooterSurface({ |
| 184 | view: { |
| 185 | surface: visibleDecisionSurface, |
| 186 | activeTabId, |
| 187 | cwd: state.meta?.cwd, |
| 188 | workspaceScopeKey: session.workspaceScopeKey, |
| 189 | approval: state.approval, |
| 190 | ask: state.ask, |
| 191 | mcpInteraction: state.mcpInteraction, |
| 192 | extensionForm: state.extensionForm, |
| 193 | workspaceConflict: session.workspaceConflict, |
| 194 | toolApprovalMode: session.profileProjection.toolApprovalMode, |
| 195 | insertRequest: session.insertCommands.activePlanRevisionInsertRequest, |
| 196 | }, |
| 197 | prompts: session.promptCommands, |
| 198 | extension: session.extensionSurface, |
| 199 | tabs: session.tabBarCommands, |
| 200 | clear: session.clearCommands, |
| 201 | onStop: session.controlCommands.handleStopActive, |
| 202 | cancelWorkspaceConflict: session.controlCommands.cancelWorkspaceConflict, |
| 203 | onOpenLink: core.onOpenLink, |
| 204 | onRevisionActiveChange: session.insertCommands.handleRevisionActiveChange, |
| 205 | t, |
| 206 | }); |
| 207 | |
| 208 | return ( |
| 209 | <ShellExpandProvider> |
| 210 | <RemoteNavigationContext.Provider value={session.desktopNavigation.openRemoteProject}> |
| 211 | <UpdaterProvider> |
| 212 | <ShellHotkeys /> |
| 213 | <TextSizeHotkeys /> |
| 214 | <WindowChromeLifecycle /> |
| 215 | <StartupGateLifecycle /> |
| 216 | <AppRuntimeEffects |
| 217 | running={state.running} |
| 218 | onEvent={session.runtimeEventCommands.handleRuntimeEvent} |
| 219 | onReady={session.runtimeEventCommands.handleRuntimeReady} |
| 220 | onRebuilt={session.runtimeEventCommands.handleRuntimeRebuilt} |
| 221 | onRemoteStatus={session.runtimeEventCommands.handleRemoteStatus} |
| 222 | onRemoteForwards={session.runtimeEventCommands.handleRemoteForwards} |
| 223 | onRemoteServer={session.runtimeEventCommands.handleRemoteServer} |
| 224 | onInitialRemoteHosts={session.runtimeEventCommands.handleInitialRemoteHosts} |
| 225 | onInitialRemoteStatuses={session.runtimeEventCommands.handleInitialRemoteStatuses} |
| 226 | /> |
| 227 | <div |
| 228 | ref={appRef} |
| 229 | onDoubleClickCapture={chromeCommands.handleChromeTitlebarDoubleClick} |
| 230 | className={shellClassNames.app} |
| 231 | > |
| 232 | <ThemeBackground /> |
| 233 | <div |
| 234 | ref={layoutRef} |
| 235 | className={shellClassNames.layout} |
| 236 | style={layoutStyle} |
| 237 | > |
| 238 | <a className="skip-to-composer" href="#composer-input"> |
| 239 | {t("shortcuts.skipToComposer")} |
| 240 | </a> |
| 241 | |
| 242 | <SidebarRegion {...buildSidebarRegionProps({ |
| 243 | className: sidebarClassName, |
| 244 | shell, |
| 245 | t, |
| 246 | geometry: shellGeometry, |
| 247 | projectTree: { |
| 248 | activeTab: draftActive ? undefined : activeTab, |
| 249 | activeRemote: activeRemoteRef, |
| 250 | activeScope: draft.surface?.draft.scope, |
| 251 | activeWorkspaceRoot: draft.surface?.draft.workspaceRoot, |
| 252 | imTopicSources: shell.preferences.imTopicSources, refreshSignal: local.projectRevision, |
| 253 | searchExpanded: true, searchFocusSignal: shell.sidebarSearchFocusSignal, |
| 254 | showShortcutBadges: navigation.topicShortcuts.showTopicBadges, shortcutPlatform: shell.desktopPlatform, |
| 255 | onVisibleTopicsChange: navigation.topicShortcuts.handleVisibleTopicsChange, |
| 256 | draftSummaries: draft.summaries, |
| 257 | onOpenDraft: draft.open, |
| 258 | }, |
| 259 | topics: navigation.projectTopicCommands, |
| 260 | paletteShortcut: navigation.commandPaletteShortcut, |
| 261 | commands: { |
| 262 | onNewSession: () => void navigationCommands.handleNewTab(), |
| 263 | onOpenPalette: () => void navigation.paletteCommands.openPalette(), |
| 264 | onOpenTrash: () => void navigation.historyCommands.openTrash(), |
| 265 | onOpenAutomation: () => shell.openPage({ kind: "automation" }), |
| 266 | onOpenSettings: chromeCommands.openSidebarSettings, |
| 267 | onOpenTopic: navigationCommands.handleOpenTopic, |
| 268 | }, |
| 269 | })} /> |
| 270 | |
| 271 | <TopicbarRegion view={buildTopicbarView({ |
| 272 | t, locale, activeTab: draftActive ? undefined : activeTab, cwd: draft.surface?.draft.workspaceRoot ?? state.meta?.cwd, |
| 273 | imDetail: draftActive ? null : sidebarImDetailConnection, imTopicSources: shell.preferences.imTopicSources, |
| 274 | chromeHidden: workbenchChromeHidden, windowsBrand: windowsFramelessChrome, |
| 275 | automationReturn: shell.automationReturn, |
| 276 | sidebar: { title: navigation.sidebarToggleTitle, blocked: navigation.sidebarExpandBlocked, pressed: shell.sidebarTogglePressed, collapsed: shell.sidebarCollapsed }, |
| 277 | rename: { editing: !draftActive && navigation.projectTopicCommands.topicbarEditing, draft: navigation.projectTopicCommands.topicTitleDraft }, |
| 278 | draft: draft.surface ? { |
| 279 | title: t("draft.badge"), |
| 280 | workspaceLabel: draft.surface.draft.scope === "project" |
| 281 | ? draft.surface.draft.workspaceRoot.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || draft.surface.draft.workspaceRoot |
| 282 | : t("draft.globalWorkspace"), |
| 283 | } : undefined, |
| 284 | })} commands={{ |
| 285 | openAutomation: () => shell.openPage({ kind: "automation" }), toggleSidebar: shellGeometry.toggleSidebar, |
| 286 | setTitleDraft: navigation.projectTopicCommands.setTopicTitleDraft, commitRename: navigation.projectTopicCommands.commitActiveTopicRename, cancelRename: navigation.projectTopicCommands.cancelActiveTopicRename, |
| 287 | startRename: navigation.projectTopicCommands.startActiveTopicRename, openWorktree: navigation.worktreeMergeCommands.openWorktreeMerge, |
| 288 | }}> |
| 289 | {draft.surface ? <DraftTopicbarActions |
| 290 | t={t} |
| 291 | draft={draft.surface} |
| 292 | onSetMCPEnabled={draft.setMCPEnabled} |
| 293 | onDiscard={() => void draft.confirmDiscard({ |
| 294 | title: t("draft.discardTitle"), |
| 295 | message: t("draft.discardTitle"), |
| 296 | detail: t("draft.discardDetail"), |
| 297 | confirmLabel: t("draft.discardConfirm"), |
| 298 | cancelLabel: t("common.cancel"), |
| 299 | })} |
| 300 | /> : <TopicbarActionsStack |
| 301 | t={t} |
| 302 | activeTab={activeTab} |
| 303 | activeTabId={activeTabId} |
| 304 | imDetailActive={Boolean(sidebarImDetailConnection)} |
| 305 | dismissSignal={shell.transientOverlayDismissSignal} |
| 306 | sessionHasContent={session.sessionHasContent} |
| 307 | exportCommands={session.sessionExportCommands} |
| 308 | terminal={{ toggle: session.terminalPanelCommands.toggleTerminalPanel, enabled: !core.remoteSurfaceActive, open: shell.terminalPanelOpen && !core.remoteSurfaceActive, prefetch: local.prefetchTerminalPanel }} |
| 309 | tasksOpen={local.tasksOpen} |
| 310 | setTasksOpen={local.setTasksOpen} |
| 311 | onCloseTasks={() => local.setTasksOpen(false)} |
| 312 | onOpenTaskSession={navigationCommands.openTaskMonitorSession} |
| 313 | dockToggle={<DockToggleButton renderable={surfaceWorkspacePanelRenderable} t={t} onToggle={session.workspacePanelCommands.toggleWorkspacePanel} />} |
| 314 | launcherToggle={<LauncherToggleButton |
| 315 | visible={session.workspacePanelCommands.launcherCard.visible} |
| 316 | t={t} |
| 317 | onToggle={session.workspacePanelCommands.toggleLauncherCard} |
| 318 | />} |
| 319 | />} |
| 320 | </TopicbarRegion> |
| 321 | |
| 322 | <section className={`chat-pane${creationHeroVisible(draft.surface, session.transcript.emptyHero) ? " chat-pane--creation-empty" : ""}`}> |
| 323 | {!draft.surface && <SessionStatusBanners {...buildSessionStatusBannerProps({ |
| 324 | t, |
| 325 | activeTab, |
| 326 | leaseBlocked: session.leaseBlockedTab ? { tabId: session.leaseBlockedTab.id, message: session.leaseBlockedTab.runtime!.issue!.message } : null, |
| 327 | meta: state.meta, |
| 328 | configWarnings: shell.preferences.configLoadWarnings, |
| 329 | dismissConfigWarnings: shell.preferences.dismissConfigWarnings, |
| 330 | updateChecksEnabled: shell.preferences.startupUpdateChecksEnabled === true, |
| 331 | shell, |
| 332 | banners: session.bannerCommands, |
| 333 | onboarding: navigation.onboardingCommands, |
| 334 | })} historical={core.remoteSurfaceActive ? undefined : { tab: activeTab, navigate: session.desktopNavigation.enqueueNavigation, |
| 335 | captureNavigation: () => { const intent = runtime.navigation.currentNavigationIntent(); return () => runtime.navigation.isNavigationIntentCurrent(intent); }, |
| 336 | }} />} |
| 337 | |
| 338 | <ChatPaneRegion |
| 339 | // Local navigation is now history-first: keep the transcript |
| 340 | // mounted while the controller is rebuilt in the background. The |
| 341 | // transcript's own hydrating/readiness state still reports progress |
| 342 | // and the controllerReady fence keeps writes disabled. Remote |
| 343 | // surfaces retain their existing transition gate because they do |
| 344 | // not share the local runtime/cache ownership model. |
| 345 | transitioning={presentationTransitioning} |
| 346 | t={t} |
| 347 | imDetail={sidebarImDetailConnection ? { |
| 348 | connection: sidebarImDetailConnection, |
| 349 | onClose: () => local.setSidebarImDetailConnectionId(""), |
| 350 | onOpenSettings: chromeCommands.openBotSettings, |
| 351 | onManageAllowlist: chromeCommands.openBotAllowlistSettings, |
| 352 | onOpenSession: (connection) => void navigationCommands.openSidebarImConnectionSession(connection), |
| 353 | } : null} |
| 354 | remote={activeTab?.remote ? { tab: activeTab, session: core.remoteSession } : undefined} |
| 355 | draft={draft.surface ? { |
| 356 | surface: draft.surface, |
| 357 | onUseSaved: draft.useSavedConflict, |
| 358 | onKeepLocal: () => void draft.keepLocalConflict(), |
| 359 | onRetrySave: () => void draft.retrySave(), |
| 360 | onDismissTaskError: () => draft.reportTaskError(draft.surface!.draft.id, draft.surface!.generation, ""), |
| 361 | onResume: () => void draft.resumeSubmission(), |
| 362 | onOpenSession: () => void draft.openAcceptedSession(), |
| 363 | onCheckSubmission: () => void draft.refreshSubmission(), |
| 364 | } : undefined} |
| 365 | launcher={!draftActive && session.workspacePanelCommands.launcherCardMounted && !core.remoteSurfaceActive ? ( |
| 366 | <Suspense fallback={null}> |
| 367 | <DockLauncher |
| 368 | tabId={activeTabId ?? ""} |
| 369 | scopeKey={session.workspaceScopeKey} |
| 370 | workspaceRoot={activeTab?.workspaceRoot ?? state.meta?.cwd ?? ""} |
| 371 | visible={!shell.managementActive && !sidebarImDetailConnection} |
| 372 | onSelect={session.workspacePanelCommands.openDockEntry} |
| 373 | gitBranch={state.meta?.gitBranch} |
| 374 | onSpaceModeChange={session.workspacePanelCommands.setLauncherSpaceMode} |
| 375 | overlay={session.workspacePanelCommands.launcherCardOverlay} |
| 376 | /> |
| 377 | </Suspense> |
| 378 | ) : null} |
| 379 | transcript={{ |
| 380 | state, |
| 381 | items: session.transcript.visibleTranscriptItems, |
| 382 | tabId: session.transcript.visibleTranscriptTabId, |
| 383 | geometrySessionKey: session.transcript.visibleTranscriptGeometryKey, |
| 384 | footerHeight, |
| 385 | invocationMetadata: session.transcript.visibleTranscriptTabId ? session.invocation.invocationMetadataByTab[session.transcript.visibleTranscriptTabId] : undefined, |
| 386 | surfaceCommitToken: core.surface.surfaceCommitToken, |
| 387 | liveStore: core.liveStore, |
| 388 | transcriptHydrating: session.transcript.transcriptHydrating, |
| 389 | navigationDataReady: core.surface.dataReady, |
| 390 | readOnly: Boolean(activeTab?.readOnly), |
| 391 | controllerReady, |
| 392 | hydratePlaceholderActive: session.hydratePlaceholderActive, |
| 393 | clearContextPending: session.clearCommands.clearContextPending, |
| 394 | emptyHero: session.transcript.emptyHero, |
| 395 | availability: session.transcript.availability, |
| 396 | rewind: { stateActive: session.sessionUndo.rewindState != null, committing: session.sessionUndo.rewindCommitting }, |
| 397 | }} |
| 398 | onRetryHistory={() => runtime.sessionActions.retrySessionHistory(activeTabId)} |
| 399 | commands={{ |
| 400 | onPrompt: session.transcript.handleTranscriptPrompt, |
| 401 | onFork: (turnId) => session.sessionUndo.handleForkTurn(turnId), |
| 402 | onLoadOlderHistory: session.transcript.handleLoadOlderHistory, |
| 403 | onLoadNewerHistory: session.transcript.handleLoadNewerHistory, |
| 404 | onSurfacePaintReady: session.transcript.handleSurfacePaintReady, |
| 405 | }} |
| 406 | /> |
| 407 | <DecisionFooterRegion |
| 408 | hidden={Boolean(sidebarImDetailConnection)} |
| 409 | className={["footer", terminalSurfaceOpen ? "footer--compact" : "", visibleDecisionSurface ? "footer--decision" : "", presentationTransitioning ? "footer--navigation-hidden" : ""].filter(Boolean).join(" ")} |
| 410 | footerRef={footerRef} |
| 411 | style={core.surface.surface?.phase === "source-retained" && footerHeight > 0 ? { height: footerHeight, minHeight: footerHeight, boxSizing: "border-box" } : undefined} |
| 412 | todo={draft.surface ? undefined : footerTodo} |
| 413 | undo={draft.surface ? undefined : footerUndo} |
| 414 | decision={draft.surface ? undefined : decisionFooterSurface} |
| 415 | composer={buildComposerSurface({ |
| 416 | view: { |
| 417 | hidden: composerSurfaceHidden, |
| 418 | inert: runtimeTransitioning, |
| 419 | hero: session.transcript.emptyHero, |
| 420 | headline: t("welcome.creation.title"), |
| 421 | remote: core.remoteSurfaceActive, |
| 422 | rewindCommitting: session.sessionUndo.rewindCommitting, |
| 423 | messageActionPending: state.messageAction != null, |
| 424 | decisionActive: Boolean(decisionSurface), |
| 425 | runtimeTransitioning: presentationTransitioning, |
| 426 | controllerReady: controllerReady && session.transcript.availability.kind === "ready", |
| 427 | submitDisabledReason: session.transcript.availability.kind !== "ready" && session.transcript.availability.source !== "runtime" |
| 428 | ? t("sessionRecovery.sendAfterRecovery") : undefined, |
| 429 | showContextWindowRing: false, |
| 430 | draftHint: t("draft.createOnSend"), |
| 431 | }, |
| 432 | base: conversationView.composer, |
| 433 | tab: activeTab, |
| 434 | tabId: activeTabId, |
| 435 | profile: session.profileProjection, |
| 436 | router: session.routerCommands, |
| 437 | modes: session.modeActions, |
| 438 | goals: session.goalCommands, |
| 439 | remoteGoal: session.remoteGoalActions, |
| 440 | modelSwitch: session.controllerProfileCommands, |
| 441 | inserts: session.insertCommands, |
| 442 | control: session.controlCommands, |
| 443 | remoteComposer: { |
| 444 | send: session.remoteComposerSend, |
| 445 | cancel: core.remoteCancel, |
| 446 | ready: core.remoteComposerReady, |
| 447 | profileReady: session.profileProjection.remoteComposerProfileReady, |
| 448 | liveStore: core.remoteSession.liveStore, |
| 449 | }, |
| 450 | localLiveStore: core.liveStore, |
| 451 | onInvocationMetadataChange: session.invocation.handleInvocationMetadataChange, |
| 452 | onCycleMode: session.cycleMode, |
| 453 | transientDismissSignal: shell.transientOverlayDismissSignal, |
| 454 | sessionKey: session.composerSessionKey, |
| 455 | workspaceScopeKey: session.workspaceScopeKey, |
| 456 | workspaceContext: { |
| 457 | scope: workspaceContextProject ? "project" : "global", |
| 458 | workspaceRoot: workspaceContextRoot, |
| 459 | workspaceName: workspaceContextProject |
| 460 | ? activeTab?.workspaceName ?? state.meta?.workspaceName |
| 461 | : undefined, |
| 462 | gitBranch: workspaceContextProject && !activeTab?.remote ? state.meta?.gitBranch : undefined, |
| 463 | tabId: activeTabId, |
| 464 | scopeKey: session.workspaceScopeKey, |
| 465 | remote: Boolean(activeTab?.remote), |
| 466 | onSwitchWorkspace: navigation.projectTopicCommands.onAddProject, |
| 467 | onWorkWithoutProject: () => navigationCommands.openBlankSession("global", ""), |
| 468 | onRefreshProjects: navigation.projectTopicCommands.refreshProjectsAndTabs, |
| 469 | }, |
| 470 | fileRefRefreshKey: local.composerFileRefRefreshKey, |
| 471 | guidance: session.transcript.latestGuidanceConsumed, |
| 472 | guidanceQueuePreviewItems: navigation.guidanceQueueMockItems, |
| 473 | draft, |
| 474 | })} |
| 475 | /> |
| 476 | </section> |
| 477 | |
| 478 | <WorkspaceDockRegion navigation={dockNavigation} fileNavigation={fileNavigation} workspaceRoot={activeTab?.workspaceRoot ?? state.meta?.cwd ?? ""} {...buildWorkspaceDockProps({ |
| 479 | surface: { renderable: !draftActive && surfaceWorkspacePanelRenderable, overlay: !draftActive && surfaceWorkspacePanelOverlay, gridOpen: !draftActive && surfaceWorkspacePanelGridOpen }, |
| 480 | showContext: SHOW_CONTEXT_DOCK, |
| 481 | remote: core.remoteSurfaceActive, |
| 482 | t, |
| 483 | context: conversationView.context, |
| 484 | sessionTurns: session.sessionTurns, |
| 485 | contextRefreshKey: local.dockRefreshKey + visibleRuntimeState.contextPanelSeq, |
| 486 | workspaceKey: session.workspaceTreeMemoryKey, |
| 487 | workspaceScopeKey: session.workspaceScopeKey, |
| 488 | mode: shell.rightDockMode, |
| 489 | meta: state.meta, |
| 490 | tabId: activeTabId, |
| 491 | completionSummary: state.completionSummary, |
| 492 | turnStartAt: state.turnStartAt, |
| 493 | layout: { treeWidth: shell.rightDockTreeWidth, previewWidth: shell.rightDockPreviewWidth, maximized: shell.workspacePanelMaximized }, |
| 494 | geometry: shellGeometry, |
| 495 | panels: session.workspacePanelCommands, |
| 496 | inserts: session.insertCommands, |
| 497 | verification: session.turnVerificationCommands, |
| 498 | onFileTreeRefresh: local.refreshComposerFileRefs, |
| 499 | onSessionRevertCommitted: session.sessionUndo.handleSessionRevertCommitted, |
| 500 | onOpenInTerminal: core.remoteSurfaceActive ? undefined : session.terminalPanelCommands.openTerminalForPath, |
| 501 | })} /> |
| 502 | <AppBottomRegions {...buildBottomRegionsProps({ |
| 503 | t, |
| 504 | chatSurfaceVisible: session.chatSurfaceVisible, |
| 505 | surfaceOpen: !draftActive && terminalSurfaceOpen, |
| 506 | contentVisible: local.terminalContentVisible, |
| 507 | remote: core.remoteSurfaceActive, |
| 508 | readOnly: Boolean(activeTab?.readOnly && !activeTab.takenOver), |
| 509 | tabId: activeTabId, |
| 510 | meta: state.meta, |
| 511 | fitEnabled: local.terminalFitEnabled, |
| 512 | liveTerminalHeight: shell.liveTerminalHeight, |
| 513 | geometry: shellGeometry, |
| 514 | terminal: { |
| 515 | onClose: session.terminalPanelCommands.closeTerminalPanel, |
| 516 | onAddOutput: (sessionId) => void session.insertCommands.addTerminalOutputToComposer(sessionId), |
| 517 | onAddToChat: session.insertCommands.addTerminalSelectionToComposer, |
| 518 | }, |
| 519 | status: draftActive || !session.statusBarVisible ? undefined : { |
| 520 | base: conversationView.status, |
| 521 | rewindCommitting: session.sessionUndo.rewindCommitting, |
| 522 | sessionTurns: session.sessionTurns, |
| 523 | labelStyle: shell.preferences.statusBarStyle, |
| 524 | items: shell.preferences.statusBarItems, |
| 525 | extensionStatuses: session.extensionStatusList, |
| 526 | remoteHosts: shell.remoteHosts, |
| 527 | remoteStatuses: shell.remoteStatuses, |
| 528 | onCancelJob: core.remoteSurfaceActive ? core.remoteSession.cancelJob : runtime.composer.cancelJob, |
| 529 | onCancelRuntimeJob: session.controlCommands.cancelRuntimeJob, |
| 530 | onRevealRuntime: session.tabBarCommands.revealBackgroundRuntime, |
| 531 | onConnectRemote: session.remoteWorkspaceCommands.connectAndOpenRemoteWorkspace, |
| 532 | onDisconnectRemote: session.controlCommands.handleDisconnectRemote, |
| 533 | onManageRemote: () => shell.setSettingsTarget("remote"), |
| 534 | onOpenRemote: shell.requestRemoteExplorer, |
| 535 | onOpenRemoteWorkspace: session.remoteWorkspaceCommands.openRemoteWorkspaceFromStatus, |
| 536 | }, |
| 537 | })} /> |
| 538 | </div> |
| 539 | |
| 540 | <AppOverlayHost {...buildOverlayHostProps({ |
| 541 | t, |
| 542 | running: state.running, |
| 543 | histView: local.histView, |
| 544 | pageKind: shell.page.kind, |
| 545 | activeTab, |
| 546 | activeTabId, |
| 547 | cwd: state.meta?.cwd, |
| 548 | paletteItems: navigation.paletteCommands.paletteItems, |
| 549 | startupSplashHold, |
| 550 | automationTopic: session.automation.openAutomationTopic, |
| 551 | shell, |
| 552 | history: navigation.historyCommands, |
| 553 | navigation: navigationCommands, |
| 554 | chrome: chromeCommands, |
| 555 | onboarding: navigation.onboardingCommands, |
| 556 | worktree: navigation.worktreeMergeCommands, |
| 557 | prefillSubagentCommand: session.insertCommands.prefillSubagentCommand, |
| 558 | sessionActions: { |
| 559 | previewSession: runtime.sessionActions.previewSession, |
| 560 | listTrashedSessions: runtime.sessionActions.listTrashedSessions, |
| 561 | restoreSession: runtime.sessionActions.restoreSession, |
| 562 | purgeTrashedSession: runtime.sessionActions.purgeTrashedSession, |
| 563 | }, |
| 564 | setSettingsTarget: shell.setSettingsTarget, |
| 565 | })} /> |
| 566 | {windowsFramelessChrome && ( |
| 567 | <WindowsWindowControls |
| 568 | maximised={mainWindowMaximised} |
| 569 | onMinimize={chromeCommands.minimizeMainWindow} |
| 570 | onToggleMaximize={chromeCommands.toggleMainWindowMaximized} |
| 571 | onClose={chromeCommands.closeMainWindow} |
| 572 | /> |
| 573 | )} |
| 574 | </div> |
| 575 | </UpdaterProvider> |
| 576 | </RemoteNavigationContext.Provider> |
| 577 | </ShellExpandProvider> |
| 578 | ); |
| 579 | } |
| 580 |