🎬 saga — record → replay → verdict — guide
Class:
guide· audience: a techie recording a journey through the viz, persisting it, and replaying it as a regression test. The what is insaga-design.md; the zero-knowledge walkthrough issaga-for-idiots.md.
f0 — the whole workflow
saga records what you actually do in the viz, saves it, and replays it as a robot test so a later change that breaks that journey turns RED.
- Build with capture on —
--features saga(off by default; release ships clean). - ⏺ Record in the Test tab → navigate/click the viz → ⏹ Stop to persist.
- The journey lands in the
saga_eventswarehouse table as a replayable session. - ▶ Replay it → the recorded path runs as a robot
Scenario→ GREEN rows if the widgets still exist, a RED row the instant one vanished. nornir saga path <session>— read the journey back as a route funnel.
f1 — build with capture, then record
Capture is behind the off-by-default saga feature. Build the viz with it:
cargo run --features "viz saga" --bin nornir-ui # or your viz launch
In the running viz:
- Open the 🧪 Test tab, expand 🎬 Saga Sessions (record/replay).
- Press ⏺ Record — this clears any stale capture and arms the recorder.
- Now use the app: click tab headers (🧪 Test, 🗂 Funnel, 🧵 Timeline…), press buttons. Each tab switch records a
navwhosecomponentis the header's AccessKit label; wired on-clicks record aclick. - Press ⏹ Stop — the buffer drains, writes to
saga_events(wrapped in aSAGA_CAPTUREjob), and the session list reloads. You'll seestopped — wrote N event(s) to saga_events.
The whole window's journey groups under one session id (stable per window), so a session is exactly "one window's worth of navigating".
f2 — see what you recorded, replay it
The Saga Sessions table lists every recorded session: id, app, event count, replayable-step count, error count, and a 🚩 stuck flag (rage-click clusters or repeated-error routes float to the top so you triage the painful sessions first).
Press ▶ Replay on a replayable row. saga builds that session's ReplayPlan (its nav/click steps in capture order) and turns it into a robot Scenario; the last-replay line shows N step(s) → N matrix row(s). A replayed step that can't find its recorded widget is a RED matrix row — a real regression caught, never a panic.
f3 — replay as a verdict (the e2e proof)
The tests/saga_e2e.rs proof is the pattern to copy for a scripted regression check:
cargo test --features "saga" --test saga_e2e
It: (1) drives real AccessKit header clicks through the viz and drains the recorded events; (2) persists them through the same IcebergSagaSink the ⏹ Stop toggle uses; (3) reads the session back, builds a ReplayPlan, converts it to a Scenario, and runs it against a fresh app — asserting every step is status::PASS; (4) remaps the first step's label to a non-existent widget and asserts it produces a status::FAIL row. That last assert is the inject-assert LAW: replay must go red when a recorded widget disappears.
f4 — the CLI verbs
# Fold a saga file's movement stream and print the final observable model:
nornir saga view path/to/saga.json
nornir saga view --demo --frames # the built-in synthetic saga, one state_json per movement
# Replay a saga file. Default = print each frame as JSONL (deterministic):
nornir saga replay --demo
nornir saga replay saga.json --web # serve a self-contained browser page (needs --features saga-web)
nornir saga replay saga.json --drive http://localhost:4444 # dispatch gestures at a real page (mock unless --features saga-drive)
# The nav-path / funnel for a RECORDED session (read saga_events back):
nornir saga path <session-id>
nornir saga path <session-id> --json
nornir saga path prints the ordered route funnel — route → sessions-reached, per-step drop-off, next-route transitions. An unknown session prints a clear "no events" message (an empty funnel is a valid answer, not an error).
Where the same data shows up
nornir jobs --kind saga_capture— every ⏹ Stop is aSAGA_CAPTUREjob row.- the
saga_eventswarehouse table — one partition per session; read vianornir saga pathor the Test tab's session list. - a
ui::Sagacaptured in a browser bridges into the same table + replay path viaSaga::to_saga_events— a web journey replays exactly like a native one.
Gotchas
- No
--features saga→ capture is a no-op; ⏺/⏹ record nothing (the pane still lists + replays sessions already in the warehouse). - A remote (thin) viz can't persist on ⏹ Stop yet — the status reads
remote persist: phase 2. Record in a local viz. componentMUST be a widget's AccessKit label for replay to re-drive it — that's why the choke recordsTab::label(), not a debug id.