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
@@ -21,6 +21,7 @@ import { runtimeFetch } from "@/lib/runtime-fetch";
|
||||
import { markStartupTrace, measureStartupTrace } from "@/lib/startupTrace";
|
||||
import { normalizePath } from "@/lib/pathNormalization";
|
||||
import { getSyncConfig, subscribeToSyncConfigChanges } from "@/sync/sync-refs";
|
||||
import { getRuntimeKey } from "@/lib/runtime-switch";
|
||||
|
||||
const MODELS_DEV_API_URL = "https://models.dev/api.json";
|
||||
const MODELS_DEV_PROXY_URL = "/api/openchamber/models-metadata";
|
||||
@@ -717,18 +718,57 @@ const resolveInitialDirectoryKey = (): string => {
|
||||
// We cache resolved mappings to localStorage so subsequent launches resolve the
|
||||
// project synchronously at init time. worktree→project is effectively immutable,
|
||||
// so a cached entry is safe to trust.
|
||||
const WORKTREE_PROJECT_MAP_KEY = 'oc.worktreeProjectMap';
|
||||
let _worktreeProjectMap: Record<string, string> | null = null;
|
||||
const WORKTREE_PROJECT_MAP_KEY = 'oc.worktreeProjectMap.v2';
|
||||
const LEGACY_WORKTREE_PROJECT_MAP_KEY = 'oc.worktreeProjectMap';
|
||||
const MAX_WORKTREE_PROJECT_RUNTIME_MAPS = 8;
|
||||
type WorktreeProjectMapEnvelope = {
|
||||
version: 2;
|
||||
legacyClaimed: boolean;
|
||||
runtimes: Record<string, { updatedAt: number; entries: Record<string, string> }>;
|
||||
};
|
||||
const _worktreeProjectMaps = new Map<string, Record<string, string>>();
|
||||
const readWorktreeProjectEnvelope = (): WorktreeProjectMapEnvelope => {
|
||||
try {
|
||||
const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(WORKTREE_PROJECT_MAP_KEY) : null;
|
||||
if (!raw) return { version: 2, legacyClaimed: false, runtimes: {} };
|
||||
const parsed = JSON.parse(raw) as Partial<WorktreeProjectMapEnvelope>;
|
||||
if (parsed.version !== 2 || !parsed.runtimes || typeof parsed.runtimes !== 'object') {
|
||||
return { version: 2, legacyClaimed: false, runtimes: {} };
|
||||
}
|
||||
return { version: 2, legacyClaimed: parsed.legacyClaimed === true, runtimes: parsed.runtimes };
|
||||
} catch {
|
||||
return { version: 2, legacyClaimed: false, runtimes: {} };
|
||||
}
|
||||
};
|
||||
const writeWorktreeProjectEnvelope = (envelope: WorktreeProjectMapEnvelope): void => {
|
||||
const runtimes = Object.fromEntries(
|
||||
Object.entries(envelope.runtimes)
|
||||
.sort(([, left], [, right]) => right.updatedAt - left.updatedAt)
|
||||
.slice(0, MAX_WORKTREE_PROJECT_RUNTIME_MAPS),
|
||||
);
|
||||
localStorage.setItem(WORKTREE_PROJECT_MAP_KEY, JSON.stringify({ ...envelope, runtimes }));
|
||||
};
|
||||
const getWorktreeProjectMap = (): Record<string, string> => {
|
||||
if (_worktreeProjectMap === null) {
|
||||
const runtimeKey = getRuntimeKey() || 'default';
|
||||
const existing = _worktreeProjectMaps.get(runtimeKey);
|
||||
if (existing) return existing;
|
||||
const envelope = readWorktreeProjectEnvelope();
|
||||
let map = envelope.runtimes[runtimeKey]?.entries ?? null;
|
||||
if (!map && !envelope.legacyClaimed) {
|
||||
try {
|
||||
const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(WORKTREE_PROJECT_MAP_KEY) : null;
|
||||
_worktreeProjectMap = raw ? (JSON.parse(raw) as Record<string, string>) : {};
|
||||
const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(LEGACY_WORKTREE_PROJECT_MAP_KEY) : null;
|
||||
map = raw ? (JSON.parse(raw) as Record<string, string>) : {};
|
||||
envelope.legacyClaimed = true;
|
||||
envelope.runtimes[runtimeKey] = { updatedAt: Date.now(), entries: map };
|
||||
writeWorktreeProjectEnvelope(envelope);
|
||||
localStorage.removeItem(LEGACY_WORKTREE_PROJECT_MAP_KEY);
|
||||
} catch {
|
||||
_worktreeProjectMap = {};
|
||||
map = {};
|
||||
}
|
||||
}
|
||||
return _worktreeProjectMap;
|
||||
const result = map ?? {};
|
||||
_worktreeProjectMaps.set(runtimeKey, result);
|
||||
return result;
|
||||
};
|
||||
const rememberWorktreeProject = (worktree: string, project: string): void => {
|
||||
if (!worktree || !project || worktree === project) return;
|
||||
@@ -736,7 +776,12 @@ const rememberWorktreeProject = (worktree: string, project: string): void => {
|
||||
if (map[worktree] === project) return;
|
||||
map[worktree] = project;
|
||||
try {
|
||||
localStorage.setItem(WORKTREE_PROJECT_MAP_KEY, JSON.stringify(map));
|
||||
const runtimeKey = getRuntimeKey() || 'default';
|
||||
const envelope = readWorktreeProjectEnvelope();
|
||||
envelope.legacyClaimed = true;
|
||||
envelope.runtimes[runtimeKey] = { updatedAt: Date.now(), entries: map };
|
||||
writeWorktreeProjectEnvelope(envelope);
|
||||
localStorage.removeItem(LEGACY_WORKTREE_PROJECT_MAP_KEY);
|
||||
} catch {
|
||||
// localStorage quota exceeded — ignore; live resolution still works.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user