From b6f7606670dee5891b4e5c50c4163ab2fa833853 Mon Sep 17 00:00:00 2001 From: Raing5Days Date: Sat, 7 Nov 2026 23:43:00 +0800 Subject: [PATCH] perf(interface): optimize main-btw alternation, canvas tab switch, and settings traversal --- .../panels/content-canvas/editor-area/EditorGroup.tsx | 7 +++++-- src/web-ui/src/app/scenes/settings/SettingsNav.tsx | 10 +++++++++- .../src/flow_chat/components/btw/BtwSessionPanel.tsx | 10 +++++++++- .../src/flow_chat/components/btw/btwPanelViewState.ts | 1 + 4 files changed, 24 insertions(+), 4 deletions(-) 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 8b9a692052..3d1d58b246 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 @@ -175,17 +175,20 @@ export const EditorGroup: React.FC = ({ } }, [group.activeTabId, group.tabs, isKeepAliveTerminalTab]); + // Fast lookup set for cached tabs + const cachedTabs = cachedTabsRef.current; + // Tabs to render (active + cached). Hidden terminal tabs stay mounted so // reopening a terminal reuses the xterm buffer instead of replaying history. // Child-session tabs retain only their lightweight wrapper; that wrapper // unmounts its transcript while inactive and owns reading state until close. const tabsToRender = useMemo(() => { const result = group.tabs.filter(t => - (!t.isHidden && isCanvasTabVisibleForSession(t, activeSessionId) && (t.content.type === 'btw-session' || t.id === effectiveActiveTabId || cachedTabsRef.current.has(t.id))) || + (!t.isHidden && isCanvasTabVisibleForSession(t, activeSessionId) && (t.content.type === 'btw-session' || t.id === effectiveActiveTabId || cachedTabs.has(t.id))) || (t.isHidden && isKeepAliveTerminalTab(t)) ); return result; - }, [group.tabs, effectiveActiveTabId, activeSessionId, isKeepAliveTerminalTab]); + }, [group.tabs, effectiveActiveTabId, activeSessionId, isKeepAliveTerminalTab, cachedTabs]); const handleContentChange = useCallback((content: PanelContent | null) => { if (content && group.activeTabId) { diff --git a/src/web-ui/src/app/scenes/settings/SettingsNav.tsx b/src/web-ui/src/app/scenes/settings/SettingsNav.tsx index 01c65d44a9..0ec163ea08 100644 --- a/src/web-ui/src/app/scenes/settings/SettingsNav.tsx +++ b/src/web-ui/src/app/scenes/settings/SettingsNav.tsx @@ -61,13 +61,18 @@ function resolvePhrases(i18n: I18nApi, phrases: readonly SettingsSearchPhrase[]) return parts.join(' '); } +const searchIndexCache = new WeakMap(); + function buildSettingsSearchIndex(t: SettingsT, i18n: I18nApi): SettingsSearchRow[] { + const cached = searchIndexCache.get(i18n); + if (cached) return cached; + const categoryLabels = new Map(SETTINGS_CATEGORIES.map((category) => [ category.id, translateString(t, category.labelKey, category.id), ])); - return SETTINGS_PAGE_MANIFESTS.flatMap((page) => { + const rows = SETTINGS_PAGE_MANIFESTS.flatMap((page) => { const categoryLabel = categoryLabels.get(page.categoryId) ?? page.categoryId; const pageLabel = translateString(t, page.labelKey, page.id); const description = translateString(t, page.descriptionKey, ''); @@ -106,6 +111,9 @@ function buildSettingsSearchIndex(t: SettingsT, i18n: I18nApi): SettingsSearchRo }), ]; }); + + searchIndexCache.set(i18n, rows); + return rows; } function highlightFirstMatch(text: string, query: string): React.ReactNode { diff --git a/src/web-ui/src/flow_chat/components/btw/BtwSessionPanel.tsx b/src/web-ui/src/flow_chat/components/btw/BtwSessionPanel.tsx index 0e630a6cf2..25765f7d3b 100644 --- a/src/web-ui/src/flow_chat/components/btw/BtwSessionPanel.tsx +++ b/src/web-ui/src/flow_chat/components/btw/BtwSessionPanel.tsx @@ -229,7 +229,15 @@ const BtwSessionPanelContent: React.FC { + if (!viewState.projectSession) { + viewState.projectSession = createSessionVirtualItemProjector(); + } + return viewState.projectSession; + }, + [viewState], + ); const sessionVirtualItems = useMemo( () => projectSession(childSession ?? null), [childSession, projectSession], diff --git a/src/web-ui/src/flow_chat/components/btw/btwPanelViewState.ts b/src/web-ui/src/flow_chat/components/btw/btwPanelViewState.ts index cd8071db06..e9f39c057f 100644 --- a/src/web-ui/src/flow_chat/components/btw/btwPanelViewState.ts +++ b/src/web-ui/src/flow_chat/components/btw/btwPanelViewState.ts @@ -6,6 +6,7 @@ export interface BtwPanelViewState { expandedToolCapsules?: ReadonlySet; restoredReviewLocation?: string; restoring: boolean; + projectSession?: (session: any) => any; } export function createBtwPanelViewState(): BtwPanelViewState {