From demos to tutorials
From demos to tutorials
We had six working scenario demos — form filling, parameterised scripts, table population, composable workflows, a script library browser, and a scenario controller showcase. Each one proved a feature worked. None of them taught anyone how to use it.
Issue #395 asked for a tutorial series that teaches the automation platform progressively. The first instinct was to slot tutorials into the existing examples gallery. That was wrong — tutorials are a peer to the gallery, not entries within it. They need their own navigation, their own catalog, their own identity.
The type system shift
The scenario engine had a flat Playbook type: a name and a steps array. Tutorials
need sections — narrative content interleaved with executable steps. Rather than
bolting optional fields onto the existing type, we split it into a discriminated
union: FlatScenario | SectionedScenario. The parser detects which format the
YAML uses and returns the right variant. Existing scenarios work unchanged; the
runner narrows with isSectioned() before accessing steps.
This is the kind of change that’s easy on a pre-release codebase and painful on
a released one. Every call site that accessed scenario.steps had to narrow
first. The payoff: the type system now prevents someone from accidentally treating
a sectioned tutorial as a flat scenario.
Browser-only was the hard part
The scenario controller was built for server mode — REST endpoints for outline data, push wire for state events, fetch for content resolution. Tutorials run browser-only. No server. No WebSocket. No REST.
Three controller modifications made it work:
hostConnected()registers thepages-eventlistener even without a connection — previously gated onconn && targetsendCommand()detects browser-only mode and dispatchesscenario-commandevents instead of hitting REST endpoints — one fix covers all transport buttons, keyboard shortcuts, and outline clicks- The outline arrives in the initial state event payload rather than via
GET /scenario/outline— the controller checks forstate.outlinebefore falling back to the REST call
The sectioned runner fires the same pages-event with scenario:state topic
that the server handler does. The controller and narrative component don’t know
the difference.
SVG in markdown
Tutorial slides need architecture diagrams. Inline SVG in markdown is the
right approach — CSS custom properties (var(--pages-neutral-12)) adapt to
light and dark themes automatically. But the narrative renderer’s markdown
processor escaped all HTML, turning SVG into visible angle brackets.
The fix has two layers. An HTML sanitizer with an SVG-aware allowlist
passes through safe SVG elements while stripping <script>, event handlers,
and dangerous style values. The markdown processor extracts SVG blocks before
processing, replaces them with placeholders, runs the regex-based markdown
conversion, then re-inserts the SVGs. Same approach for fenced code blocks —
extract, placeholder, process, re-insert.
YAML code blocks get syntax highlighting via the existing yaml-highlighter.ts
tokeniser. Keys render in light blue, strings in green, literals in amber.
The completion marker bug
The outline shows ✓ for completed sections. This sounds simple until you have a mixed outline — some sections are slides-only (leaf nodes, no children), others have executable steps (group nodes with children). The highlighting logic had three bugs:
- Group headings (sections with steps) never showed ✓ — the CSS classes were only applied to leaf nodes
- When
state.stepis null (current section is slides-only), child steps of completed sections lost their ✓ — the completion check couldn’t find their label in the section-level comparison - Slides-only leaf sections never showed ● (current) — the leaf check only
matched against
state.step, which is null for slides-only sections
Each bug got a test first, then the minimal fix. The pattern: _isBeforeCurrent
needs to know whether a label is a section title or a step name, and whether to
compare at the section level or the step level. A _findParentSection helper
resolves child steps to their parent section for section-level comparison.
What shipped
The tutorial catalog component (<pages-tutorial-catalog>) shows areas at root
level, drills into tutorial cards with hero icons, difficulty chips, and concept
labels. Two display modes — tiled cards and a filterable list. A
<pages-tutorial-host> component wires catalog selection to the sectioned
runner, mounting the controller and narrative components with prev/next slide
navigation.
Tutorial 0 (Architecture & Concepts) is seven slides-only sections with four
inline SVG diagrams covering the orchestrator topology, ARIA targeting, the
compilation pipeline, and the script registry. Tutorial 1 (Form Automation
Basics) is seven sections mixing narrative with executable fill, select,
and click steps.
A build script scans tutorials/, validates metadata and template file
references, and emits tutorial-registry.json. The aggregation contract lets
casehub/examples merge registries from multiple sources — tutorials are
portable across repos.