diff --git a/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx b/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx index 036903dd..f2dc4ad8 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx @@ -18,6 +18,8 @@ export interface ContentCanvasProps { workspacePath?: string; /** App mode */ mode?: 'agent' | 'project' | 'git'; + /** Whether the containing scene is currently visible */ + isSceneActive?: boolean; /** Interaction callback */ onInteraction?: (itemId: string, userInput: string) => Promise; /** Before-close callback */ @@ -29,6 +31,7 @@ export interface ContentCanvasProps { export const ContentCanvas: React.FC = ({ workspacePath, mode = 'agent', + isSceneActive = true, onInteraction, disablePopOut = false, }) => { @@ -114,6 +117,7 @@ export const ContentCanvas: React.FC = ({
void; onInteraction?: (itemId: string, userInput: string) => Promise; onTabCloseWithDirtyCheck?: (tabId: string, groupId: EditorGroupId) => Promise; @@ -20,6 +21,7 @@ export interface EditorAreaProps { export const EditorArea: React.FC = ({ workspacePath, + isSceneActive = true, onOpenMissionControl, onInteraction, onTabCloseWithDirtyCheck, @@ -125,6 +127,7 @@ export const EditorArea: React.FC = ({ groupId={groupId} group={group} isActive={activeGroupId === groupId} + isSceneActive={isSceneActive} draggingTabId={draggingTabId} draggingFromGroupId={draggingFromGroupId} splitMode={layout.splitMode} diff --git a/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx b/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx index b789165c..1044395b 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx @@ -24,6 +24,7 @@ export interface EditorGroupProps { groupId: EditorGroupId; group: EditorGroupState; isActive: boolean; + isSceneActive?: boolean; draggingTabId: string | null; draggingFromGroupId: EditorGroupId | null; splitMode: SplitMode; @@ -50,6 +51,7 @@ export const EditorGroup: React.FC = ({ groupId, group, isActive, + isSceneActive = true, draggingTabId, draggingFromGroupId, splitMode, @@ -174,7 +176,7 @@ export const EditorGroup: React.FC = ({ > { } if (document.visibilityState === 'visible') { + sendDebugProbe( + 'useWindowControls.ts:handleVisibilityChange', + 'Window became visible', + { + isToolbarMode, + } + ); try { const appWindow = getCurrentWindow(); // Delay update until window fully restores setTimeout(async () => { - await updateWindowState(appWindow); - await restoreMacOSOverlayTitlebar(appWindow); + const startedAt = typeof performance !== 'undefined' ? performance.now() : Date.now(); + try { + await updateWindowState(appWindow); + await restoreMacOSOverlayTitlebar(appWindow); + sendDebugProbe( + 'useWindowControls.ts:handleVisibilityChange', + 'Window restore sync completed', + { + durationMs: + Math.round( + ((typeof performance !== 'undefined' ? performance.now() : Date.now()) - + startedAt) * + 10 + ) / 10, + isToolbarMode, + } + ); + } catch (error) { + sendDebugProbe( + 'useWindowControls.ts:handleVisibilityChange', + 'Window restore sync failed', + { + error: formatErrorMessage(error), + isToolbarMode, + } + ); + } }, 300); - } catch (_error) { - // Ignore errors + } catch (error) { + sendDebugProbe( + 'useWindowControls.ts:handleVisibilityChange', + 'Window restore setup failed', + { + error: formatErrorMessage(error), + isToolbarMode, + } + ); } } }; diff --git a/src/web-ui/src/app/scenes/SceneViewport.tsx b/src/web-ui/src/app/scenes/SceneViewport.tsx index cc2e9e89..4a42ae82 100644 --- a/src/web-ui/src/app/scenes/SceneViewport.tsx +++ b/src/web-ui/src/app/scenes/SceneViewport.tsx @@ -84,7 +84,7 @@ const SceneViewport: React.FC = ({ workspacePath, isEntering ) : null } > - {renderScene(tab.id, workspacePath, isEntering)} + {renderScene(tab.id, workspacePath, isEntering, isActive)}
); @@ -94,16 +94,21 @@ const SceneViewport: React.FC = ({ workspacePath, isEntering ); }; -function renderScene(id: SceneTabId, workspacePath?: string, isEntering?: boolean) { +function renderScene( + id: SceneTabId, + workspacePath?: string, + isEntering?: boolean, + isActive: boolean = false +) { switch (id) { case 'welcome': return ; case 'session': - return ; + return ; case 'terminal': - return ; + return ; case 'git': - return ; + return ; case 'settings': return ; case 'file-viewer': @@ -125,7 +130,7 @@ function renderScene(id: SceneTabId, workspacePath?: string, isEntering?: boolea case 'insights': return ; case 'shell': - return ; + return ; case 'panel-view': return ; default: diff --git a/src/web-ui/src/app/scenes/git/GitScene.tsx b/src/web-ui/src/app/scenes/git/GitScene.tsx index e17685be..fa6cf439 100644 --- a/src/web-ui/src/app/scenes/git/GitScene.tsx +++ b/src/web-ui/src/app/scenes/git/GitScene.tsx @@ -16,9 +16,13 @@ import './GitScene.scss'; interface GitSceneProps { workspacePath?: string; + isActive?: boolean; } -const GitScene: React.FC = ({ workspacePath: workspacePathProp }) => { +const GitScene: React.FC = ({ + workspacePath: workspacePathProp, + isActive = true, +}) => { const { workspace } = useCurrentWorkspace(); const workspacePath = workspacePathProp ?? workspace?.rootPath ?? ''; const { t } = useTranslation('panels/git'); @@ -33,13 +37,16 @@ const GitScene: React.FC = ({ workspacePath: workspacePathProp }) refresh, } = useGitState({ repositoryPath: workspacePath, - isActive: true, + isActive, refreshOnMount: true, layers: ['basic', 'status'], }); const repoLoading = statusLoading && !isRepository; - const handleRefresh = useCallback(() => refresh({ force: true, layers: ['basic', 'status'], reason: 'manual' }), [refresh]); + const handleRefresh = useCallback( + () => refresh({ force: true, layers: ['basic', 'status'], reason: 'manual' }), + [refresh] + ); useEffect(() => { if (repoLoading || statusLoading) { @@ -65,6 +72,22 @@ const GitScene: React.FC = ({ workspacePath: workspacePathProp }) globalEventBus.emit('fill-chat-input', { content: t('init.chatPrompt') }); }, [t]); + const renderView = useCallback(() => { + switch (activeView) { + case 'branches': + return ; + case 'graph': + return ; + case 'working-copy': + default: + return ; + } + }, [activeView, isActive, workspacePath]); + + if (!isActive) { + return diff --git a/src/web-ui/src/app/scenes/shell/ShellScene.tsx b/src/web-ui/src/app/scenes/shell/ShellScene.tsx index 9bd8b8f5..a6e898dc 100644 --- a/src/web-ui/src/app/scenes/shell/ShellScene.tsx +++ b/src/web-ui/src/app/scenes/shell/ShellScene.tsx @@ -3,10 +3,14 @@ import './ShellScene.scss'; const TerminalScene = lazy(() => import('../terminal/TerminalScene')); -const ShellScene: React.FC = () => ( +interface ShellSceneProps { + isActive?: boolean; +} + +const ShellScene: React.FC = ({ isActive = true }) => (
}> - +
); diff --git a/src/web-ui/src/app/scenes/terminal/TerminalScene.tsx b/src/web-ui/src/app/scenes/terminal/TerminalScene.tsx index 59506d82..87deb411 100644 --- a/src/web-ui/src/app/scenes/terminal/TerminalScene.tsx +++ b/src/web-ui/src/app/scenes/terminal/TerminalScene.tsx @@ -13,7 +13,11 @@ import { useTerminalSceneStore } from '../../stores/terminalSceneStore'; import ConnectedTerminal from '../../../tools/terminal/components/ConnectedTerminal'; import './TerminalScene.scss'; -const TerminalScene: React.FC = () => { +interface TerminalSceneProps { + isActive?: boolean; +} + +const TerminalScene: React.FC = ({ isActive = true }) => { const { activeSessionId, setActiveSession } = useTerminalSceneStore(); const { t } = useTranslation('panels/terminal'); @@ -25,6 +29,10 @@ const TerminalScene: React.FC = () => { setActiveSession(null); }, [setActiveSession]); + if (!isActive) { + return