Motion Controller Security Contract and Rejected Patterns in Diagram Design

The Diagram Design skill enforces a strict static-first security contract requiring complete HTML/SVG source availability before animation, permitting only the vetted controller found in assets/template-motion.html, and explicitly rejecting patterns like unscoped hiding or infinite animations that compromise accessibility.

The cathrynlavery/diagram-design repository governs optional animated diagrams through a rigorous motion controller security contract designed to ensure deterministic rendering and universal accessibility. This contract mandates that every semantic node, label, and connector exist in the static markup before JavaScript enhancement, while the verification system in scripts/verify-motion.py actively rejects implementations that violate timing budgets, controller uniqueness, or no-JS fallback requirements.

Static-First Enhancement Contract

The contract defines seven mandatory requirements that every motion diagram must satisfy, ensuring the source remains complete and accessible regardless of JavaScript state.

Source Completeness and Deterministic Capture

Every semantic node, label, connector, and outcome must be present in the HTML/SVG before any motion is applied. Only selectors under .motion-ready may hide or transform items. The final static frame must be exposed via data-frame="static" (or query parameter ?motion=static), and this frame must be pixel-identical on repeated captures without arbitrary delays, as specified in references/animation.md【animation.md†L20-L24】【animation.md†L23-L24】.

CSS-Only Presentation and Timing Budget

Appearance and travel must be expressed exclusively with CSS transitions and keyframes. JavaScript is limited to binding controls, updating step attributes, and managing a single timeout chain. The timing budget enforces specific CSS variables: --motion-fast: 160ms, --motion-step: 480ms, --motion-hold: 720ms, with a total animation time not exceeding 8000ms. All delays derive from integer steps; randomness and spring physics are prohibited【animation.md†L25-L30】【animation.md†L25-L26】.

Explicit Ordering and Stable End State

Items must be marked with data-motion-item data-step="N" for integer steps 1 through 8, with no more than two semantic items sharing a step. Upon completion, all items must become visible (data-frame="end"), and replay functionality must reset to step 0 before playing again【animation.md†L26-L28】.

Scoped State and Fail-Safe Startup

Controls must operate on the nearest [data-motion-root]; IDs, timers, and live-status regions must never cross diagram boundaries. The script adds .motion-ready only after controls are bound and the initial render succeeds, ensuring a script error leaves the complete source visible rather than a broken partial state【animation.md†L28-L30】.

Verified Controller Contract

The only permissible JavaScript controller is the exact script embedded in assets/template-motion.html. The verifier (scripts/verify-motion.py) extracts the script body from candidate diagrams and compares it—after normalizing line endings—to the canonical controller:

canonical = normalized_controller(open(MOTION_TEMPLATE).read())
if normalized_controller(candidate_body) != canonical:
    errors.append("script must exactly match the controller in template-motion.html")

This strict equality check appears in verify-motion.py lines 56-86 and 82-85【verify-motion.py†L56-L86】【verify-motion.py†L82-L85】. The contract enforces three specific constraints:

  • Script count: At most one <script> element is allowed in the document.
  • Attribute check: The script must carry exactly data-diagram-controls="".
  • Controller equality: The script body must be identical to the canonical controller byte-for-byte after normalization.

Rejected Patterns That Violate the Contract

The verifier rejects specific anti-patterns that would break the static-first guarantee or accessibility requirements:

  • Unscoped hiding: Selectors that hide data-motion-item before .motion-ready is applied break the no-JS fallback. Detected by the hidden_unscoped_motion_selectors function.
  • Infinite animation outside loop mode: Prevents endless motion that could alter semantic meaning, detected by infinite_unscoped_selectors.
  • Non-standard playback controls: In step mode, the diagram must expose exactly one control group with actions play, pause, replay, prev, and next.
  • Missing ARIA live region: Motion status must exist as a live region outside the controls to remain visible in reduced-motion mode.
  • Missing reduced-motion or print CSS: The contract requires CSS fallbacks for prefers-reduced-motion and @media print.
  • Incorrect SVG accessibility: SVG elements must have role="img" and a first-child <title> followed by <desc> linked via aria-labelledby.
  • Budget violations: Diagrams with more than 12 motion items, steps exceeding 8, or more than two items per step fail verification checks on parser.items and step continuity.

These validations ensure that motion enhancements never obscure the underlying information architecture.

Implementing Motion Diagrams Safely

Developers must follow a four-step workflow to comply with the security contract:

  1. Copy the canonical template: cp skills/diagram-design/assets/template-motion.html my-diagram.html supplies the required controller, CSS variables, and baseline structure.
  2. Mark semantic items: Add data-motion-item data-step="N" and descriptive aria-label attributes to each animated element.
  3. Declare the motion root: Wrap content in <main data-motion-root data-motion-mode="step" data-step-count="5">…</main> to establish scope and mode.
  4. Run verification: Execute python3 scripts/verify-motion.py path/to/my-diagram.html to validate contract compliance before deployment.

The following example demonstrates a compliant step-mode diagram:

<!-- 1️⃣ Start from the canonical template -->
<link rel="stylesheet" href="template-motion.html">

<!-- 2️⃣ Define the motion root with step count -->
<main data-motion-root data-motion-mode="step"
      data-step-count="3" data-frame="static">
  <!-- 3️⃣ Semantic items – each must have an aria-label -->
  <g data-motion-item data-step="1" aria-label="Request received">
    …
  </g>
  <g data-motion-item data-step="2" aria-label="Policy passed">
    …
  </g>
  <g data-motion-item data-step="3" aria-label="Audit logged">
    …
  </g>

  <!-- 4️⃣ Playback controls (required for step mode) -->
  <div data-motion-controls role="group" aria-label="Diagram playback controls">
    <button type="button" data-motion-action="prev">Previous</button>
    <button type="button" data-motion-action="play" aria-pressed="false">Play</button>
    <button type="button" data-motion-action="pause" aria-pressed="true">Pause</button>
    <button type="button" data-motion-action="next">Next</button>
    <button type="button" data-motion-action="replay">Replay</button>
  </div>

  <!-- 5️⃣ Live status region (must be outside controls) -->
  <p class="sr-only" data-motion-status role="status"
     aria-live="polite" aria-atomic="true"></p>
</main>

Validate the implementation using:


# Verify the diagram against the contract

python3 scripts/verify-motion.py my-diagram.html

# Lint the skin (checks CSS, SVG, and controller identity)

python3 scripts/lint-skin.py my-diagram.html

Summary

  • The static-first contract requires complete HTML/SVG source visibility before JavaScript activation, ensuring diagrams remain understandable without scripting.
  • Controller verification enforces byte-for-byte identity with assets/template-motion.html, rejecting any unauthorized scripts or modifications.
  • Rejected patterns include unscoped hiding, infinite animations, incorrect ARIA implementations, and budget overruns (max 8 steps, 12 items, 8000ms total).
  • Implementation requires using the canonical template, marking items with data-motion-item, and passing verify-motion.py before shipping.
  • The contract guarantees accessibility compliance across reduced-motion preferences, print media, and no-JS environments.

Frequently Asked Questions

What happens if JavaScript fails to load in a motion diagram?

The fail-safe startup requirement ensures that the .motion-ready class— which controls visibility transitions—is added only after successful controller initialization and binding. If the script errors or fails to load, the class is never applied, leaving all semantic content fully visible in its initial static state (data-frame="static"), preserving the diagram's informational integrity.

Why does the verifier require an exact byte-for-byte match with the canonical controller?

The verified controller contract treats assets/template-motion.html as the single source of truth to prevent injection of unauthorized logic, tracking code, or performance-harming animation techniques. By normalizing line endings and comparing the candidate script body against the canonical version in verify-motion.py, the system guarantees that only audited, accessibility-compliant code executes within animated diagrams.

What are the hard limits for motion timing and complexity?

The contract enforces a timing budget with CSS variables defining --motion-fast: 160ms, --motion-step: 480ms, and --motion-hold: 720ms, requiring total animation duration to remain under 8000ms. Complexity is constrained to a maximum of 8 steps, 12 total motion items, and no more than two semantic items per step, as validated by the parser.items checks in verify-motion.py.

How does the contract support users with motion sensitivity or print requirements?

The rejected patterns explicitly include missing prefers-reduced-motion and @media print CSS fallbacks. Additionally, diagrams must include an ARIA live region outside the control set to announce status changes, ensuring that users who disable animations or print the page receive the complete semantic content without relying on the motion controller.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →