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