Motion Controller Contract in diagram-design: Validation Rules and Canonical Implementation

The motion controller contract in diagram-design is a strict HTML schema enforced by verify-motion.py that mandates specific data attributes, accessibility annotations, and an exact canonical JavaScript controller to ensure animated diagrams remain consistent, accessible, and performant across all browsers and assistive technologies.

The cathrynlavery/diagram-design repository defines a rigorous motion controller contract governing how optional animations are embedded in diagram HTML files. This contract ensures that motion-enabled diagrams meet strict accessibility standards—including reduced-motion fallbacks and screen-reader compatibility—while maintaining behavioral consistency through a validator that parses and enforces every structural requirement.

Core Elements of the Motion Controller Contract

The contract specifies precise markup requirements across seven domains, validated in scripts/verify-motion.py using a custom MotionParser class.

Root Element and Mode Attributes

Every motion-enabled diagram must contain exactly one element carrying data-motion-root. The validator explicitly checks len(parser.roots) != 1 and raises the error "expected exactly one data-motion-root" if this constraint is violated according to the source at lines 84-86 of verify-motion.py.

The data-motion-mode attribute must belong to a strict set defined in the MODES constant. Valid values are none, reveal, step, or loop, enforced at lines 90-92 against the set defined at lines 16-18.

Step Count and Motion Item Constraints

The data-step-count attribute must be an ASCII decimal integer bounded between 0 (for none mode) and 8. The validator parses and bounds this value in the verify logic at lines 92-100.

A diagram may contain up to 12 motion items (data-motion-item). Each item requires:

  • A valid data-step attribute containing an ASCII decimal integer (checked in parser.items validation at lines 102-122)
  • For semantic items (lacking data-motion-decorative): a non-empty aria-label
  • For decorative items: aria-hidden="true" and focusable="false" attributes

Control Elements and Actions

For controlled modes (step or reveal with a script), the contract mandates exactly one control group marked with data-motion-controls. This group must contain all five required actions: play, pause, replay, prev, and next, validated at lines 136-148 of the verification script.

Additionally, a status element marked data-motion-status must appear with role="status", aria-live="polite", and aria-atomic="true" to provide focus-friendly announcements to assistive technologies.

Script and CSS Requirements

The contract enforces strict embedding rules for JavaScript and stylesheets:

  • Script uniqueness: At most one <script> tag, carrying only the data-diagram-controls attribute
  • Canonical fidelity: The script body must match the canonical controller exactly after whitespace normalization via normalized_controller() at lines 34-36, compared at lines 82-86
  • Reduced motion: CSS must contain a @media (prefers-reduced-motion: reduce) block
  • Print support: CSS must contain a @media print block
  • Control hiding: Selector [data-motion-controls] { display:none !important; } must be present
  • Static fallback: Selector html[data-motion="static"] [data-motion-item] { opacity:1 … } must provide static overrides

SVG Accessibility Standards

Every motion diagram must include at least one <svg> element with role="img" and a proper aria-labelledby attribute referencing a non-empty <title> and <desc>. The title must be the first child of the SVG, validated at lines 70-88 of verify-motion.py.

The Canonical Motion Controller

The canonical controller lives in skills/diagram-design/assets/template-motion.html. This self-executing JavaScript provides the exact logic that every motion-enabled diagram must embed:

  1. Parameter parsing: Reads URL parameters (motion, step) and respects the user's prefers-reduced-motion setting
  2. Step management: Determines step count from data-step-count and builds label lists for each step
  3. Playback controls: Wires DOM elements marked with data-motion-action to play, pause, replay, prev, and next functions
  4. Keyboard navigation: Handles shortcuts for arrow keys (←/→), Home/End, Space, and R
  5. ARIA management: Updates the data-motion-status live region to announce state changes to screen readers
  6. Render stability: Adds the motion-ready class only after the initial render succeeds, ensuring static fallbacks display correctly if JavaScript fails

The verification script extracts this controller from template-motion.html and compares it against the target file's script body using whitespace normalization to ensure byte-for-byte equivalence.

How verify-motion.py Enforces the Contract

Running python -m scripts.verify-motion.py <file> executes a dependency-free validation pipeline (standard library only) consisting of six stages:

  1. HTML Parsing: Instantiates MotionParser to collect roots, items, actions, controls, statuses, scripts, styles, and SVGs
  2. Structural Validation: Verifies root count, mode validity against MODES, step count bounds (0-8), and item budget (max 12)
  3. Controlled Mode Checks: Ensures unique control groups, presence of all five actions, and required status element for interactive modes
  4. Script Verification: Confirms single data-diagram-controls script presence and validates canonical controller equivalence using normalized_controller()
  5. CSS Contract Enforcement: Checks for prefers-reduced-motion and print media queries, control hiding rules, and static overrides
  6. SVG Accessibility Audit: Validates role="img", aria-labelledby references, and proper <title>/<desc> ordering

The validator emits human-readable error messages for any violations; the CI workflow fails on any non-empty report.

Running the Verification

Validate individual files or the entire repository:


# Verify a single diagram file

python -m scripts.verify-motion.py docs/example-diagram.html

# Verify every shipped motion asset (templates + HTML containing motion)

python -m scripts.verify-motion.py --shipped

Successful validation outputs OK <path> when all contract rules are satisfied.

Practical Implementation Example

The following minimal HTML satisfies the motion controller contract for a three-step diagram:

<main data-motion-root data-motion-mode="step" data-step-count="3">
  <svg role="img" aria-labelledby="title desc">
    <title id="title">Demo Diagram</title>
    <desc id="desc">A three-step illustration</desc>
    
    <g data-motion-item data-step="1" aria-label="Step 1">…</g>
    <g data-motion-item data-step="2" aria-label="Step 2">…</g>
    <g data-motion-item data-step="3" aria-label="Step 3">…</g>
  </svg>

  <div data-motion-controls role="group" aria-label="Playback controls">
    <button data-motion-action="prev">Previous</button>
    <button data-motion-action="play" aria-pressed="false">Play</button>
    <button data-motion-action="pause" aria-pressed="true">Pause</button>
    <button data-motion-action="next">Next</button>
    <button data-motion-action="replay">Replay</button>
  </div>

  <p data-motion-status role="status" aria-live="polite" aria-atomic="true"></p>

  <script data-diagram-controls>
    /* Exact copy of the canonical controller from template-motion.html */
  </script>
</main>

Summary

  • The motion controller contract requires exactly one data-motion-root element and a valid data-motion-mode selected from the MODES set (none, reveal, step, loop)
  • Interactive modes mandate a data-motion-controls group containing five specific actions (play, pause, replay, prev, next) and a data-motion-status live region with proper AIA attributes
  • The canonical JavaScript controller must be embedded identically—verified via normalized_controller() whitespace comparison—within a single <script data-diagram-controls> tag
  • CSS must provide @media (prefers-reduced-motion: reduce) and @media (print) fallbacks alongside selectors that hide controls and enable static overrides
  • scripts/verify-motion.py validates all constraints using the MotionParser class without external dependencies, ensuring diagrams meet accessibility and behavioral standards before publication

Frequently Asked Questions

What happens if the embedded script doesn't match the canonical controller exactly?

The validator will report a contract violation. At lines 82-86, verify-motion.py compares the whitespace-normalized content of the diagram's <script data-diagram-controls> against the canonical controller extracted from template-motion.html using normalized_controller(). Any deviation—including extra whitespace, comments, or logic changes—causes validation to fail, ensuring behavioral consistency across all diagrams.

How does the contract accommodate users with motion sensitivity?

The contract enforces multiple accessibility layers. First, the canonical controller automatically detects and respects prefers-reduced-motion settings. Second, CSS must include a @media (prefers-reduced-motion: reduce) block that disables animations. Third, semantic items require aria-label attributes while decorative items must carry aria-hidden="true", ensuring screen readers announce meaningful content without distraction.

What is the maximum number of animation steps allowed in the contract?

The step count is constrained to ASCII decimal integers between 0 and 8, validated at lines 92-100 of verify-motion.py. Additionally, diagrams are limited to 12 motion items (data-motion-item elements), checked during the parser.items validation phase at lines 102-122.

How do I verify all motion diagrams in the repository at once?

Run the verification script with the --shipped flag: python -m scripts.verify-motion.py --shipped. This command validates every named template in skills/diagram-design/assets/ plus any HTML files containing motion attributes, making it suitable for CI/CD pipelines to enforce the motion controller contract across the entire cathrynlavery/diagram-design codebase.

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 →