fix(runtime): stabilize session recovery and history lifecycle - #35
Conversation
- preserve authoritative history handoffs and ordered live projections - reconcile fork ownership, profile-scoped receipts, effort and Work context - keep Plan and Goal controls stable across responsive layouts
- preserve live history, process state, and compaction boundaries across reconnects - honor profile-specific model and effort defaults for new, resumed, and forked sessions - add durable long-paste drafts and per-engine surface preferences
- align lagging profile daemons without breaking shared-session affinity - rebuild corrupt optional projections during account topology migration - preserve fork deletion state when native removal remains uncertain
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 294f4c9aa3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5aba31703f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (cloned.kind === "text") { | ||
| cloned.done = true; | ||
| } else if (cloned.kind === "process" && !cloned.done | ||
| && cloned.processKind !== "plan") { |
There was a problem hiding this comment.
web/src/history-merge.ts:L486: 🟠 This unconditional Plan exemption misses the reverse IndexedDB race. A cache written from a neutral-steer segment can have cached.done=true with a running Plan. If authoritative idle History installs first and a delayed same-revision hydrate_cache follows, restoreCachedTurnDetails() reintroduces that running block after History already settled it; no later fence or frame is guaranteed to close it. Carry the current running/idle authority into cache restoration, preserving the Plan only for a running or live-raced page, and add the regression in this order: idle History first, cache second.
There was a problem hiding this comment.
Fixed in b4f9da5: cached detail restoration now receives explicit enclosing-task authority. An authoritative idle History settles a delayed same-revision cached Plan, while only a newer unsettled live frame preserves it. Regressions cover History-first/cache-second and a later lifecycle terminal superseding the live frame.
| <span className="paste-card-icon"><Icon name="read" size={15} /></span> | ||
| <span className="paste-card-body"> | ||
| <span className="paste-card-preview"> | ||
| {paste.text.replace(/\s+/g, " ").trim()} |
There was a problem hiding this comment.
web/src/components/PasteCards.tsx:L45: 🟠 The CSS ellipsis does not bound the work here: every render still scans the full paste with replace(), creates the normalized copy, and mounts the complete text node. With the supported 2 MiB prompt size, opening the editor makes every keystroke repeat this work and also split the full editingText at L73, which can freeze or exhaust memory on a phone. Slice to a small bounded preview before normalization/rendering and avoid allocating a full line array during render; cover the preview helper with a maximum-output regression.
There was a problem hiding this comment.
Fixed in b4f9da5: paste cards render a 180-character preview from a bounded 1 KiB prefix, the editor is uncontrolled, and line metadata uses a debounced single-pass counter instead of allocating a full split array on every render. The 2 MiB browser regression verifies the exact bounded preview and editable payload.
- settle delayed cached plans from authoritative lifecycle state - bind running indicators to the exact live turn across Code and BTW - bound oversized paste previews and editor metadata work
| // row, another account, or a read-only browse projection. | ||
| const activeTurnId = exactActiveTurnId( | ||
| historyView.turns, | ||
| rt.liveOwner?.turnId, |
There was a problem hiding this comment.
🟠 The authoritative State(idle) path leaves liveOwner pointing at the completed task. The wrapper emits the next State(running) before its UserMsg/TurnBinding, so this resolves task A as active while task B is launching: both rows render as working and the new completion guard hides A’s footer. Clear or generation-correlate the owner at the idle boundary, and cover idle A → query B → running State before B binding.
| rt.turns = restoreCachedTurnDetails(rt.turns, action.turns); | ||
| const newerUnsettledLiveFrame = rt.lastLiveSeq | ||
| > Math.max(rt.historyLiveSeq, rt.lastLifecycleSeq); | ||
| const cacheRestoreAuthority = rt.state !== "idle" |
There was a problem hiding this comment.
🟠 This authority is session-wide rather than turn-scoped. A Query does not advance the History revision before it materializes, so task B can start after idle History(A) but before delayed same-revision IndexedDB hydration; rt.state is then running and restoreCachedTurnDetails() revives task A’s stale open Plan as B’s monitor. Preserve an open cached Plan only for the exactly matched active owner, and add the History(A) → query B → cache(A) ordering regression.
Summary
Validation
.venv/bin/python -m pytest— 1932 passed, 2 skippeduvx --from ruff==0.15.13 ruff check cc_remote tests deploynpm --prefix web run buildnpm --prefix web run test:reliabilitynpm --prefix web run test:history-browser— 234 passed, 20 skippednpm --prefix web run lintbash -n deploy/install.sh deploy/install-relay.sh deploy/install-wrapper.sh deploy/setup-vps.shshellcheck -x deploy/install.sh deploy/install-relay.sh deploy/install-wrapper.sh deploy/setup-vps.sh deploy/setup_transaction.shgit diff --check