feat(web): replay mode + runs drawer (NF-UI-7) #365

Merged
code-lead merged 1 commit from feat/337-nfui7-replay-runs into main 2026-04-24 16:44:56 +00:00
Collaborator

Summary

  • RunsDrawer (apps/web/src/features/flows/RunsDrawer.tsx) — bottom-docked collapsible panel mounted on the /flows parent layout, streams recent flow runs from the mock flowsApi.listRuns surface. Columns: flow id, trigger, status badge (running / ok / error / cancelled), started-at (relative), duration. Four filters: trigger dropdown, flow-id substring, status dropdown, repo substring. Collapsed state persists to localStorage("flow.runs-drawer.open"), default expanded.
  • Replay mode in FlowCanvas.tsx — when the URL carries ?run=<runId>, the canvas loads the run via flowsApi.getRun, fetches the flow body pinned to run.flow_version via getFlow(flowId, version), and paints per-node status outlines (ok green, skipped dashed/dim, error red + ⚠, timeout amber, running pulsing accent). A blue banner identifies the replayed run + version; a floating "Exit replay" pill strips the query param. Save is disabled and Ctrl+S / palette-Q are suppressed while replaying.
  • Fidelity rulegetFlow() accepts an optional version arg. The mock can only produce the current body, so the response carries a versionMatch flag that flips to false whenever the pinned version differs from the current version; the banner then surfaces a "body may differ" warning. Real version-pinning lands with NF-7's GET /flows/:id/versions/:version.
  • Inspector Run tab (Inspector.tsx) — accepts an optional runNode prop; the tab is only enabled when the parent canvas is in replay mode and the current selection resolves to a recorded flow_node_runs row. Surfaces status, duration_ms, and JSON-formatted input / output / error / intent.
  • Fixture (apps/web/src/fixtures/flow-runs.json) — 8 sample runs across 3 flows + 5 statuses (ok / error / cancelled / running / with-timeout), plus matching per-node trace rows. Shape mirrors apps/server/src/infrastructure/database/db.ts::FlowRunRow + FlowNodeRunRow.

Test plan

  • bun x turbo run typecheck — clean
  • bun x biome check apps/web/src — clean
  • RunsDrawer.test.tsx — 10 tests (rows render, all 3 filters narrow, click navigates with {run} search, collapse/expand toggle)
  • FlowCanvas.replay.test.tsx — 9 tests (banner + version, no banner without runId, body-may-differ warning, save disabled, Ctrl+S suppressed, status overlay in node data, error-node overlay, Inspector Run tab enabled + JSON surfaced, exit-pill navigates)
  • Existing suites regression-clean: Inspector.test.tsx (13) + FlowCanvasEditing.test.tsx (18) + FlowCanvas.test.tsx (8) + InputRefPicker.test.tsx (30) + flows.test.tsx route (8) all pass — 125 flows tests total
  • Manual: drawer toggles, row click lands on /flows/:flowId?run=:id, replay banner reads right, Save is disabled, Exit-replay pill clears the query param

Out of scope

  • Live SSE subscription — drawer fetches once on open + exposes a Refresh button; NF-UI-6 / NF-UI-8 land the typed flow_run_* SSE wiring.
  • Node-run time-travel controls (scrubber, re-fire) — separate story (NF-UI-8).
  • Server endpoints — unchanged; NF-4 already ships GET /flows/runs + GET /flows/runs/:id and the fixture mirrors those response shapes.

Closes #337

🤖 Generated with Claude Code

## Summary - **RunsDrawer** (`apps/web/src/features/flows/RunsDrawer.tsx`) — bottom-docked collapsible panel mounted on the `/flows` parent layout, streams recent flow runs from the mock `flowsApi.listRuns` surface. Columns: flow id, trigger, status badge (running / ok / error / cancelled), started-at (relative), duration. Four filters: trigger dropdown, flow-id substring, status dropdown, repo substring. Collapsed state persists to `localStorage("flow.runs-drawer.open")`, default expanded. - **Replay mode** in `FlowCanvas.tsx` — when the URL carries `?run=<runId>`, the canvas loads the run via `flowsApi.getRun`, fetches the flow body **pinned to `run.flow_version`** via `getFlow(flowId, version)`, and paints per-node status outlines (ok green, skipped dashed/dim, error red + ⚠, timeout amber, running pulsing accent). A blue banner identifies the replayed run + version; a floating "Exit replay" pill strips the query param. Save is disabled and Ctrl+S / palette-Q are suppressed while replaying. - **Fidelity rule** — `getFlow()` accepts an optional `version` arg. The mock can only produce the current body, so the response carries a `versionMatch` flag that flips to `false` whenever the pinned version differs from the current version; the banner then surfaces a "body may differ" warning. Real version-pinning lands with NF-7's `GET /flows/:id/versions/:version`. - **Inspector Run tab** (`Inspector.tsx`) — accepts an optional `runNode` prop; the tab is only enabled when the parent canvas is in replay mode and the current selection resolves to a recorded `flow_node_runs` row. Surfaces `status`, `duration_ms`, and JSON-formatted `input` / `output` / `error` / `intent`. - **Fixture** (`apps/web/src/fixtures/flow-runs.json`) — 8 sample runs across 3 flows + 5 statuses (ok / error / cancelled / running / with-timeout), plus matching per-node trace rows. Shape mirrors `apps/server/src/infrastructure/database/db.ts::FlowRunRow` + `FlowNodeRunRow`. ## Test plan - [x] `bun x turbo run typecheck` — clean - [x] `bun x biome check apps/web/src` — clean - [x] `RunsDrawer.test.tsx` — 10 tests (rows render, all 3 filters narrow, click navigates with `{run}` search, collapse/expand toggle) - [x] `FlowCanvas.replay.test.tsx` — 9 tests (banner + version, no banner without `runId`, body-may-differ warning, save disabled, Ctrl+S suppressed, status overlay in node data, error-node overlay, Inspector Run tab enabled + JSON surfaced, exit-pill navigates) - [x] Existing suites regression-clean: `Inspector.test.tsx` (13) + `FlowCanvasEditing.test.tsx` (18) + `FlowCanvas.test.tsx` (8) + `InputRefPicker.test.tsx` (30) + `flows.test.tsx` route (8) all pass — 125 flows tests total - [ ] Manual: drawer toggles, row click lands on `/flows/:flowId?run=:id`, replay banner reads right, Save is disabled, Exit-replay pill clears the query param ## Out of scope - **Live SSE subscription** — drawer fetches once on open + exposes a Refresh button; NF-UI-6 / NF-UI-8 land the typed `flow_run_*` SSE wiring. - **Node-run time-travel controls** (scrubber, re-fire) — separate story (NF-UI-8). - **Server endpoints** — unchanged; NF-4 already ships `GET /flows/runs` + `GET /flows/runs/:id` and the fixture mirrors those response shapes. Closes #337 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(web): replay mode + runs drawer (NF-UI-7)
All checks were successful
qa / qa (pull_request) Successful in 5m14s
qa / dockerfile (pull_request) Successful in 11s
48eee9de01
Bottom-docked RunsDrawer lists recent flow runs across every /flows/*
route; clicking a row navigates to /flows/:flowId?run=:runId, which
flips the FlowCanvas into replay mode: pinned-version body, disabled
save, status/duration/IO overlays per node, and an exit-replay pill.

Closes #337
charles force-pushed feat/337-nfui7-replay-runs from 48eee9de01
All checks were successful
qa / qa (pull_request) Successful in 5m14s
qa / dockerfile (pull_request) Successful in 11s
to 20031dd80c
Some checks failed
qa / qa (pull_request) Failing after 3m7s
qa / dockerfile (pull_request) Successful in 8s
2026-04-24 16:32:19 +00:00
Compare
charles force-pushed feat/337-nfui7-replay-runs from 20031dd80c
Some checks failed
qa / qa (pull_request) Failing after 3m7s
qa / dockerfile (pull_request) Successful in 8s
to f3d4dfe446
All checks were successful
qa / qa (pull_request) Successful in 4m54s
qa / dockerfile (pull_request) Successful in 9s
2026-04-24 16:39:34 +00:00
Compare
code-lead deleted branch feat/337-nfui7-replay-runs 2026-04-24 16:44:57 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!365
No description provided.