PR: #457: feat(agents): replay-on-rehydrate — re-run bound render selection on reload [Story-1800] by @ankitmaheshwari-com
View PR →
Story-1800

Replay-on-rehydrate: re-run the bound render selection on page reload

Story-1794 shipped the render substrate with replay-re-run deferred: the reference persists ({component_id, query_ref, capturedAt, renderCatalogVersion}, no rows, HR-23) but a cold page load showed only an empty placeholder — the ponytail: note in the dock. This change closes that gap. On rehydrate the dock re-runs the bound selection by replaying the run that produced it through the existing rail; the broker (#1799, merged) re-binds the persisted selection with FRESH RLS at replay time, so the redelivered render.data frame repopulates the component — never a stale row (none are stored).

The split (why this is client-only, no functions change)

The render design is reference-only, so the fix is NOT to replay a stored data frame — it is to re-bind the persisted selection with fresh RLS at replay time. That re-bind happens server-side and is the paired functions ticket #1799 (now MERGED to functions main). Because #1799 re-binds the bound selection on replay, the client only needs to trigger the existing run-replay by run id — the broker returns RLS-fresh data for that selection. Re-emitting the selection from the app would duplicate the bound-selection source of truth, so the app does NOT. This app ticket is the page-reload side: kick the existing #356 query reconnect on mount. No new outbound message, no contract change, no functions change in this repo.

What changed

The run id is read back off the reference id

A persisted render item is keyed render-${runId}. The new pure runIdFromRenderItemId (inverse of renderItemId) recovers the run to replay — and because it is the SAME id, the redelivered live item overrides the placeholder in place (mergeLiveRender, unchanged).

One rail method: resume(runId)

AgentRailConnection.resume adopts the run id, shows the loading state (status: "launching", streaming), and sends the EXISTING buildDockChatQuery reconnect — the same path a mid-run socket close already uses. #1799 re-binds the selection with fresh RLS, so run id alone returns fresh data. Idempotent per run.

One rehydrate effect in the dock

On mount, if there is no live render and the thread holds a persisted componentResult reference, the dock calls rail.resume for that run — once. A same-session render (rail already has it) needs no replay and is skipped.

Existing states, reused

In-flight → the dock's existing loading/thinking status. The redelivered frame flows back through reduceFrame → rail.render → the unchanged denied / empty / unknown-id text fallback branches (#1794 AC5). No new component state, no new framework.

The ponytail: notes are gone

Both #1794 deferral notes (the persist-effect block and the toThreadItem componentResult case) now describe the real re-run. The empty placeholder is explicitly only the pre-redelivery state, never a stored stale row.

Reference-only persistence kept

No row data reaches sessionStorage (HR-23) — the #1794 persist path is untouched. Replay re-authorizes server-side, so the rows are always RLS-fresh on reload.

Where it lives

Verify

pnpm lint        # clean (1 pre-existing avatar.tsx warning, unrelated)
pnpm test        # 903 passed
pnpm build       # green
git diff --check # clean
# NOTE: do NOT run pnpm codegen — no contract change; import from src/gen.

Design (accepted): the re-run replays the run by id via the existing dock_chat query reconnect rather than re-emitting the selection. #1799 (merged to functions main) re-binds the persisted render selection with fresh RLS at replay time, so run id alone returns RLS-scoped data on rehydrate; re-emitting the selection would duplicate the bound-selection source of truth.

Non-goals (per the ticket): no functions changes (server re-bind is #1799), no row-data persistence, no new framework — the existing rail + loading/denied/empty/fallback states are reused.

Files changed

7 file(s), +299 / -11 lines

Path Role + -
src/components/agents/agent-dock-mount.render.test.tsxComponent+96-0
src/components/agents/agent-dock-mount.tsxComponent+29-8
src/lib/agents/__tests__/atlas-rail-store.test.tsLibrary+101-1
src/lib/agents/atlas-rail-store.tsLibrary+33-0
src/lib/agents/dock-render-bridge.test.tsLibrary+21-1
src/lib/agents/dock-render-bridge.tsLibrary+15-1
src/lib/agents/use-dock-chat-rail.tsLibrary+4-0