Skip to content

Commit f718864

Browse files
committed
frontend/aria/hotkey: compute tree only when dialog is visible
1 parent 24cb036 commit f718864

File tree

5 files changed

+61
-43
lines changed

5 files changed

+61
-43
lines changed

src/.claude/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"mcp__github__get_pull_request_status",
6363
"mcp__github__list_workflow_runs",
6464
"mcp__github__list_workflows"
65+
"mcp__chrome-devtools__*"
6566
],
6667
"deny": []
6768
}

src/packages/frontend/app/hotkey/build-tree.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import {
99
ACCOUNT_MAIN_MENU_ITEMS,
1010
PREFERENCES_SUB_TABS,
1111
} from "@cocalc/frontend/account/account-preferences-config";
12-
import { isIntlMessage, labels } from "@cocalc/frontend/i18n";
13-
import type { IntlMessage } from "@cocalc/frontend/i18n";
14-
import AIAvatar from "@cocalc/frontend/components/ai-avatar";
1512
import { Icon, IconName } from "@cocalc/frontend/components";
13+
import AIAvatar from "@cocalc/frontend/components/ai-avatar";
1614
import { filenameIcon } from "@cocalc/frontend/file-associations";
15+
import type { IntlMessage } from "@cocalc/frontend/i18n";
16+
import { isIntlMessage, labels } from "@cocalc/frontend/i18n";
1717
import type { FixedTab } from "@cocalc/frontend/project/page/file-tab";
1818
import { FIXED_PROJECT_TABS } from "@cocalc/frontend/project/page/file-tab";
1919
import { COLORS } from "@cocalc/util/theme";

src/packages/frontend/app/hotkey/dialog.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import {
1919
} from "@cocalc/frontend/misc/local-storage";
2020
import { RenderFrameTree } from "./render-frame-tree";
2121
import type { AppPageAction, FrameInfo } from "./build-tree";
22+
import {
23+
useActiveFrameData,
24+
useEnhancedNavigationTreeData,
25+
} from "./use-navigation-data";
2226
import { focusFrameWithRetry } from "./util";
2327

2428
// Extended TreeDataNode with navigation data
@@ -53,11 +57,6 @@ export interface NavigationTreeNode extends TreeDataNode {
5357
interface QuickNavigationDialogProps {
5458
visible: boolean;
5559
onClose: () => void;
56-
treeData: NavigationTreeNode[];
57-
frameTreeStructure?: any; // Frame tree visualization structure
58-
activeFrames?: FrameInfo[]; // Current active frames for keyboard shortcuts
59-
activeFileName?: string; // Path to currently open file
60-
activeProjectId?: string; // ID of currently active project
6160
}
6261

6362
/**
@@ -260,13 +259,13 @@ function saveExpandedKeys(
260259
export const QuickNavigationDialog: React.FC<QuickNavigationDialogProps> = ({
261260
visible,
262261
onClose,
263-
treeData,
264-
frameTreeStructure,
265-
activeFrames,
266-
activeFileName,
267-
activeProjectId,
268262
}) => {
269263
const intl = useIntl();
264+
// Compute navigation tree data only when dialog is visible
265+
// Pass skip=!visible to skip computation when dialog is closed
266+
const treeData = useEnhancedNavigationTreeData(!visible);
267+
const { frameTreeStructure, activeFrames, activeFileName, activeProjectId } =
268+
useActiveFrameData(!visible);
270269
const searchInputRef = useRef<any>(null);
271270
const treeContainerRef = useRef<HTMLDivElement>(null);
272271
const [searchValue, setSearchValue] = useState("");

src/packages/frontend/app/hotkey/use-navigation-data.ts

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@ declare var DEBUG: boolean;
88
import { useMemo } from "react";
99
import { useIntl } from "react-intl";
1010

11+
import { switchAccountPage } from "@cocalc/frontend/account/util";
1112
import {
1213
redux,
1314
useActions,
14-
useTypedRedux,
1515
useRedux,
16+
useTypedRedux,
1617
} from "@cocalc/frontend/app-framework";
18+
import type { EditorSpec } from "@cocalc/frontend/frame-editors/frame-tree/types";
19+
import { labels } from "@cocalc/frontend/i18n";
20+
import type { FixedTab } from "@cocalc/frontend/project/page/file-tab";
21+
import { FIXED_PROJECT_TABS } from "@cocalc/frontend/project/page/file-tab";
1722
import { useBookmarkedProjects } from "@cocalc/frontend/projects/use-bookmarked-projects";
1823
import {
24+
getRandomColor,
1925
path_to_tab,
2026
trunc_middle,
2127
unreachable,
22-
getRandomColor,
2328
} from "@cocalc/util/misc";
24-
2529
import {
2630
buildNavigationTree,
2731
PageInfo,
@@ -32,11 +36,6 @@ import {
3236
type ProjectInfo,
3337
} from "./build-tree";
3438
import type { NavigationTreeNode } from "./dialog";
35-
import { switchAccountPage } from "@cocalc/frontend/account/util";
36-
import { labels } from "@cocalc/frontend/i18n";
37-
import type { EditorSpec } from "@cocalc/frontend/frame-editors/frame-tree/types";
38-
import type { FixedTab } from "@cocalc/frontend/project/page/file-tab";
39-
import { FIXED_PROJECT_TABS } from "@cocalc/frontend/project/page/file-tab";
4039
import {
4140
ensureFrameFilePath,
4241
focusFrameWithRetry,
@@ -171,8 +170,12 @@ function extractFramesFromTree(
171170
* - Current project (prioritized)
172171
* - All other projects
173172
* - Account pages (hardcoded, always available)
173+
*
174+
* @param skip - If true, skip computation and return empty array (used to avoid updates when dialog is closed)
174175
*/
175-
export function useNavigationTreeData(): NavigationTreeNode[] {
176+
export function useNavigationTreeData(
177+
skip: boolean = false,
178+
): NavigationTreeNode[] {
176179
const intl = useIntl();
177180
const is_logged_in = useTypedRedux("account", "is_logged_in");
178181
const is_anonymous = useTypedRedux("account", "is_anonymous");
@@ -216,6 +219,10 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
216219
// This would improve navigation for frequently-used files across all projects,
217220
// following accessibility best practices for quick navigation dialogs
218221
const projectsData = useMemo(() => {
222+
// Short-circuit if dialog is closed
223+
if (skip) {
224+
return [];
225+
}
219226
// if (DEBUG) {
220227
// console.log("useNavigationTreeData - Building projectsData:", {
221228
// openProjectsLength: open_projects?.size,
@@ -308,7 +315,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
308315
if (starred_files) {
309316
const allStarred = Array.isArray(starred_files)
310317
? starred_files
311-
: (starred_files.toArray?.() ?? []);
318+
: starred_files.toArray?.() ?? [];
312319
starredFiles = allStarred.filter(
313320
(path) => !openFilePaths.has(path),
314321
);
@@ -321,7 +328,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
321328
if (otherStarredFiles) {
322329
const allStarred = Array.isArray(otherStarredFiles)
323330
? otherStarredFiles
324-
: (otherStarredFiles.toArray?.() ?? []);
331+
: otherStarredFiles.toArray?.() ?? [];
325332
starredFiles = allStarred.filter(
326333
(path) => !openFilePaths.has(path),
327334
);
@@ -339,6 +346,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
339346
})
340347
.filter((p): p is ProjectInfo => p !== null);
341348
}, [
349+
skip,
342350
project_map,
343351
open_projects,
344352
project_id,
@@ -355,7 +363,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
355363

356364
// Build ProjectInfo for bookmarked projects (excluding open projects)
357365
const bookmarkedProjectsData = useMemo(() => {
358-
if (!bookmarksInitialized || !project_map) {
366+
if (skip || !bookmarksInitialized || !project_map) {
359367
return [];
360368
}
361369

@@ -377,9 +385,13 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
377385
} as ProjectInfo;
378386
})
379387
.filter((p): p is ProjectInfo => p !== null);
380-
}, [bookmarkedProjectIds, project_map, bookmarksInitialized]);
388+
}, [skip, bookmarkedProjectIds, project_map, bookmarksInitialized]);
381389

382390
const appPages: AppPageInfo[] = useMemo(() => {
391+
if (skip) {
392+
return [];
393+
}
394+
383395
const pages: AppPageInfo[] = [];
384396

385397
const projectsLabel = intl.formatMessage(labels.projects);
@@ -412,10 +424,14 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
412424
}
413425

414426
return pages;
415-
}, [intl, is_logged_in, is_anonymous]);
427+
}, [skip, intl, is_logged_in, is_anonymous]);
416428

417429
// Build the complete navigation tree
418430
const treeData = useMemo(() => {
431+
if (skip) {
432+
return [];
433+
}
434+
419435
const currentProject =
420436
projectsData.find((p) => p.id === project_id) || null;
421437
const otherProjects = projectsData.filter((p) => p.id !== project_id);
@@ -427,16 +443,18 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
427443
appPages,
428444
intl,
429445
);
430-
}, [projectsData, project_id, bookmarkedProjectsData, appPages, intl]);
446+
}, [skip, projectsData, project_id, bookmarkedProjectsData, appPages, intl]);
431447

432448
return treeData;
433449
}
434450

435451
/**
436452
* Hook that returns just the frame tree structure and active frames
437453
* (without the full tree data)
454+
*
455+
* @param skip - If true, skip computation and return empty values (used to avoid updates when dialog is closed)
438456
*/
439-
export function useActiveFrameData(): {
457+
export function useActiveFrameData(skip: boolean = false): {
440458
frameTreeStructure: FrameTreeStructure | null;
441459
activeFrames: FrameInfo[];
442460
activeFileName?: string;
@@ -514,7 +532,8 @@ export function useActiveFrameData(): {
514532
const activeFrameTree = useRedux(frameTreePath);
515533

516534
const { activeFrames, frameTreeStructure } = useMemo(() => {
517-
if (!activeEditorContext.activeFileName || !activeFrameTree) {
535+
// Short-circuit if dialog is closed
536+
if (skip || !activeEditorContext.activeFileName || !activeFrameTree) {
518537
return {
519538
activeFrames: [],
520539
frameTreeStructure: null,
@@ -549,6 +568,7 @@ export function useActiveFrameData(): {
549568
frameTreeStructure: result.treeStructure,
550569
};
551570
}, [
571+
skip,
552572
activeFrameTree,
553573
activeEditorContext.activeFileName,
554574
activeEditorContext.editorSpec,
@@ -567,9 +587,13 @@ export function useActiveFrameData(): {
567587
/**
568588
* Hook that adds action handlers to tree nodes
569589
* Ties navigation to Redux actions and routing
590+
*
591+
* @param skip - If true, skip computation and return empty array (used to avoid updates when dialog is closed)
570592
*/
571-
export function useEnhancedNavigationTreeData(): NavigationTreeNode[] {
572-
const treeData = useNavigationTreeData();
593+
export function useEnhancedNavigationTreeData(
594+
skip: boolean = false,
595+
): NavigationTreeNode[] {
596+
const treeData = useNavigationTreeData(skip);
573597
const active_top_tab = useTypedRedux("page", "active_top_tab");
574598

575599
// Get project_id from active_top_tab (same logic as useNavigationTreeData)
@@ -582,6 +606,10 @@ export function useEnhancedNavigationTreeData(): NavigationTreeNode[] {
582606

583607
// Enhance tree nodes with action handlers
584608
const enhancedTreeData = useMemo(() => {
609+
if (skip || treeData.length === 0) {
610+
return [];
611+
}
612+
585613
const enhanceNode = (node: NavigationTreeNode): NavigationTreeNode => {
586614
if (node.navigationData) {
587615
const navData = node.navigationData;
@@ -720,7 +748,7 @@ export function useEnhancedNavigationTreeData(): NavigationTreeNode[] {
720748
};
721749

722750
return treeData.map(enhanceNode);
723-
}, [treeData, project_id, page_actions]);
751+
}, [skip, treeData, project_id, page_actions]);
724752

725753
return enhancedTreeData;
726754
}

src/packages/frontend/app/page.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import { Icon } from "@cocalc/frontend/components/icon";
2929
import {
3030
GlobalHotkeyDetector,
3131
QuickNavigationDialog,
32-
useActiveFrameData,
33-
useEnhancedNavigationTreeData,
3432
} from "@cocalc/frontend/app/hotkey";
3533
import Next from "@cocalc/frontend/components/next";
3634
import { FileUsePage } from "@cocalc/frontend/file-use/page";
@@ -131,9 +129,6 @@ export const Page: React.FC = () => {
131129
const quick_nav_hotkey_delay =
132130
other_settings?.get("quick_nav_hotkey_delay") ?? DEFAULT_HOTKEY_DELAY_MS;
133131
const [quick_nav_visible, setQuickNavVisible] = useState<boolean>(false);
134-
const quick_nav_tree_data = useEnhancedNavigationTreeData();
135-
const { frameTreeStructure, activeFrames, activeFileName, activeProjectId } =
136-
useActiveFrameData();
137132

138133
const accountIsReady = useTypedRedux("account", "is_ready");
139134
const account_id = useTypedRedux("account", "account_id");
@@ -427,11 +422,6 @@ export const Page: React.FC = () => {
427422
<QuickNavigationDialog
428423
visible={quick_nav_visible}
429424
onClose={() => setQuickNavVisible(false)}
430-
treeData={quick_nav_tree_data}
431-
frameTreeStructure={frameTreeStructure}
432-
activeFrames={activeFrames}
433-
activeFileName={activeFileName}
434-
activeProjectId={activeProjectId}
435425
/>
436426
</div>
437427
);

0 commit comments

Comments
 (0)