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
@@ -36,16 +36,15 @@ import { useStreamingStore } from '@/sync/streaming';
import {
useSessionMessageCount,
useSessionMessageRecords,
useSessionMessageLoadState,
useSyncDirectory,
useDirectorySync,
useSessionRenderable,
useSessionStatus,
useScopedBlockingPermissions,
useScopedBlockingQuestions,
useParentSession,
} from '@/sync/sync-context';
import { useSync } from '@/sync/use-sync';
import { getSessionPrefetch, subscribeSessionPrefetch } from '@/sync/session-prefetch-cache';
import { getSessionMaterializationStatus } from '@/sync/materialization';
import { usePlanDetection } from '@/hooks/usePlanDetection';
import { useI18n } from '@/lib/i18n';
import { isMobileSurfaceRuntime } from '@/lib/runtimeSurface';
@@ -215,6 +214,11 @@ const ChatViewport = React.memo(({
// Shell-mode prompts show their extracted command; cache by message id so
// the parts array reference is stable while the command is unchanged.
const shellPreviewCache = React.useRef(new Map<string, { command: string; parts: Part[] }>());
const shellPreviewSessionRef = React.useRef(currentSessionId);
if (shellPreviewSessionRef.current !== currentSessionId) {
shellPreviewSessionRef.current = currentSessionId;
shellPreviewCache.current.clear();
}
const promptPreviewsByTurnId = React.useMemo(() => {
const next = new Map<string, Part[]>();
for (let index = 0; index < renderedMessages.length; index += 1) {
@@ -339,6 +343,7 @@ const ChatViewport = React.memo(({
</div>
)}
<MessageList
key={currentSessionId}
ref={messageListRef}
sessionKey={currentSessionId}
disableStaging={pendingRevealWork}
@@ -487,12 +492,42 @@ const renderDraftTitle = (title: string, projectLabel: string | null): React.Rea
);
};
const DraftWelcome: React.FC = () => {
const { t } = useI18n();
const selectedProjectId = useSessionUIStore((state) => state.newSessionDraft.selectedProjectId ?? null);
const projectLabel = useProjectsStore(React.useCallback((state) => {
const projectId = selectedProjectId ?? state.activeProjectId;
const project = (projectId
? state.projects.find((candidate) => candidate.id === projectId)
: null) ?? state.projects[0] ?? null;
return project ? getProjectDisplayLabel(project) : null;
}, [selectedProjectId]));
return (
<div className="oc-draft-center flex min-h-0 flex-1 flex-col items-center justify-center px-6 text-center">
<h1 className="text-balance text-3xl font-normal tracking-tight text-foreground">
{renderDraftTitle(
projectLabel
? t('chat.emptyState.draftTitleWithProject', { project: projectLabel })
: t('chat.emptyState.draftTitle'),
projectLabel,
)}
</h1>
<DraftPresetChips
onSubmit={(text) => useInputStore.getState().requestPresetSubmit(text)}
className="oc-draft-starters mt-8 max-w-md"
/>
</div>
);
};
type ChatContainerProps = {
active?: boolean;
autoOpenDraft?: boolean;
readOnly?: boolean;
};
export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = true, readOnly = false }) => {
export const ChatContainer: React.FC<ChatContainerProps> = ({ active = true, autoOpenDraft = true, readOnly = false }) => {
const { t } = useI18n();
// Session UI state
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
@@ -500,16 +535,14 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft);
const setCurrentSession = useSessionUIStore((s) => s.setCurrentSession);
const newSessionDraft = useSessionUIStore((s) => s.newSessionDraft);
const projects = useProjectsStore((s) => s.projects);
const activeProjectId = useProjectsStore((s) => s.activeProjectId);
// Sync actions
const sync = useSync();
const syncDirectory = useSyncDirectory();
const effectiveSessionDirectory = currentSessionDirectory ?? syncDirectory;
const ensureSessionRenderable = React.useCallback(
(sessionId: string) => sync.ensureSessionRenderable(sessionId),
[sync],
(sessionId: string) => sync.ensureSessionRenderable(sessionId, false, effectiveSessionDirectory),
[effectiveSessionDirectory, sync],
);
const loadMoreMessages = React.useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -542,31 +575,17 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
),
);
const sessionMessageCount = useSessionMessageCount(currentSessionId ?? '', effectiveSessionDirectory);
const hasRenderableSessionSnapshot = useDirectorySync(
React.useCallback(
(state) => (currentSessionId ? getSessionMaterializationStatus(state, currentSessionId).renderable : false),
[currentSessionId],
),
effectiveSessionDirectory,
);
const hasRenderableSessionSnapshot = useSessionRenderable(currentSessionId ?? '', effectiveSessionDirectory);
// Messages from sync system
const sessionMessageRecords = useSessionMessageRecords(currentSessionId ?? '', effectiveSessionDirectory, {
enabled: active,
suspendPartUpdates: Boolean(streamingMessageId),
suspendPartUpdatesForMessageId: streamingMessageId,
});
const sessionMessages = currentSessionId ? sessionMessageRecords : EMPTY_MESSAGES;
const sessionPrefetchInfo = React.useSyncExternalStore(
React.useCallback(
(notify) => currentSessionId
? subscribeSessionPrefetch(effectiveSessionDirectory, currentSessionId, notify)
: () => undefined,
[currentSessionId, effectiveSessionDirectory],
),
React.useCallback(
() => currentSessionId ? getSessionPrefetch(effectiveSessionDirectory, currentSessionId) : undefined,
[currentSessionId, effectiveSessionDirectory],
),
React.useCallback(() => undefined, []),
const sessionMessageLoadState = useSessionMessageLoadState(
currentSessionId ?? '',
effectiveSessionDirectory,
);
// Plan detection - watches messages for plan creation and signals store
@@ -643,20 +662,12 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
// History metadata — use sync's hasMore/isLoading
const historyMeta = React.useMemo(() => {
if (!currentSessionId) return null;
// Sync's meta is authoritative once a fetch has confirmed the history
// is fully loaded — a stale prefetch-cache entry (cursor recorded at
// the initial page) must not keep the "load older" affordance alive
// after the user has already reached the top.
const syncComplete = sync.isComplete(currentSessionId);
const prefetchHasMore = !syncComplete
&& Boolean(sessionPrefetchInfo?.cursor)
&& sessionPrefetchInfo?.complete !== true;
return {
limit: sessionMessages.length,
complete: syncComplete || !(sync.hasMore(currentSessionId) || prefetchHasMore),
loading: sync.isLoading(currentSessionId),
complete: sessionMessageLoadState.complete || !sessionMessageLoadState.cursor,
loading: sessionMessageLoadState.status === 'loading',
};
}, [currentSessionId, sessionMessages.length, sessionPrefetchInfo, sync]);
}, [currentSessionId, sessionMessageLoadState.complete, sessionMessageLoadState.cursor, sessionMessageLoadState.status, sessionMessages.length]);
const { isMobile } = useDeviceInfo();
const isVSCode = isVSCodeRuntime();
@@ -668,17 +679,6 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
const isDesktopExpandedInput = isExpandedInput;
const useCompactDraftLayout = isMobile || isVSCode || chatSurfaceMode === 'mini-chat';
const messageListRef = React.useRef<MessageListHandle | null>(null);
const draftProjectLabel = React.useMemo(() => {
const selectedProject = newSessionDraft?.selectedProjectId
? projects.find((project) => project.id === newSessionDraft.selectedProjectId) ?? null
: null;
const activeProject = activeProjectId
? projects.find((project) => project.id === activeProjectId) ?? null
: null;
const project = selectedProject ?? activeProject ?? projects[0] ?? null;
return project ? getProjectDisplayLabel(project) : null;
}, [activeProjectId, newSessionDraft?.selectedProjectId, projects]);
const parentSession = useParentSession(currentSessionId, effectiveSessionDirectory);
// In the embedded session-chat iframe, hide "Return to parent" when
@@ -942,9 +942,13 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
const isSessionHydrating =
Boolean(currentSessionId)
&& !hasRenderableSessionSnapshot;
const retrySessionLoad = React.useCallback(() => {
if (!active || !currentSessionId) return;
void sync.ensureSessionRenderable(currentSessionId, true, effectiveSessionDirectory);
}, [active, currentSessionId, effectiveSessionDirectory, sync]);
React.useEffect(() => {
if (!currentSessionId) return;
if (!active || !currentSessionId) return;
if (lastScrolledSessionRef.current === currentSessionId) return;
const hasHashTarget = typeof window !== 'undefined' && window.location.hash.length > 0;
@@ -963,14 +967,13 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
} else {
window.requestAnimationFrame(run);
}
}, [currentSessionId, releaseAutoFollow, restoreSnapshot]);
}, [active, currentSessionId, releaseAutoFollow, restoreSnapshot]);
React.useEffect(() => {
if (!currentSessionId) return;
if (!active || !currentSessionId) return;
if (hasRenderableSessionSnapshot) return;
if (effectiveSessionDirectory !== syncDirectory) return;
void ensureSessionRenderable(currentSessionId);
}, [currentSessionId, effectiveSessionDirectory, ensureSessionRenderable, hasRenderableSessionSnapshot, syncDirectory]);
}, [active, currentSessionId, ensureSessionRenderable, hasRenderableSessionSnapshot]);
if (!currentSessionId && !draftOpen) {
// With auto-open, the draft welcome opens on the next tick (effect below),
@@ -993,22 +996,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
// the fullscreen composer's position:fixed visual-viewport pinning in
// mobile browsers (see ChatInput's composerFormRef effect).
<div className="relative flex h-full flex-col bg-background">
{useCompactDraftLayout && !isDesktopExpandedInput ? (
<div className="oc-draft-center flex min-h-0 flex-1 flex-col items-center justify-center px-6 text-center">
<h1 className="text-balance text-3xl font-normal tracking-tight text-foreground">
{renderDraftTitle(
draftProjectLabel
? t('chat.emptyState.draftTitleWithProject', { project: draftProjectLabel })
: t('chat.emptyState.draftTitle'),
draftProjectLabel,
)}
</h1>
<DraftPresetChips
onSubmit={(text) => useInputStore.getState().requestPresetSubmit(text)}
className="oc-draft-starters mt-8 max-w-md"
/>
</div>
) : null}
{useCompactDraftLayout && !isDesktopExpandedInput ? <DraftWelcome /> : null}
<div
className={cn(
'relative z-10 flex min-h-0',
@@ -1030,6 +1018,28 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
}
if (isSessionHydrating && sessionMessages.length === 0 && !sessionIsWorking) {
if (sessionMessageLoadState.status === 'error') {
return (
<div className="relative flex h-full flex-col bg-background">
{returnToParentButton}
<div className="flex min-h-0 flex-1 items-center justify-center px-6">
<div className="max-w-sm text-center">
<div className="mx-auto mb-3 flex size-9 items-center justify-center rounded-full bg-[color-mix(in_srgb,var(--status-error)_10%,transparent)] text-[var(--status-error)]">
<Icon name="error-warning" className="size-4" />
</div>
<p className="typography-ui-label font-medium text-foreground">{t('chat.container.sessionLoadError.title')}</p>
<p className="typography-meta mt-1 text-muted-foreground">{t('chat.container.sessionLoadError.description')}</p>
<Button variant="outline" size="sm" className="mt-4" onClick={retrySessionLoad}>
{t('chat.container.sessionLoadError.retry')}
</Button>
</div>
</div>
<div className="relative z-10 bg-background">
{promptReadOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={scrollToBottomOnSend} />}
</div>
</div>
);
}
return (
<div className="relative flex flex-col h-full bg-background">
{returnToParentButton}
@@ -1124,7 +1134,6 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
<div className="relative flex flex-col h-full bg-background">
{returnToParentButton}
<ChatViewport
key={currentSessionId}
currentSessionId={currentSessionId}
isDesktopExpandedInput={isDesktopExpandedInput}
isMobile={isMobile}