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
100 lines
3.3 KiB
TypeScript
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.
|
|
}
|
|
}
|