← Choice Health console-flow-map-design-v0.md raw .md

RIFF Console — Flow Map Design, v0 (addendum to the artifact & trace schema)

Status: design handoff for the implementing Claude — open decisions marked OD-Mn Date: 2026-07-01 Author: Fable 5 (design) Depends on: docs/call-plan-artifact-schema-v0.md (artifact §3, trace §6, invariants I1–I6) and the existing web/console.html + web/flow-walk.html mockups Replaces: the node-graph problem as previously framed. This is the seventh and final rebuild of the flow view, because it changes the problem, not the rendering.


0. The design decision

The map is a projection of the artifact around a cursor, not a graph layout. Every prior attempt drew the whole FSM and then tried to make it readable. The operator's actual question during a call is always the same: what just happened, what is happening, what can happen next. That question has a fixed shape — past | present | futures — which is computable from the artifact with zero layout heuristics. Readable by construction.

We explicitly refuse to solve general graph layout. The domain has exactly two flow topologies (§4.3), each with a deterministic layout. If a third topology ever appears, it gets its own deterministic layout; we still never ship a force-directed renderer.


1. One component, three semantic zoom levels

Zoom is semantic, not pixel — each level shows different information, never smaller text.

level name shows default when
L2 Overview the whole flow, molecules folded, traversed path as a gold trail, return rail browsing a flow with no call
L1 Corridor past breadcrumb | current node | all exits live call, replay, or sim step (the default lens)
L0 Inspector the existing detail row (aliases, asset, freshness, then-what) a node is selected

Transitions: scroll-out / Z key / minimap click → L2. Click any node at L2 → L1 centered there. Click the current node at L1 → L0 opens as the detail row (already built). L0 never replaces the map; it docks below it.


2. The cursor

The single unifying concept. The map always renders relative to a cursor (call_id, seq):

Consequence for build order: the map lands after trace-on-the-engine-bus (handoff step 1), not before. Against the static mockup, the cursor is driven by the hand-written sample trace.


3. L1 — the corridor

Three fixed zones, left to right.

3.1 Past (left, ~25% width)

3.2 Now (center, ~30% width)

3.3 Next (right, ~45% width) — exhaustive, grouped, classed

Every possible exit from the current node, no exceptions. Three edge classes with fixed styling:

class derivation from artifact rendering
advance on_done, token gotos, on_complete, on_success, confirm yes/no solid arrow → chip
failure on_timeout, no_match.*, on_failure, on_deadline, fallback rungs thin amber arrow → amber chip, grouped below the advance chips
reloop edge whose target is an ancestor on the cursor's entry path no drawn back-edge. A ↻ returns to <target> badge line under the chips; at L2 it's the return rail

Node-type-specific rendering of the next zone — this is the keypad unification:

3.4 Lookahead

Depth 1 strictly (OD-M1: hover-to-peek depth 2 deferred; add only if operators ask. Depth 2 doubles the visual budget and the corridor's whole value is restraint).


4. L2 — the overview

4.1 What it must show

The entire flow with: molecules folded (announce+choose = one node; parameterized states as one node with ×N badge per I6 — an unfold toggle expands per-topic, engineer view only), the traversed path of the cursor's call drawn as a gold trail over the base map, current node pulsing in context, freshness dots on every say node, orphaned/legacy nodes in a collapsed "unreachable" tray (not on the canvas).

4.2 Reloops: the return rail

All loop-back edges exit their source node rightward into a single dashed vertical rail at the right edge, which returns as one dashed lane to the loop target. One lane, however many loops. This is the single rule that keeps IVR overviews readable — back-edges are what make flow graphs spaghetti, so they get a dedicated road instead of individual curves.

4.3 Shape-driven layout — the only two layouts

Detection runs on the artifact at render time; no config.

Mixed flows (a pipeline that opens with a small hub — plumbing's proposed intent front door) render as pipeline with the hub as the first row, its non-booking branches (hours, emergency) as short right-side branches terminating early.

4.4 Minimap

At L1, a passive thumbnail of L2 in the corner with the cursor dot — click to zoom out. No interaction beyond that.


5. Behaviors

5.1 Auto-follow and pinning (specify, don't improvise)

Live default = follow: the corridor recenters on every state_enter. Any user interaction (click, hover >1s on a chip, scroll) pauses follow; a call moved → resume pill appears top-right showing the current node's name; click or F resumes. This is the log-tail pattern. Replay mode never auto-follows; the cursor moves only by trace selection or j/k.

5.2 Keyboard

j/k cursor step (replay) · Z toggle L1/L2 · F resume follow · 1–9 at a choose cursor = simulate that DTMF press in sim mode (drives the existing offline simulator, appends sim trace rows).

5.3 Diagnosis rows stay in the trace, echoed on the map

stall_detected and degraded events flash the current node red/amber and pin a badge until the next state_enter. The trace row remains the primary diagnostic record; the map echo is so an operator watching only the map can't miss it.


6. Data contract — nothing new

Everything renders from artifact + trace. Derivations, all client-side:

No schema changes. No layout hints in the artifact. If the client ever needs a hint, that's a signal the layout rule is wrong, not that the schema is incomplete.

7. Console layout changes

8. Audiences and rendering modes

9. Build order and acceptance

Static mockup first (extends console.html, hand-written data, no live wiring):

  1. <KeypadChips> extracted as the shared component; corridor skeleton (three zones) rendering the §6-sample plumbing trace with j/k scrubbing and trace-row click → cursor.
  2. Edge classing + failure grouping + reloop badge; node-type-specific next zones (choose/say/collect/do), including the Do outcome-frame chips and the speculative ghost chip.
  3. L2 hub layout for Space Channel with return rail + gold trail; L2 pipeline layout for austin_plumbing; Z toggle; minimap.
  4. Follow/pin behavior with the resume pill (fake a "live" mode by replaying the sample trace on a timer).
  5. Drawer for L0 findings; white-label render flag (hide, don't style).

Acceptance (the "stop re-litigating" tests):

10. Open decisions

id decision default in v0
OD-M1 Corridor lookahead depth 1. Hover-peek depth 2 only if operators ask.
OD-M2 White-label mode scope Render flag exists, hides engineer surfaces; visual styling deferred until the first external customer view.
OD-M3 Mobile observer Capability in the design (L1-only, per-call trace topic, signed URL); build nothing until OD-6 lands.
OD-M4 Multi-call supervision Out of v0; component stays per-call.
OD-M5 Sim entry point 1–9 keypress at a choose cursor drives the existing offline simulator. Confirm the all-options harness can emit §6 trace rows; if not, that emission is a prerequisite (and generally useful — it's L1-sim speaking the shared format).

11. Implementation plan & status (Claude)

Division of labor (per David): Claude implements (with codex reviewing plans/diffs); antigravity (agy) publishes documentation to docs/html.

Dependencies already landed:

Build steps (map to §9), each shippable, static mockup first (no live wiring):

  1. Extend riffc to emit {shape, edges:[{from,to,class}]} (derivations in §6). Validation-only, safe. → feeds L2 layout + edge classing.
  2. Extract <KeypadChips> shared component; corridor three-zone skeleton rendering the §6 sample trace; j/k scrub + trace-row→cursor. (§9.1)
  3. Edge classing + failure grouping + reloop badge; node-type next zones incl. Do outcome-frames + speculative ghost. (§9.2)
  4. L2 hub layout (Space Channel) + pipeline layout (austin) + return rail + gold trail + Z toggle + minimap. (§9.3)
  5. Follow/pin + resume pill (fake-live via timed replay). (§9.4)
  6. L0-findings drawer + white-label render flag. (§9.5)

Validation: the five acceptance tests A1–A5 (§9). Each build step is validated against its acceptance criterion in the browser (Playwright screenshot). No live wiring until the trace-on-the-engine-bus step (schema §9 step 1, part 2) lands.

Codex: review this plan before build step 2 (the front-end architecture — <KeypadChips> boundary, cursor model). Review the diff after steps 3 and 4 (the layout logic, where bugs hide).

Codex plan review (2026-07-01) — 8 findings, resolutions:

  1. "Static prototype" vs "production map" conflict with the "map after trace-bus" rule → split them; require a §6 JSONL trace fixture before live wiring. DONE: tests/fixtures/trace_plumbing_sample.jsonl + schema test.
  2. Cursor (call_id, seq) underspecified for non-state_enter rows (e.g. tool_start while state is still collect_job) → current_state = latest prior state_enter; the selected event is an overlay. (corridor build note)
  3. Live follow + pause + resume + scrubbing in single-file HTML → use a tiny event reducer/state machine, not scattered DOM mutations. (corridor build note)
  4. <KeypadChips> coupling risk → make it a pure token-chip renderer; caller-view and corridor are separate adapters. (corridor build note) 5/6. Client-side edge derivation duplicates FSM semantics → riffc's edge list is canonical (class + reason); the browser renders, never infers. DONE: classify_edges emits {class, reason}; L1 recomputes reloop vs the live cursor path.
  5. Real contradiction: hub = choose≥4 tokens, but austin's proposed intent (4 tokens) is a pipeline → token-count alone is wrong. Correct on today's real flows (space=hub/10, austin=chain/3); flagged as OD-M-shape — distinguish menu-hub from front-door intent by compiled topology (fan-out to distinct destinations) before adding intent.
  6. Pipeline needs deterministic order but nodes is a map → riffc emits order (DFS pre-order from entry). DONE.

Status log: