Skip to content

Commit 75bf83d

Browse files
committed
Reduce restore-time UI load
1 parent fb9c1bf commit 75bf83d

File tree

18 files changed

+343
-58
lines changed

18 files changed

+343
-58
lines changed

src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface ContentCanvasProps {
1818
workspacePath?: string;
1919
/** App mode */
2020
mode?: 'agent' | 'project' | 'git';
21+
/** Whether the containing scene is currently visible */
22+
isSceneActive?: boolean;
2123
/** Interaction callback */
2224
onInteraction?: (itemId: string, userInput: string) => Promise<void>;
2325
/** Before-close callback */
@@ -29,6 +31,7 @@ export interface ContentCanvasProps {
2931
export const ContentCanvas: React.FC<ContentCanvasProps> = ({
3032
workspacePath,
3133
mode = 'agent',
34+
isSceneActive = true,
3235
onInteraction,
3336
disablePopOut = false,
3437
}) => {
@@ -114,6 +117,7 @@ export const ContentCanvas: React.FC<ContentCanvasProps> = ({
114117
<div className="canvas-content-canvas__editor">
115118
<EditorArea
116119
workspacePath={workspacePath}
120+
isSceneActive={isSceneActive}
117121
onOpenMissionControl={handleOpenMissionControl}
118122
onInteraction={onInteraction}
119123
onTabCloseWithDirtyCheck={handleCloseWithDirtyCheck}

src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorArea.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
import './EditorArea.scss';
1212
export interface EditorAreaProps {
1313
workspacePath?: string;
14+
isSceneActive?: boolean;
1415
onOpenMissionControl?: () => void;
1516
onInteraction?: (itemId: string, userInput: string) => Promise<void>;
1617
onTabCloseWithDirtyCheck?: (tabId: string, groupId: EditorGroupId) => Promise<boolean>;
@@ -20,6 +21,7 @@ export interface EditorAreaProps {
2021

2122
export const EditorArea: React.FC<EditorAreaProps> = ({
2223
workspacePath,
24+
isSceneActive = true,
2325
onOpenMissionControl,
2426
onInteraction,
2527
onTabCloseWithDirtyCheck,
@@ -125,6 +127,7 @@ export const EditorArea: React.FC<EditorAreaProps> = ({
125127
groupId={groupId}
126128
group={group}
127129
isActive={activeGroupId === groupId}
130+
isSceneActive={isSceneActive}
128131
draggingTabId={draggingTabId}
129132
draggingFromGroupId={draggingFromGroupId}
130133
splitMode={layout.splitMode}

src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface EditorGroupProps {
2424
groupId: EditorGroupId;
2525
group: EditorGroupState;
2626
isActive: boolean;
27+
isSceneActive?: boolean;
2728
draggingTabId: string | null;
2829
draggingFromGroupId: EditorGroupId | null;
2930
splitMode: SplitMode;
@@ -50,6 +51,7 @@ export const EditorGroup: React.FC<EditorGroupProps> = ({
5051
groupId,
5152
group,
5253
isActive,
54+
isSceneActive = true,
5355
draggingTabId,
5456
draggingFromGroupId,
5557
splitMode,
@@ -174,7 +176,7 @@ export const EditorGroup: React.FC<EditorGroupProps> = ({
174176
>
175177
<FlexiblePanel
176178
content={tab.content as any}
177-
isActive={group.activeTabId === tab.id}
179+
isActive={isSceneActive && group.activeTabId === tab.id}
178180
onContentChange={group.activeTabId === tab.id ? handleContentChange : undefined}
179181
onDirtyStateChange={group.activeTabId === tab.id ? handleDirtyStateChange : undefined}
180182
onFileMissingFromDiskChange={

src/web-ui/src/app/hooks/useWindowControls.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
33
import { useWorkspaceContext } from '../../infrastructure/contexts/WorkspaceContext';
44
import { notificationService } from '@/shared/notification-system';
55
import { createLogger } from '@/shared/utils/logger';
6+
import { sendDebugProbe } from '@/shared/utils/debugProbe';
67
import { useI18n } from '@/infrastructure/i18n';
78

89
const log = createLogger('useWindowControls');
@@ -90,15 +91,54 @@ export const useWindowControls = (options?: { isToolbarMode?: boolean }) => {
9091
}
9192

9293
if (document.visibilityState === 'visible') {
94+
sendDebugProbe(
95+
'useWindowControls.ts:handleVisibilityChange',
96+
'Window became visible',
97+
{
98+
isToolbarMode,
99+
}
100+
);
93101
try {
94102
const appWindow = getCurrentWindow();
95103
// Delay update until window fully restores
96104
setTimeout(async () => {
97-
await updateWindowState(appWindow);
98-
await restoreMacOSOverlayTitlebar(appWindow);
105+
const startedAt = typeof performance !== 'undefined' ? performance.now() : Date.now();
106+
try {
107+
await updateWindowState(appWindow);
108+
await restoreMacOSOverlayTitlebar(appWindow);
109+
sendDebugProbe(
110+
'useWindowControls.ts:handleVisibilityChange',
111+
'Window restore sync completed',
112+
{
113+
durationMs:
114+
Math.round(
115+
((typeof performance !== 'undefined' ? performance.now() : Date.now()) -
116+
startedAt) *
117+
10
118+
) / 10,
119+
isToolbarMode,
120+
}
121+
);
122+
} catch (error) {
123+
sendDebugProbe(
124+
'useWindowControls.ts:handleVisibilityChange',
125+
'Window restore sync failed',
126+
{
127+
error: formatErrorMessage(error),
128+
isToolbarMode,
129+
}
130+
);
131+
}
99132
}, 300);
100133
} catch (error) {
101-
// Ignore errors
134+
sendDebugProbe(
135+
'useWindowControls.ts:handleVisibilityChange',
136+
'Window restore setup failed',
137+
{
138+
error: formatErrorMessage(error),
139+
isToolbarMode,
140+
}
141+
);
102142
}
103143
}
104144
};

src/web-ui/src/app/scenes/SceneViewport.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const SceneViewport: React.FC<SceneViewportProps> = ({ workspacePath, isEntering
8484
) : null
8585
}
8686
>
87-
{renderScene(tab.id, workspacePath, isEntering)}
87+
{renderScene(tab.id, workspacePath, isEntering, isActive)}
8888
</Suspense>
8989
</div>
9090
);
@@ -94,16 +94,21 @@ const SceneViewport: React.FC<SceneViewportProps> = ({ workspacePath, isEntering
9494
);
9595
};
9696

97-
function renderScene(id: SceneTabId, workspacePath?: string, isEntering?: boolean) {
97+
function renderScene(
98+
id: SceneTabId,
99+
workspacePath?: string,
100+
isEntering?: boolean,
101+
isActive: boolean = false
102+
) {
98103
switch (id) {
99104
case 'welcome':
100105
return <WelcomeScene />;
101106
case 'session':
102-
return <SessionScene workspacePath={workspacePath} isEntering={isEntering} />;
107+
return <SessionScene workspacePath={workspacePath} isEntering={isEntering} isActive={isActive} />;
103108
case 'terminal':
104-
return <TerminalScene />;
109+
return <TerminalScene isActive={isActive} />;
105110
case 'git':
106-
return <GitScene workspacePath={workspacePath} />;
111+
return <GitScene workspacePath={workspacePath} isActive={isActive} />;
107112
case 'settings':
108113
return <SettingsScene />;
109114
case 'file-viewer':
@@ -125,7 +130,7 @@ function renderScene(id: SceneTabId, workspacePath?: string, isEntering?: boolea
125130
case 'insights':
126131
return <InsightsScene />;
127132
case 'shell':
128-
return <ShellScene />;
133+
return <ShellScene isActive={isActive} />;
129134
case 'panel-view':
130135
return <PanelViewScene workspacePath={workspacePath} />;
131136
default:

src/web-ui/src/app/scenes/git/GitScene.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ import './GitScene.scss';
1616

1717
interface GitSceneProps {
1818
workspacePath?: string;
19+
isActive?: boolean;
1920
}
2021

21-
const GitScene: React.FC<GitSceneProps> = ({ workspacePath: workspacePathProp }) => {
22+
const GitScene: React.FC<GitSceneProps> = ({
23+
workspacePath: workspacePathProp,
24+
isActive = true,
25+
}) => {
2226
const { workspace } = useCurrentWorkspace();
2327
const workspacePath = workspacePathProp ?? workspace?.rootPath ?? '';
2428
const { t } = useTranslation('panels/git');
@@ -33,11 +37,15 @@ const GitScene: React.FC<GitSceneProps> = ({ workspacePath: workspacePathProp })
3337
refresh,
3438
} = useGitState({
3539
repositoryPath: workspacePath,
36-
isActive: true,
40+
isActive,
3741
refreshOnMount: true,
3842
layers: ['basic', 'status'],
3943
});
4044

45+
if (!isActive) {
46+
return <div className="bitfun-git-scene" aria-hidden="true" />;
47+
}
48+
4149
const repoLoading = statusLoading && !isRepository;
4250
const handleRefresh = useCallback(() => refresh({ force: true, layers: ['basic', 'status'], reason: 'manual' }), [refresh]);
4351

@@ -128,7 +136,7 @@ const GitScene: React.FC<GitSceneProps> = ({ workspacePath: workspacePathProp })
128136
return <GraphView workspacePath={workspacePath} />;
129137
case 'working-copy':
130138
default:
131-
return <WorkingCopyView workspacePath={workspacePath} />;
139+
return <WorkingCopyView workspacePath={workspacePath} isActive={isActive} />;
132140
}
133141
};
134142

src/web-ui/src/app/scenes/git/views/WorkingCopyView.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,13 @@ const FILE_LIST_WIDTH_MAX = 560;
5252

5353
interface WorkingCopyViewProps {
5454
workspacePath?: string;
55+
isActive?: boolean;
5556
}
5657

57-
const WorkingCopyView: React.FC<WorkingCopyViewProps> = ({ workspacePath }) => {
58+
const WorkingCopyView: React.FC<WorkingCopyViewProps> = ({
59+
workspacePath,
60+
isActive = true,
61+
}) => {
5862
const { t } = useTranslation('panels/git');
5963
const notification = useNotification();
6064

@@ -76,7 +80,7 @@ const WorkingCopyView: React.FC<WorkingCopyViewProps> = ({ workspacePath }) => {
7680
refresh,
7781
} = useGitState({
7882
repositoryPath: workspacePath ?? '',
79-
isActive: true,
83+
isActive,
8084
refreshOnMount: true,
8185
layers: ['basic', 'status'],
8286
});

src/web-ui/src/app/scenes/session/AuxPane.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ export interface AuxPaneRef {
2626

2727
interface AuxPaneProps {
2828
workspacePath?: string;
29+
isSceneActive?: boolean;
2930
}
3031

3132
const AuxPane = forwardRef<AuxPaneRef, AuxPaneProps>(
32-
({ workspacePath }, ref) => {
33+
({ workspacePath, isSceneActive = true }, ref) => {
3334
const {
3435
addTab,
3536
switchToTab,
@@ -124,6 +125,7 @@ const AuxPane = forwardRef<AuxPaneRef, AuxPaneProps>(
124125
<ContentCanvas
125126
workspacePath={workspacePath}
126127
mode="agent"
128+
isSceneActive={isSceneActive}
127129
onInteraction={handleInteraction}
128130
onBeforeClose={handleBeforeClose}
129131
/>

src/web-ui/src/app/scenes/session/SessionScene.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ import './SessionScene.scss';
3636
interface SessionSceneProps {
3737
workspacePath?: string;
3838
isEntering?: boolean;
39+
isActive?: boolean;
3940
}
4041

4142
const SessionScene: React.FC<SessionSceneProps> = ({
4243
workspacePath,
4344
isEntering = false,
45+
isActive = true,
4446
}) => {
4547
const { t } = useTranslation('flow-chat');
4648
const { state, updateRightPanelWidth, toggleRightPanel } = useApp();
@@ -296,6 +298,7 @@ const SessionScene: React.FC<SessionSceneProps> = ({
296298
<AuxPane
297299
ref={auxPaneRef}
298300
workspacePath={workspacePath}
301+
isSceneActive={isActive}
299302
/>
300303
</div>
301304
</div>

src/web-ui/src/app/scenes/shell/ShellScene.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import './ShellScene.scss';
33

44
const TerminalScene = lazy(() => import('../terminal/TerminalScene'));
55

6-
const ShellScene: React.FC = () => (
6+
interface ShellSceneProps {
7+
isActive?: boolean;
8+
}
9+
10+
const ShellScene: React.FC<ShellSceneProps> = ({ isActive = true }) => (
711
<div className="bitfun-shell-scene">
812
<Suspense fallback={<div className="bitfun-shell-scene__loading" />}>
9-
<TerminalScene />
13+
<TerminalScene isActive={isActive} />
1014
</Suspense>
1115
</div>
1216
);

0 commit comments

Comments
 (0)