perf: overhaul session loading, caching, and runtime isolation (#2360)

Improve OpenChamber responsiveness under large session workloads while fixing
cache, synchronization, and persistence correctness across runtimes, projects,
directories, and worktrees.

- prioritize selected and visible sessions during bootstrap and defer
  non-critical enrichment work
- reduce redundant message loading, event processing, store publication, and
  hidden sidebar work
- prevent stale session and message requests from overwriting newer
  authoritative state
- preserve existing data when authoritative fetches fail instead of treating
  failures as successful empty responses
- scope session materialization, messages, drafts, queues, todos, pins,
  permissions, folders, tabs, Git state, and pull request data by runtime and
  directory identity
- harden runtime switching, reconnect, cleanup, mutation reconciliation, and
  persisted-state ordering
- preserve live subagent Task linkage when metadata arrives after an older
  message request or while streaming parts are suspended
- coalesce overlapping tail refreshes without losing newer refresh demand
- improve cold-session loading by moving deferrable work out of the critical
  bootstrap path
- isolate URL authentication, mobile credentials, native secrets, and other
  runtime-owned state across endpoint changes
- bound long-lived caches and remove avoidable allocations from event and
  rendering hot paths
- limit virtualization to archive collections where it improves rendering
  without disrupting active sidebar layout
- stabilize session folders, pin ordering, expanded state, and persisted
  sidebar behavior
- open skill files through the same secure editor and outside-workspace grant
  flow used by file navigation, including worktree sessions
- expand regression coverage for stale completions, runtime collisions,
  reconnect behavior, persistence races, authoritative empty results, and
  subagent refresh ordering
- document the updated synchronization, cache ownership, performance, and
  runtime-isolation invariants
This commit is contained in:
Bohdan Triapitsyn
2026-07-21 20:52:20 +03:00
committed by GitHub
parent 485efc7117
commit 85400459e9
197 changed files with 10835 additions and 3400 deletions
@@ -542,6 +542,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
const [hoverTarget, setHoverTarget] = React.useState<PreviewElementMetadata | null>(null);
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
const newSessionDraftOpen = useSessionUIStore((state) => state.newSessionDraft?.open);
const effectiveDirectory = useEffectiveDirectory();
const addInlineCommentDraft = useInlineCommentDraftStore((state) => state.addDraft);
const addAttachedFile = useInputStore((state) => state.addAttachedFile);
@@ -688,7 +689,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
const attachPreviewAnnotation = React.useCallback((target: PreviewElementMetadata) => {
const sessionKey = currentSessionId ?? (newSessionDraftOpen ? 'draft' : null);
if (!sessionKey) {
if (!sessionKey || !effectiveDirectory) {
toast.error(t('contextPanel.preview.inspect.attachNoSession'));
return;
}
@@ -712,8 +713,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
attachedScreenshot = false;
}
addInlineCommentDraft({
sessionKey,
addInlineCommentDraft({ directory: effectiveDirectory, sessionKey }, {
source: 'preview-annotation',
fileLabel: pageUrl || 'preview',
startLine: 1,
@@ -731,7 +731,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
});
toast.success(t('contextPanel.preview.inspect.attached'));
})();
}, [addAttachedFile, addInlineCommentDraft, currentSessionId, effectiveSrc, newSessionDraftOpen, rawUrl, t]);
}, [addAttachedFile, addInlineCommentDraft, currentSessionId, effectiveDirectory, effectiveSrc, newSessionDraftOpen, rawUrl, t]);
React.useEffect(() => {
setBridgeReady(false);
@@ -921,7 +921,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
const attachConsoleEvents = React.useCallback(() => {
const sessionKey = currentSessionId ?? (newSessionDraftOpen ? 'draft' : null);
if (!sessionKey) {
if (!sessionKey || !effectiveDirectory) {
toast.error(t('contextPanel.preview.console.attachNoSession'));
return;
}
@@ -937,8 +937,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
return `[${timestamp}] [${event.level}] ${event.message}${details}`;
}).join('\n');
addInlineCommentDraft({
sessionKey,
addInlineCommentDraft({ directory: effectiveDirectory, sessionKey }, {
source: 'preview-console',
fileLabel: rawUrl || effectiveSrc || 'preview',
startLine: 1,
@@ -948,7 +947,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
text: t('contextPanel.preview.console.attachAnnotation'),
});
toast.success(t('contextPanel.preview.console.attached'));
}, [addInlineCommentDraft, consoleEvents, currentSessionId, effectiveSrc, newSessionDraftOpen, rawUrl, t]);
}, [addInlineCommentDraft, consoleEvents, currentSessionId, effectiveDirectory, effectiveSrc, newSessionDraftOpen, rawUrl, t]);
// Out-of-band upstream probe: iframes don't expose HTTP status to the parent,
// so when the proxy returns a 502 (upstream dev server is offline) the iframe
@@ -1590,8 +1589,7 @@ const IframeBrowserPane: React.FC<DesktopBrowserPaneProps> = ({ initialUrl, dire
await addAttachedFile(file);
}
addInlineCommentDraft({
sessionKey,
addInlineCommentDraft({ directory, sessionKey }, {
source: 'preview-annotation',
fileLabel: currentUrl || 'browser',
startLine: 1,
@@ -1610,7 +1608,7 @@ const IframeBrowserPane: React.FC<DesktopBrowserPaneProps> = ({ initialUrl, dire
text: '',
});
toast.success(t('contextPanel.preview.inspect.attached'));
}, [addAttachedFile, addInlineCommentDraft, currentSessionId, currentUrl, newSessionDraftOpen, t]);
}, [addAttachedFile, addInlineCommentDraft, currentSessionId, currentUrl, directory, newSessionDraftOpen, t]);
const cancelInspect = React.useCallback(() => {
const iframe = iframeRef.current;
@@ -1998,8 +1996,7 @@ const DesktopBrowserPane: React.FC<DesktopBrowserPaneProps> = ({ initialUrl, dir
await addAttachedFile(file);
}
addInlineCommentDraft({
sessionKey,
addInlineCommentDraft({ directory, sessionKey }, {
source: 'preview-annotation',
fileLabel: currentUrl || 'browser',
startLine: 1,
@@ -2018,7 +2015,7 @@ const DesktopBrowserPane: React.FC<DesktopBrowserPaneProps> = ({ initialUrl, dir
toast.success(t('contextPanel.preview.inspect.attached'));
})
.catch(() => setIsInspecting(false));
}, [addAttachedFile, addInlineCommentDraft, currentSessionId, currentUrl, isInspecting, newSessionDraftOpen, t]);
}, [addAttachedFile, addInlineCommentDraft, currentSessionId, currentUrl, directory, isInspecting, newSessionDraftOpen, t]);
return (
<div className="absolute inset-0 flex flex-col bg-background">
+57 -68
View File
@@ -21,12 +21,12 @@ import { useConfigStore } from '@/stores/useConfigStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSessionWorktreeStore } from '@/sync/session-worktree-store';
import { formatSessionWorktreeBadge } from '@/sync/session-worktree-contract';
import { useAllLiveSessions, useSession, useSessionMessagesResolved } from '@/sync/sync-context';
import { getAllSyncSessions } from '@/sync/sync-refs';
import { useSessionMessagesResolved } from '@/sync/sync-context';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore';
import { useGitBranchLabel } from '@/stores/useGitStore';
import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
import { streamPerfCount } from '@/stores/utils/streamDebug';
import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore';
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
@@ -74,7 +74,7 @@ import { useI18n } from '@/lib/i18n';
import { runtimeFetch } from '@/lib/runtime-fetch';
import { getRuntimeBearerTokenSync } from '@/lib/runtime-auth';
import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
import type { Session } from '@opencode-ai/sdk/v2/client';
import { useShallow } from 'zustand/react/shallow';
import type { IconName } from "@/components/icon/icons";
const DESKTOP_HEADER_ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-8 w-8 items-center justify-center gap-2 rounded-md typography-ui-label font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:bg-interactive-hover transition-colors';
@@ -703,12 +703,20 @@ interface HeaderProps {
rightDrawerOpen?: boolean;
}
type HeaderSessionSnapshot = {
title: string | null;
directory: string | null;
created: number | null;
slug: string | null;
};
export const Header: React.FC<HeaderProps> = ({
onToggleLeftDrawer,
onToggleRightDrawer,
leftDrawerOpen,
rightDrawerOpen,
}) => {
streamPerfCount('ui.header.render');
const { t } = useI18n();
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
@@ -720,7 +728,6 @@ export const Header: React.FC<HeaderProps> = ({
const openContextBrowser = useUIStore((state) => state.openContextBrowser);
const openContextPanelTab = useUIStore((state) => state.openContextPanelTab);
const closeContextPanel = useUIStore((state) => state.closeContextPanel);
const contextPanelByDirectory = useUIStore((state) => state.contextPanelByDirectory);
const activeMainTab = useUIStore((state) => state.activeMainTab);
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
@@ -734,15 +741,28 @@ export const Header: React.FC<HeaderProps> = ({
const isNewSessionDraftOpen = useSessionUIStore((state) => Boolean(state.newSessionDraft?.open));
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
const currentSessionMessagesResolved = useSessionMessagesResolved(currentSessionId ?? '');
const currentSyncedSession = useSession(currentSessionId ?? null);
const globalActiveSessions = useGlobalSessionsStore((state) => state.activeSessions);
const liveSessions = useAllLiveSessions();
const activeProject = useProjectsStore((state) => {
const currentGlobalSession = useGlobalSessionsStore(useShallow(React.useCallback(
(state): HeaderSessionSnapshot | null => {
if (!currentSessionId) return null;
const session = state.activeSessions.find((candidate) => candidate.id === currentSessionId);
if (!session) return null;
const record = session as typeof session & { directory?: string | null; slug?: string | null };
return {
title: session.title ?? null,
directory: record.directory ?? null,
created: session.time?.created ?? null,
slug: record.slug ?? null,
};
},
[currentSessionId],
)));
const activeProject = useProjectsStore(useShallow((state) => {
if (!state.activeProjectId) {
return null;
}
return state.projects.find((project) => project.id === state.activeProjectId) ?? null;
});
const project = state.projects.find((candidate) => candidate.id === state.activeProjectId);
return project ? { id: project.id, path: project.path, label: project.label } : null;
}));
const activeProjectLabel = React.useMemo(() => {
if (!activeProject) {
return null;
@@ -1130,18 +1150,13 @@ export const Header: React.FC<HeaderProps> = ({
});
}, [fetchAllQuotas, isUsageRefreshSpinning]);
const currentSessionLive = React.useMemo(() => {
if (!currentSessionId) return null;
return liveSessions.find((s) => s.id === currentSessionId)
?? globalActiveSessions.find((s) => s.id === currentSessionId)
?? currentSyncedSession
?? getAllSyncSessions().find((s) => s.id === currentSessionId)
?? null;
}, [currentSessionId, currentSyncedSession, globalActiveSessions, liveSessions]);
const currentSessionSnapshot = currentSessionId
? currentGlobalSession ?? null
: null;
const lastResolvedSessionRef = React.useRef<{
sessionId: string;
session: Session;
session: HeaderSessionSnapshot;
expiresAt: number;
} | null>(null);
const [sessionFallbackVersion, setSessionFallbackVersion] = React.useState(0);
@@ -1155,10 +1170,10 @@ export const Header: React.FC<HeaderProps> = ({
return;
}
if (currentSessionLive) {
if (currentSessionSnapshot) {
lastResolvedSessionRef.current = {
sessionId: currentSessionId,
session: currentSessionLive,
session: currentSessionSnapshot,
expiresAt: Date.now() + 2000,
};
return;
@@ -1186,12 +1201,12 @@ export const Header: React.FC<HeaderProps> = ({
return () => {
window.clearTimeout(timeoutId);
};
}, [currentSessionId, currentSessionLive]);
}, [currentSessionId, currentSessionSnapshot]);
void sessionFallbackVersion;
const currentSession = (() => {
if (currentSessionLive) {
return currentSessionLive;
if (currentSessionSnapshot) {
return currentSessionSnapshot;
}
if (!currentSessionId) {
@@ -1256,6 +1271,10 @@ export const Header: React.FC<HeaderProps> = ({
const openDirectory = React.useMemo(() => {
return worktreeDirectory || sessionDirectory || draftDirectory;
}, [draftDirectory, sessionDirectory, worktreeDirectory]);
const activeContextMode = useUIStore(React.useCallback((state) => {
const directory = normalize(openDirectory || '');
return directory ? getActiveContextMode(state.contextPanelByDirectory[directory]) : null;
}, [openDirectory]));
const catalogWorktreeBranch = useSessionUIStore((state) => {
const candidateDirectory = normalize(worktreeDirectory || sessionDirectory || '');
@@ -1336,7 +1355,7 @@ export const Header: React.FC<HeaderProps> = ({
if (!currentSessionId) return;
const sessionKey = `${currentSessionId || 'none'}:${sessionDirectory || 'none'}:${currentSession?.time?.created || 0}:${currentSession?.slug || 'none'}`;
const sessionKey = `${currentSessionId || 'none'}:${sessionDirectory || 'none'}:${currentSession?.created || 0}:${currentSession?.slug || 'none'}`;
if (lastPlanSessionKeyRef.current !== sessionKey) {
lastPlanSessionKeyRef.current = sessionKey;
}
@@ -1349,7 +1368,7 @@ export const Header: React.FC<HeaderProps> = ({
planModeEnabled,
planTabAvailable,
currentSession?.slug,
currentSession?.time?.created,
currentSession?.created,
currentSessionId,
sessionDirectory,
]);
@@ -1449,23 +1468,16 @@ export const Header: React.FC<HeaderProps> = ({
return;
}
const panelState = contextPanelByDirectory[directory];
const panelState = useUIStore.getState().contextPanelByDirectory[directory];
if (getActiveContextMode(panelState) === 'context') {
closeContextPanel(directory);
return;
}
openContextOverview(directory);
}, [closeContextPanel, contextPanelByDirectory, openContextOverview, openDirectory]);
}, [closeContextPanel, openContextOverview, openDirectory]);
const isContextPanelActive = React.useMemo(() => {
const directory = normalize(openDirectory || '');
if (!directory) {
return false;
}
const panelState = contextPanelByDirectory[directory];
return getActiveContextMode(panelState) === 'context';
}, [contextPanelByDirectory, openDirectory]);
const isContextPanelActive = activeContextMode === 'context';
const handleOpenContextPlan = React.useCallback(() => {
const directory = normalize(openDirectory || '');
@@ -1473,14 +1485,14 @@ export const Header: React.FC<HeaderProps> = ({
return;
}
const panelState = contextPanelByDirectory[directory];
const panelState = useUIStore.getState().contextPanelByDirectory[directory];
if (getActiveContextMode(panelState) === 'plan') {
closeContextPanel(directory);
return;
}
openContextPlan(directory);
}, [closeContextPanel, contextPanelByDirectory, openContextPlan, openDirectory]);
}, [closeContextPanel, openContextPlan, openDirectory]);
const handleOpenContextChanges = React.useCallback(() => {
const directory = normalize(openDirectory || '');
@@ -1488,14 +1500,14 @@ export const Header: React.FC<HeaderProps> = ({
return;
}
const panelState = contextPanelByDirectory[directory];
const panelState = useUIStore.getState().contextPanelByDirectory[directory];
if (getActiveContextMode(panelState) === 'diff') {
closeContextPanel(directory);
return;
}
openContextPanelTab(directory, { mode: 'diff', stagedDiff: false });
}, [closeContextPanel, contextPanelByDirectory, openContextPanelTab, openDirectory]);
}, [closeContextPanel, openContextPanelTab, openDirectory]);
const handleOpenContextBrowser = React.useCallback(() => {
const directory = normalize(openDirectory || '');
@@ -1503,41 +1515,18 @@ export const Header: React.FC<HeaderProps> = ({
return;
}
const panelState = contextPanelByDirectory[directory];
const panelState = useUIStore.getState().contextPanelByDirectory[directory];
if (getActiveContextMode(panelState) === 'browser') {
closeContextPanel(directory);
return;
}
openContextBrowser(directory);
}, [closeContextPanel, contextPanelByDirectory, openContextBrowser, openDirectory]);
}, [closeContextPanel, openContextBrowser, openDirectory]);
const isContextPlanActive = React.useMemo(() => {
const directory = normalize(openDirectory || '');
if (!directory) {
return false;
}
const panelState = contextPanelByDirectory[directory];
return getActiveContextMode(panelState) === 'plan';
}, [contextPanelByDirectory, openDirectory]);
const isContextChangesActive = React.useMemo(() => {
const directory = normalize(openDirectory || '');
if (!directory) {
return false;
}
const panelState = contextPanelByDirectory[directory];
return getActiveContextMode(panelState) === 'diff';
}, [contextPanelByDirectory, openDirectory]);
const isContextBrowserActive = React.useMemo(() => {
const directory = normalize(openDirectory || '');
if (!directory) {
return false;
}
const panelState = contextPanelByDirectory[directory];
return getActiveContextMode(panelState) === 'browser';
}, [contextPanelByDirectory, openDirectory]);
const isContextPlanActive = activeContextMode === 'plan';
const isContextChangesActive = activeContextMode === 'diff';
const isContextBrowserActive = activeContextMode === 'browser';
const desktopHeaderIconButtonClass = DESKTOP_HEADER_ICON_BUTTON_CLASS;
const mobileHeaderIconButtonClass = MOBILE_HEADER_ICON_BUTTON_CLASS;
@@ -72,7 +72,6 @@ export const MainLayout: React.FC = () => {
setMobileLeftDrawerOpen(open);
useUIStore.getState().setSessionSwitcherOpen(open);
}, []);
const mobileRightDrawerOpenRef = React.useRef(false);
const initialDrawerWidthRef = React.useRef(typeof window === 'undefined' ? 0 : window.innerWidth);
// Left drawer motion value
@@ -114,7 +113,6 @@ export const MainLayout: React.FC = () => {
setMobileRightDrawerVisible(false);
return;
}
mobileRightDrawerOpenRef.current = mobileRightSidebarOpen;
if (mobileRightSidebarOpen) {
setMobileRightDrawerVisible(true);
}
@@ -441,7 +439,7 @@ export const MainLayout: React.FC = () => {
>
<main className="w-full h-full overflow-hidden bg-background relative" data-page-scroll-lock="true">
<div className={cn('absolute inset-0', !isChatActive && 'invisible')}>
<ErrorBoundary><ChatView /></ErrorBoundary>
<ErrorBoundary><ChatView active={isChatActive && !isSettingsDialogOpen} /></ErrorBoundary>
</div>
{secondaryView && (
<div className="absolute inset-0">
@@ -480,7 +478,7 @@ export const MainLayout: React.FC = () => {
aria-hidden={!mobileLeftDrawerOpen}
>
<ErrorBoundary>
<SessionSidebar mobileVariant />
<SessionSidebar mobileVariant isVisible={mobileLeftDrawerVisible} />
</ErrorBoundary>
</motion.div>
{mobileRightDrawerVisible && (
@@ -520,7 +518,7 @@ export const MainLayout: React.FC = () => {
className="border-border/50"
topBar={<SidebarTopBar />}
>
<SessionSidebar />
<SessionSidebar isVisible={isSidebarOpen} />
</Sidebar>
<div className="relative flex flex-1 min-w-0 flex-col overflow-hidden bg-background" data-page-scroll-lock="true">
<Header />
@@ -530,7 +528,7 @@ export const MainLayout: React.FC = () => {
<div className="relative flex flex-1 min-h-0 min-w-0 overflow-hidden" data-page-scroll-lock="true">
<main className="flex-1 overflow-hidden bg-background relative" data-page-scroll-lock="true">
<div className={cn('absolute inset-0', !isChatActive && 'invisible')}>
<ErrorBoundary><ChatView /></ErrorBoundary>
<ErrorBoundary><ChatView active={isChatActive && !isSettingsDialogOpen} /></ErrorBoundary>
</div>
{secondaryView && (
<div className="absolute inset-0">
@@ -1,4 +1,5 @@
import React from 'react';
import { getRuntimeKey } from '@/lib/runtime-switch';
import { toast } from '@/components/ui';
import {
@@ -121,20 +122,23 @@ type FileTreeCache = {
};
const FILE_TREE_CACHE_MAX_ROOTS = 8;
const fileTreeCacheByRoot = new Map<string, FileTreeCache>();
const fileTreeCacheKey = (root: string): string => JSON.stringify([getRuntimeKey(), root]);
const touchCache = (root: string): FileTreeCache | null => {
const entry = fileTreeCacheByRoot.get(root);
const key = fileTreeCacheKey(root);
const entry = fileTreeCacheByRoot.get(key);
if (!entry) return null;
entry.touchedAt = Date.now();
// Touch on read promotes the key to the end of the Map's iteration order,
// so the oldest (front) entry is the next eviction candidate.
fileTreeCacheByRoot.delete(root);
fileTreeCacheByRoot.set(root, entry);
fileTreeCacheByRoot.delete(key);
fileTreeCacheByRoot.set(key, entry);
return entry;
};
const getOrCreateCache = (root: string): FileTreeCache => {
const existing = fileTreeCacheByRoot.get(root);
const key = fileTreeCacheKey(root);
const existing = fileTreeCacheByRoot.get(key);
if (existing) {
existing.touchedAt = Date.now();
return existing;
@@ -151,12 +155,12 @@ const getOrCreateCache = (root: string): FileTreeCache => {
loadedDirs: new Set(),
touchedAt: Date.now(),
};
fileTreeCacheByRoot.set(root, created);
fileTreeCacheByRoot.set(key, created);
return created;
};
const dropCacheForRoot = (root: string): void => {
fileTreeCacheByRoot.delete(root);
fileTreeCacheByRoot.delete(fileTreeCacheKey(root));
};
const getFileIcon = (filePath: string, extension?: string): React.ReactNode => {
@@ -251,6 +251,10 @@ export const VSCodeLayout: React.FC = () => {
setCurrentView('sessions');
}, []);
const handleSessionSelected = React.useCallback(() => {
setCurrentView('chat');
}, []);
const isSessionInActiveWorkspace = React.useCallback((session: Session): boolean => {
if (!activeWorkspacePath) {
return false;
@@ -590,7 +594,7 @@ export const VSCodeLayout: React.FC = () => {
/>
<div className="flex-1 overflow-hidden">
<ErrorBoundary>
<ChatView />
<ChatView active={currentView === 'chat'} />
</ErrorBoundary>
</div>
</div>
@@ -609,7 +613,7 @@ export const VSCodeLayout: React.FC = () => {
<SessionSidebar
mobileVariant
allowReselect
onSessionSelected={() => setCurrentView('chat')}
onSessionSelected={handleSessionSelected}
hideDirectoryControls
/>
</div>
@@ -628,7 +632,7 @@ export const VSCodeLayout: React.FC = () => {
/>
<div className="flex-1 overflow-hidden">
<ErrorBoundary>
<ChatView />
<ChatView active={currentView === 'chat'} />
</ErrorBoundary>
</div>
</div>
@@ -8,6 +8,10 @@ const mainLayoutSource = readFileSync(
join(__dirname, '..', 'MainLayout.tsx'),
'utf-8',
);
const sessionSidebarSource = readFileSync(
join(__dirname, '..', '..', 'session', 'SessionSidebar.tsx'),
'utf-8',
);
describe('MainLayout mobile SessionSidebar mount (issue #1695 regression guard)', () => {
test('mobile SessionSidebar is not conditionally mounted on mobileLeftDrawerVisible', () => {
@@ -20,10 +24,11 @@ describe('MainLayout mobile SessionSidebar mount (issue #1695 regression guard)'
expect(/\{\s*mobileLeftDrawerVisible\s*&&\s*\(/.test(precedingWindow)).toBe(false);
expect(precedingWindow.includes('pointer-events-none')).toBe(true);
expect(mainLayoutSource.slice(mobileSidebarIndex, mobileSidebarIndex + 120)).toContain('isVisible={mobileLeftDrawerVisible}');
});
test('desktop SessionSidebar is rendered inside Sidebar without drawer-visibility gating', () => {
const desktopSidebarIndex = mainLayoutSource.indexOf('<SessionSidebar />');
const desktopSidebarIndex = mainLayoutSource.indexOf('<SessionSidebar isVisible={isSidebarOpen} />');
expect(desktopSidebarIndex).toBeGreaterThan(-1);
const windowStart = Math.max(0, desktopSidebarIndex - 300);
@@ -32,4 +37,12 @@ describe('MainLayout mobile SessionSidebar mount (issue #1695 regression guard)'
expect(precedingWindow).toContain('<Sidebar');
expect(/mobileLeftDrawerVisible\s*&&/.test(precedingWindow)).toBe(false);
});
test('hidden sidebars disable render-only subscriptions and effects', () => {
expect(sessionSidebarSource).toContain('useGitAllBranches(isVisible)');
expect(sessionSidebarSource).toContain('useGitRepoStatusMap(isVisible ? normalizedProjectPaths : EMPTY_STRING_ARRAY)');
expect(sessionSidebarSource).toContain('enabled: isVisible,\n isSessionSearchOpen');
expect(sessionSidebarSource).toContain('enabled: isVisible,\n isDesktopShellRuntime');
expect(sessionSidebarSource).toContain('if (!isVisible) return EMPTY_STRING_ARRAY;');
});
});