Summary
A scenario is a branching DAG, but nowhere in the app can you see it as one. scenarios/detail.html renders a depth-first text tree and the editor edits nodes one at a time; a reviewer has to rebuild the shape of the exercise in their head. Render the inject graph visually — and, post-exercise, overlay the path each team actually took.
Two views, one renderer
- Authoring/review (scenario detail + editor): nodes = injects, edges = per-option
next_inject_id branches plus node-level next_inject_id linear chains (both edge kinds already live in _check_no_cycles's adjacency graph — reuse that traversal, don't re-derive it). Show option labels on edges, team targets as the existing team-scent pills, and validation errors on the offending node — the validation sidebar already computes these.
- Debrief overlay (report/review pages): dim the full graph and highlight the edges each team traversed, per the response records and
ExerciseStateTransition timeline. "The comms team never saw the containment branch" becomes visible at a glance — this is the piece that upgrades the report page into a hotwash tool.
Rendering constraints (the hard part)
- Strict CSP (
script-src 'self', no unsafe-eval) rules out CDN-loaded graph libraries, and most off-the-shelf ones (mermaid, cytoscape) are heavyweight for this. _check_no_cycles guarantees the graph is a DAG, so a hand-rolled layered layout (longest-path layering + simple barycenter ordering) rendered as inline SVG is feasible and keeps us dependency-free. If a library is genuinely needed, it must be vendored and version-pinned like the Alpine CSP build.
- The renderer must be a CSP-safe Alpine component (
Alpine.data registry file under static/js/pages/), server data passed as |tojson factory args — no optional chaining or arrow functions in directive attributes.
- Sample scenarios are small (7 injects) but don't assume that: cap node label length, scroll/pan inside an
overflow container rather than growing the page.
Not in scope
- Graph-based editing (drag to rewire). Read-only first; the tree editor remains the write path.
- Replay animation. A static path overlay is the deliverable; time-scrubbing can build on it later if wanted.
Prior art in-repo
scenarios/detail.html depth-first tree + validation sidebar (data source for view 1)
exercises/report.html / review.html + build_report (data source for view 2)
_check_no_cycles in app/schemas/scenario_json.py (canonical edge set)
Summary
A scenario is a branching DAG, but nowhere in the app can you see it as one.
scenarios/detail.htmlrenders a depth-first text tree and the editor edits nodes one at a time; a reviewer has to rebuild the shape of the exercise in their head. Render the inject graph visually — and, post-exercise, overlay the path each team actually took.Two views, one renderer
next_inject_idbranches plus node-levelnext_inject_idlinear chains (both edge kinds already live in_check_no_cycles's adjacency graph — reuse that traversal, don't re-derive it). Show option labels on edges, team targets as the existing team-scent pills, and validation errors on the offending node — the validation sidebar already computes these.ExerciseStateTransitiontimeline. "The comms team never saw the containment branch" becomes visible at a glance — this is the piece that upgrades the report page into a hotwash tool.Rendering constraints (the hard part)
script-src 'self', nounsafe-eval) rules out CDN-loaded graph libraries, and most off-the-shelf ones (mermaid, cytoscape) are heavyweight for this._check_no_cyclesguarantees the graph is a DAG, so a hand-rolled layered layout (longest-path layering + simple barycenter ordering) rendered as inline SVG is feasible and keeps us dependency-free. If a library is genuinely needed, it must be vendored and version-pinned like the Alpine CSP build.Alpine.dataregistry file understatic/js/pages/), server data passed as|tojsonfactory args — no optional chaining or arrow functions in directive attributes.overflowcontainer rather than growing the page.Not in scope
Prior art in-repo
scenarios/detail.htmldepth-first tree + validation sidebar (data source for view 1)exercises/report.html/review.html+build_report(data source for view 2)_check_no_cyclesinapp/schemas/scenario_json.py(canonical edge set)