Files
openchamber/packages/ui/src/sync/worktree-topology-cache.ts
T
Bohdan Triapitsyn 85400459e9 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
2026-07-21 20:52:20 +03:00

100 lines
3.3 KiB
TypeScript

import type { WorktreeMetadata } from "@/types/worktree"
import { getDeferredSafeStorage } from "@/stores/utils/safeStorage"
const STORAGE_KEY = "oc.worktreeMap.v2"
const LEGACY_STORAGE_KEY = "oc.worktreeMap"
const MAX_RUNTIME_TOPOLOGIES = 8
type PersistedTopology = {
updatedAt: number
entries: Array<[string, WorktreeMetadata[]]>
}
type PersistedTopologyEnvelope = {
version: 2
legacyClaimed: boolean
runtimes: Record<string, PersistedTopology>
}
const emptyEnvelope = (): PersistedTopologyEnvelope => ({ version: 2, legacyClaimed: false, runtimes: {} })
const parseEntries = (value: unknown): Array<[string, WorktreeMetadata[]]> => {
if (!Array.isArray(value)) return []
return value.filter((entry): entry is [string, WorktreeMetadata[]] => (
Array.isArray(entry)
&& typeof entry[0] === "string"
&& Array.isArray(entry[1])
))
}
const readEnvelope = (storage: Storage): PersistedTopologyEnvelope => {
try {
const raw = storage.getItem(STORAGE_KEY)
if (!raw) return emptyEnvelope()
const parsed = JSON.parse(raw) as Partial<PersistedTopologyEnvelope>
if (parsed.version !== 2 || !parsed.runtimes || typeof parsed.runtimes !== "object") return emptyEnvelope()
const runtimes: Record<string, PersistedTopology> = {}
for (const [runtimeKey, topology] of Object.entries(parsed.runtimes)) {
const entries = parseEntries(topology?.entries)
if (!runtimeKey || entries.length === 0) continue
runtimes[runtimeKey] = {
updatedAt: typeof topology.updatedAt === "number" ? topology.updatedAt : 0,
entries,
}
}
return { version: 2, legacyClaimed: parsed.legacyClaimed === true, runtimes }
} catch {
return emptyEnvelope()
}
}
const writeEnvelope = (storage: Storage, envelope: PersistedTopologyEnvelope): void => {
const retained = Object.entries(envelope.runtimes)
.sort(([, left], [, right]) => right.updatedAt - left.updatedAt)
.slice(0, MAX_RUNTIME_TOPOLOGIES)
storage.setItem(STORAGE_KEY, JSON.stringify({ ...envelope, runtimes: Object.fromEntries(retained) }))
}
export function readPersistedWorktreeTopology(
runtimeKey: string,
storage: Storage = getDeferredSafeStorage(),
): Map<string, WorktreeMetadata[]> {
const envelope = readEnvelope(storage)
const topology = envelope.runtimes[runtimeKey]
if (topology) return new Map(topology.entries)
if (envelope.legacyClaimed) return new Map()
try {
const legacyEntries = parseEntries(JSON.parse(storage.getItem(LEGACY_STORAGE_KEY) ?? "[]"))
envelope.legacyClaimed = true
if (legacyEntries.length > 0) {
envelope.runtimes[runtimeKey] = { updatedAt: Date.now(), entries: legacyEntries }
}
writeEnvelope(storage, envelope)
storage.removeItem(LEGACY_STORAGE_KEY)
return new Map(legacyEntries)
} catch {
return new Map()
}
}
export function persistWorktreeTopology(
runtimeKey: string,
topology: Map<string, WorktreeMetadata[]>,
storage: Storage = getDeferredSafeStorage(),
): void {
if (!runtimeKey) return
try {
const envelope = readEnvelope(storage)
envelope.legacyClaimed = true
envelope.runtimes[runtimeKey] = {
updatedAt: Date.now(),
entries: [...topology.entries()],
}
writeEnvelope(storage, envelope)
storage.removeItem(LEGACY_STORAGE_KEY)
} catch {
// Discovery remains authoritative in memory when persistence is unavailable.
}
}