ADR 0030: Animated conceptual diagrams are frame-stepped captures of committed brand-tokened sources, shipped through the clip pipeline¶
Status: Accepted Date: 2026-08-03 Issue: #1161
Context¶
The Explanation section explains models: the ISA-88 procedural hierarchy, recipe-to-control-recipe instantiation, the 13-state machine, the GitOps flow, deployment topologies. Motion carries these better than stills, and the Explanation & Standards visuals epic needs one deliberate answer on treatment before it can storyboard anything.
An inventory of both sections (appendix below) sharpened the problem. The
section's 38 image embeds resolve to 22 unique PNGs, and every one of them
is a product-UI screenshot generated from a .shot.yaml spec. The repo
contains zero drawn diagrams. The conceptual load is carried by three
mermaid blocks and by prose alone: the three most conceptual pages
(procedural-sfc-dialect.md, gitops-for-automation-engineers.md,
ui-deployment-topologies.md, 5,700 words together) have no visuals at
all. The animated-diagram question is therefore a greenfield authoring
question. There is no migration backlog.
The doctrine from #1161 stands: film the product's own diagram surfaces first. The gateway already renders live SFC charts, FB networks, state-machine-driven faceplates, and the recipe hierarchy viewer, and the recorder pipeline (#655) films them with zero new tooling. The SVG leg this ADR designs exists only for the truly abstract diagrams no product surface renders.
The existing clip pipeline, which the SVG leg should ride:
- Spec: committed
docs/videos/*.clip.yaml, schema-validated (scripts/docs-shots/schema/clip.schema.json). - Record:
record.mjsdrives Playwright, captures wall-clock WebM, transcodes via ffmpeg to<stem>.mp4+<stem>.gif+<stem>.jpgposter. - Governance:
lint-docs-clips(schema validity, strict doc-orphan gate,covers:freshness, retired-site scan). - Publish:
publish.mjsuploads to R2 keyed by tier, rewritesdocs/videos/manifest.jsonwith URL + sha256; idempotent by content hash. - Embed: the canonical
<figure class="doc-video">block (STYLE-GUIDE § Video Clips) with required poster, plus the play-once autoplay contract (doc-video-autoplay.js).
Requirements from #1161 for the SVG leg: deterministic output (same input, same frames), brand-tokened styling only, committed spec files as source of truth, the same publish pipeline and strict orphan gate, and motion restraint (staged reveals and traveling highlights; no ambient looping; the play-once autoplay contract applies).
Decision¶
We build the SVG leg as a thin extension of the clip pipeline. Four decisions:
1. An animated diagram IS a clip. A diagram declares itself with a
diagram: block in an ordinary *.clip.yaml under docs/videos/:
video: isa88-four-layer-stack
summary: The four-layer stack, revealed layer by layer
diagram:
src: diagrams/isa88-four-layer-stack.html # relative to docs/videos/
tags: [diagram, explanation]
covers:
- docs/videos/diagrams/isa88-four-layer-stack.html
The animation source is a committed, self-contained HTML/SVG/CSS file
under docs/videos/diagrams/. Because the spec is a clip spec, every
downstream stage applies unchanged: lint-docs-clips validates it and
enforces the strict orphan gate, covers: freshness now watches the
diagram source itself, publish.mjs uploads the artifacts and rows the
manifest, and the doc embeds the standard figure.doc-video block. A
diagram: spec rejects the filming-only keys (steps, stage, url,
scenario, identity, auth, storageState, skipIf) at load time.
2. Rendering is deterministic frame-stepping, with no wall clock.
record.mjs routes diagram: specs to a renderer
(lib/render-diagram.mjs) that:
- serves the repo root over a loopback HTTP server and opens the diagram page in the pipeline's Chromium at the clip viewport (1280×720 default);
- waits for
document.fonts.ready, collectsdocument.getAnimations({subtree: true}), and pauses every animation; - refuses to render if any animation is infinite
(
getComputedTiming().endTimeunbounded), enforcing the no-ambient- looping rule mechanically; - derives the timeline (latest animation end plus a short hold on the
completed diagram) and seeks every animation's
currentTimetoframe / fps, screenshotting each frame; - assembles the frames with ffmpeg into the same artifact set as a
filmed clip:
<stem>.mp4(libx264, same encode parameters),<stem>.gif, and<stem>.jpgposter, which for diagrams defaults to the final frame (the completed diagram is the correct thumbnail).
The renderer never contacts the gateway, needs no cluster or docs-shots stack, and holds no load-governor heartbeat. A diagram re-render is a seconds-long, laptop-local operation. Same source and spec produce the same frames on the same host toolchain; the manifest's sha256 is the drift detector. (Bitwise stability across different Chromium/ffmpeg/font versions is out of scope, exactly as it is for screenshots.)
3. The animation vocabulary is CSS animations/transitions and WAAPI,
declared in the committed source. These are the technologies
document.getAnimations() can pause and seek. SMIL and rAF-driven JS
animation are excluded: neither is externally seekable, so neither can
render deterministically. Motion restraint follows from the vocabulary
plus the infinite-animation refusal: staged reveals (opacity/transform,
animation-delay sequencing) and traveling highlights
(stroke-dashoffset, offset-path) express everything the Explanation
diagrams need.
4. Styling consumes the gateway's generated brand tokens directly.
The diagram page links internal/gateway/static/css/base.css (served by
the render server from the repo root) and a shared
docs/videos/diagrams/diagram-base.css; diagram-specific CSS uses
var(--token) exclusively, with zero raw color literals. This is the
lint-docs-brand-hex philosophy applied to a new surface, and it reuses
the existing generated block, so no third palette block exists to drift
when the brand manifest changes. Diagrams render on the gateway's dark
--bg-primary and read as product-family artifacts next to filmed
clips.
The GIF fallback keeps the clip pipeline's existing loop behavior; the
play-once contract is enforced where it always was, on the <video>
element by doc-video-autoplay.js. The motion-restraint rule governs the
authored animation itself, which ends on a stable completed frame.
Alternatives Considered¶
-
Embed the live SVG/CSS animation in the docs page (no video at all — the reader's browser runs the animation). The source stays vector-crisp at any zoom and weighs a few kilobytes. Rejected on four grounds. First, it forfeits the settled doc-video contract: the play-once/click-to-replay behavior, start-on-scroll, poster-before-load and reduced-motion handling all fall out of
<video>for free, while a CSS animation has noendedevent to hang any of it on, so the docs theme would grow a parallel animation controller. Second, style isolation: the diagram consumes the gateway's brand tokens, which do not exist in the docs page's CSS scope (mkdocs-material has its own--md-*palette), so inlining means bridging two token systems or letting the theme's cascade, and every Material upgrade, reach into the diagram. Third, reviewability: the program's quality gate approves a fixed artifact frame by frame, and the manifest sha256 detects drift; a live animation renders differently per browser, font stack, and viewport, leaving nothing stable to approve or hash. Fourth, consistency: diagrams sit beside filmed clips on the same pages and should carry the same player chrome, publish machinery, and tier system. The conceded costs of rendering are raster output at the clip viewport and ~10x the bytes. (Recorded after the accepted decision prompted exactly this question in review; the alternative was considered and the decision is unchanged.) -
Film the product only; no SVG leg. The default, and it stays the default wherever a product surface carries the concept (see appendix verdicts). Insufficient alone: the model-mapping ladder, the instantiation cascade crossing CRD boundaries, and deployment topologies have no product surface, and mermaid cannot animate them.
-
Wall-clock recording of the diagram page through
recordOneverbatim. Maximum reuse, but it inherits everything designed for live capture: encode-time jitter, boot-trim measurement, the DEGHOST keyframe repair, the luma-inversion guard. Determinism ("same input, same frames") is unachievable by construction, and the workarounds cost more than the frame-stepper they replace. -
Motion Canvas. MIT-licensed, genuinely deterministic, built for exactly this. Rejected for dependency surface: it brings a TypeScript scene format, its own renderer and editor UI, and a second animation idiom the epic's authors would learn alongside the clip DSL. Brand tokens would need bridging from CSS into its TS scene graph, working against the generated-block model. Our motion needs (staged reveal, traveling highlight) sit far below its power floor.
-
Remotion. React-based programmatic video. Rejected on licensing (company license required at our size) before dependency-surface arguments even start.
-
Lottie/Bodymovin. Authoring lives in After Effects, a proprietary tool outside the repo; the committed artifact would be exported JSON that no reviewer can diff meaningfully. Fails the committed-source-of-truth requirement.
-
SMIL animation inside the SVG. Self-contained and dependency-free, but deprecated in spirit, uneven in tooling, and invisible to
getAnimations(), so the renderer cannot seek it. CSS on SVG reaches the same effects seekably.
Consequences¶
- Zero new dependencies: the leg is Playwright + ffmpeg, both already load-bearing in the pipeline, plus roughly two hundred lines of renderer.
- Full governance reuse. Orphan gate, freshness (now watching the diagram source file), retired-site scan, publish idempotence, manifest hashing, and the embed/autoplay contract all apply to diagrams from day one, with no parallel machinery to keep in sync.
- Diagram renders are cluster-free and fast, so re-rendering after a brand-token change is cheap, and a future CI check could re-render and hash-compare without standing up a stack.
- Authoring is hand-written HTML/SVG/CSS within a constrained
vocabulary. That is a real skill demand on the epic, and the
storyboards must design within staged-reveal/traveling-highlight
motion. The PoC (
isa88-four-layer-stack) is the reference implementation. - The clip schema now describes two kinds of clip. Load-time validation keeps them disjoint; the cost is a slightly wider schema.
- Follow-ups deferred to the Explanation epic: a lint extending the
raw-hex ban to
docs/videos/diagrams/sources, and per-page embed placement decisions (whether a mermaid block stays as the reduced-motion/print fallback beside its animated figure).
Appendix: per-diagram inventory and verdicts¶
Inventory of the Explanation and Standards sections, 2026-08-03. Verdicts follow the #1161 doctrine: product-surface footage first, SVG animation only for the truly abstract, static where motion adds nothing.
Existing visuals¶
| Asset | Page | Verdict |
|---|---|---|
| 22 unique UI screenshots (38 embeds, all shot-spec PNGs) | adaptation-guide, parameter-binding, alarms-and-interlocks, debugging-running-batches |
Static stays. How-to UI references; not a migration backlog. |
Mermaid: four-layer stack (flowchart TD) |
library/mental-models.md |
SVG animation (the PoC, isa88-four-layer-stack). Staged reveal down the ladder, traveling highlight along the binding edges. Epic decides whether the mermaid stays as fallback. |
| Mermaid: five-layer parameter binding chain | library/parameter-binding.md |
SVG animation. The master-to-control instantiation cascade crossing CRD boundaries; no product surface shows it end to end. Reuses the PoC's ladder grammar. |
| Mermaid: alarm-to-operator flow | library/alarms-and-interlocks.md |
Static stays (this wave). The page already carries the interlock-bypass clip; the operator-facing half is better served by more product footage if anything. |
Video: interlock-bypass clip |
library/alarms-and-interlocks.md |
Stays. Already filmed, published, embedded. |
Un-illustrated concepts (net-new visuals)¶
| Concept | Page | Verdict |
|---|---|---|
| ISA-88 procedural hierarchy / model-mapping ladder | library/mental-models.md |
SVG animation (the PoC). |
| Recipe instantiation cascade (master → control) | library/parameter-binding.md, terminology.md |
SVG animation. |
| 13-state machine | library/mental-models.md, equipment-operation.md |
Both, product first. Footage of the command bar and faceplate walking Hold/Restart/Stop transitions carries the lived behavior; an SVG state-graph animation carries the full topology (transient vs quiescent states) that no surface renders whole. |
| Procedural SFC dialect (charts, steps, transitions) | explanation/procedural-sfc-dialect.md |
Product-surface footage. The SFC monitor walking a live chart IS the diagram; the existing SFC clip bank (st-hold, sfc-monitor-api, three-views) proves the format. |
| GitOps promotion flow | explanation/gitops-for-automation-engineers.md |
Product-surface footage. recipe-promotion-gitops and gitops-provisioning already film the real flow end to end; the epic re-embeds or recuts. |
| UI deployment topologies (direct / Git PR / review queue), site-to-enterprise | explanation/ui-deployment-topologies.md |
SVG animation. Pure topology; no product surface. |
| Standards section (MTP, IEC 63280, NE 148, NE 175/NOA; 5 pages, zero visuals today) | docs/standards/* |
Static stays this wave. Prose + conformance tables. An MTP/NOA architecture panel is a storyboard-time stretch candidate, SVG leg if taken. |
Compliance-section visuals belong to the Compliance epic and are out of scope here.