diff --git a/src/renderer/src/workspace/hook/actions/initialHistory.ts b/src/renderer/src/workspace/hook/actions/initialHistory.ts index c19e84ac9..018a0cfb1 100644 --- a/src/renderer/src/workspace/hook/actions/initialHistory.ts +++ b/src/renderer/src/workspace/hook/actions/initialHistory.ts @@ -115,6 +115,12 @@ export async function loadInitialHistoryForSession({ limit, }) + // [xcript-diag #283] Trace the load lifecycle. The bug: this sets 'loading' + // unconditionally, but the terminal 'ready'/'error' below only land if the + // runtime entry still exists at resolve. If a rebuild/remap drops the key + // mid-load, the session is stuck at 'loading'. These warns make the timeline + // (and the exact drop) visible. REMOVE once root cause is fixed. + console.warn(`[xcript-diag] loading-set session=${sessionId} kind=${kind}`) setRuntimes(prev => { const current = prev[sessionId] ?? emptyRuntime() return { @@ -142,7 +148,16 @@ export async function loadInitialHistoryForSession({ setRuntimes(prev => { const current = prev[sessionId] - if (!current) return prev + if (!current) { + // [xcript-diag #283] SMOKING GUN: the load finished but the runtime + // entry for this id is gone, so the 'ready' write below is discarded + // and the pane stays stuck at 'loading'. Log which keys DO exist so we + // can see what it got remapped/rebuilt to. + console.warn( + `[xcript-diag] RESOLVE-DROPPED session=${sessionId} — runtime missing at resolve; 'ready' discarded (STUCK). liveKeys=[${Object.keys(prev).join(',')}]`, + ) + return prev + } const seen = (refs.seenUuidsRef.current[sessionId] ??= new Set()) seedSeenFromRuntime(current, seen) @@ -274,6 +289,9 @@ export async function loadInitialHistoryForSession({ }, ) + console.warn( + `[xcript-diag] ready-set session=${sessionId} entries=${initialEntries.length}`, + ) return { ...prev, [sessionId]: nextRuntime } }) @@ -287,7 +305,12 @@ export async function loadInitialHistoryForSession({ console.warn('[history] load initial failed', err) setRuntimes(prev => { const current = prev[sessionId] - if (!current) return prev + if (!current) { + console.warn( + `[xcript-diag] ERROR-DROPPED session=${sessionId} — runtime missing at error resolve; 'error' discarded (STUCK at 'loading'). msg=${message}`, + ) + return prev + } return { ...prev, [sessionId]: { diff --git a/src/renderer/src/workspace/hook/persistence/rehydrate.ts b/src/renderer/src/workspace/hook/persistence/rehydrate.ts index c379f1b88..66d7a46e9 100644 --- a/src/renderer/src/workspace/hook/persistence/rehydrate.ts +++ b/src/renderer/src/workspace/hook/persistence/rehydrate.ts @@ -423,6 +423,19 @@ export async function rehydrateWorkspace( : true, } } + // [xcript-diag #283] commitRehydratedState is the ONLY wholesale runtimes + // rebuild (out = {}), so it is the prime suspect for dropping a key whose + // initial-history load is still in flight. Log any key present in `prev` + // but absent from `out` — especially one mid-'loading' — to confirm + // whether this commit is what orphans the loader's 'ready' write. REMOVE + // once root cause is fixed. + for (const [id, runtime] of Object.entries(prev)) { + if (!out[id]) { + console.warn( + `[xcript-diag] commitRehydrated DROPS session=${id} (was transcriptStatus=${runtime.transcriptStatus}); not in idMap/freshSessions`, + ) + } + } return out }) return true