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:
committed by
GitHub
parent
485efc7117
commit
85400459e9
@@ -4,15 +4,16 @@ import { ChatErrorBoundary } from '@/components/chat/ChatErrorBoundary';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
|
||||
type ChatViewProps = {
|
||||
active?: boolean;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export const ChatView: React.FC<ChatViewProps> = ({ readOnly = false }) => {
|
||||
export const ChatView: React.FC<ChatViewProps> = ({ active = true, readOnly = false }) => {
|
||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||
|
||||
return (
|
||||
<ChatErrorBoundary sessionId={currentSessionId || undefined}>
|
||||
<ChatContainer readOnly={readOnly} />
|
||||
<ChatContainer active={active} readOnly={readOnly} />
|
||||
</ChatErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||
import { useGitStore, useGitStatus, useIsGitRepo, useGitLoadingStatus } from '@/stores/useGitStore';
|
||||
import { getRuntimeKey } from '@/lib/runtime-switch';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { GitStatus } from '@/lib/api/types';
|
||||
import {
|
||||
@@ -667,6 +668,7 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
|
||||
setIsLoading(true);
|
||||
|
||||
let cancelled = false;
|
||||
const runtimeKey = getRuntimeKey();
|
||||
const contextLines = loadFullFiles ? FULL_CONTEXT_DIFF_LINES : DEFAULT_CONTEXT_DIFF_LINES;
|
||||
const fetchPromise = isImageFile(file.path)
|
||||
? git.getGitFileDiff(directory, { path: file.path, staged })
|
||||
@@ -697,7 +699,7 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
|
||||
if (staged) {
|
||||
setStagedDiffData(nextDiff);
|
||||
} else {
|
||||
setDiff(directory, file.path, nextDiff);
|
||||
setDiff(directory, file.path, nextDiff, runtimeKey);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
@@ -1494,6 +1496,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
||||
}
|
||||
|
||||
setOpeningEditorFilePath(filePath);
|
||||
const runtimeKey = getRuntimeKey();
|
||||
try {
|
||||
let targetLine: number | null = null;
|
||||
|
||||
@@ -1525,7 +1528,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
||||
isBinary: response.isBinary,
|
||||
};
|
||||
if (!activeDiffStaged) {
|
||||
setDiff(effectiveDirectory, filePath, diffForNavigation);
|
||||
setDiff(effectiveDirectory, filePath, diffForNavigation, runtimeKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -567,9 +567,9 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
onOpenChange={(open) => setContextMenuPath(open ? node.path : null)}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-6"
|
||||
onClick={handleMenuButtonClick}
|
||||
>
|
||||
@@ -810,7 +810,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const openPaths = useFilesViewTabsStore((state) => (root ? (state.byRoot[root]?.openPaths ?? EMPTY_PATHS) : EMPTY_PATHS));
|
||||
const selectedPath = useFilesViewTabsStore((state) => (root ? (state.byRoot[root]?.selectedPath ?? null) : null));
|
||||
const expandedPaths = useFilesViewTabsStore((state) => (root ? (state.byRoot[root]?.expandedPaths ?? EMPTY_PATHS) : EMPTY_PATHS));
|
||||
const addOpenPath = useFilesViewTabsStore((state) => state.addOpenPath);
|
||||
const removeOpenPath = useFilesViewTabsStore((state) => state.removeOpenPath);
|
||||
const removeOpenPathsByPrefix = useFilesViewTabsStore((state) => state.removeOpenPathsByPrefix);
|
||||
const removeExpandedPathsByPrefix = useFilesViewTabsStore((state) => state.removeExpandedPathsByPrefix);
|
||||
@@ -922,6 +921,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const diagramEditorRef = React.useRef<React.ComponentRef<typeof DiagramEditor>>(null);
|
||||
const lastLoadedFileStatRef = React.useRef<FileStatSnapshot | null>(null);
|
||||
const activeFileLoadIdRef = React.useRef(0);
|
||||
const loadingFilePathRef = React.useRef<string | null>(null);
|
||||
const [autoSaveStatus, setAutoSaveStatus] = React.useState<'idle' | 'saved'>('idle');
|
||||
const [diagramSaved, setDiagramSaved] = React.useState(false);
|
||||
const [autoSaveEnabled, setAutoSaveEnabled] = React.useState(getInitialAutoSaveEnabled);
|
||||
@@ -1952,7 +1952,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
|
||||
if (root) {
|
||||
setSelectedPath(root, node.path);
|
||||
addOpenPath(root, node.path);
|
||||
void ensurePathVisible(node.path, false);
|
||||
}
|
||||
|
||||
@@ -1966,7 +1965,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
if (isMobile) {
|
||||
setShowMobilePageContent(true);
|
||||
}
|
||||
}, [addOpenPath, ensurePathVisible, isDirty, isMobile, root, setSelectedPath]);
|
||||
}, [ensurePathVisible, isDirty, isMobile, root, setSelectedPath]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!selectedFile?.path) {
|
||||
@@ -1979,16 +1978,23 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
React.useEffect(() => {
|
||||
if (!selectedFile) {
|
||||
activeFileLoadIdRef.current += 1;
|
||||
loadingFilePathRef.current = null;
|
||||
setFileLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadedFilePath === selectedFile.path) {
|
||||
if (loadedFilePath === selectedFile.path || loadingFilePathRef.current === selectedFile.path) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Selection changes are guarded; this effect is also what restores persisted tabs on mount.
|
||||
void loadSelectedFile(selectedFile);
|
||||
const loadingPath = selectedFile.path;
|
||||
loadingFilePathRef.current = loadingPath;
|
||||
void loadSelectedFile(selectedFile).finally(() => {
|
||||
if (loadingFilePathRef.current === loadingPath) {
|
||||
loadingFilePathRef.current = null;
|
||||
}
|
||||
});
|
||||
}, [loadSelectedFile, loadedFilePath, selectedFile]);
|
||||
|
||||
// Sync isDirty to a ref so the polling interval can read the latest value
|
||||
@@ -2192,7 +2198,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const getFileStatus = React.useCallback((path: string): FileStatus | null => {
|
||||
// Check open status
|
||||
if (openPaths.includes(path)) return 'open';
|
||||
|
||||
|
||||
// Check git status
|
||||
if (gitStatus?.files) {
|
||||
const relative = path.startsWith(root + '/') ? path.slice(root.length + 1) : path;
|
||||
@@ -2210,7 +2216,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
if (!gitStatus?.files) return null;
|
||||
const relativeDir = dirPath.startsWith(root + '/') ? dirPath.slice(root.length + 1) : dirPath;
|
||||
const prefix = relativeDir ? `${relativeDir}/` : '';
|
||||
|
||||
|
||||
let modified = 0, added = 0;
|
||||
for (const f of gitStatus.files) {
|
||||
if (f.path.startsWith(prefix)) {
|
||||
|
||||
@@ -615,9 +615,8 @@ export const TerminalView: React.FC<TerminalViewProps> = ({ visible }) => {
|
||||
const handleAttachSelection = React.useCallback(() => {
|
||||
const selection = terminalControllerRef.current?.getSelection();
|
||||
const sessionKey = currentSessionId ?? (newSessionDraft?.open ? 'draft' : null);
|
||||
if (!selection || !sessionKey || !activeTab) return;
|
||||
addContextDraft({
|
||||
sessionKey,
|
||||
if (!selection || !sessionKey || !activeTab || !effectiveDirectory) return;
|
||||
addContextDraft({ directory: effectiveDirectory, sessionKey }, {
|
||||
source: 'terminal',
|
||||
fileLabel: activeTab.label,
|
||||
startLine: selection.startLine,
|
||||
@@ -626,7 +625,7 @@ export const TerminalView: React.FC<TerminalViewProps> = ({ visible }) => {
|
||||
language: activeTab.terminalSessionId ?? activeTab.id,
|
||||
text: '',
|
||||
});
|
||||
}, [activeTab, addContextDraft, currentSessionId, newSessionDraft?.open]);
|
||||
}, [activeTab, addContextDraft, currentSessionId, effectiveDirectory, newSessionDraft?.open]);
|
||||
|
||||
const handleSelectTab = React.useCallback(
|
||||
(tabId: string) => {
|
||||
|
||||
@@ -382,8 +382,8 @@ export const PullRequestSection: React.FC<{
|
||||
const canShow = Boolean(directory && branch && baseBranch && (branch !== baseBranch || isFork));
|
||||
|
||||
const prStatusKey = React.useMemo(
|
||||
() => getGitHubPrStatusKey(directory, branch),
|
||||
[directory, branch],
|
||||
() => getGitHubPrStatusKey(directory, branch, selectedRemote?.name ?? null),
|
||||
[directory, branch, selectedRemote?.name],
|
||||
);
|
||||
const statusEntry = useGitHubPrStatusStore((state) => state.entries[prStatusKey]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user