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-stepattribute containing an ASCII decimal integer (checked inparser.itemsvalidation at lines 102-122) - For semantic items (lacking
data-motion-decorative): a non-emptyaria-label - For decorative items:
aria-hidden="true"andfocusable="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 thedata-diagram-controlsattribute - 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 printblock - 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:
- Parameter parsing: Reads URL parameters (
motion,step) and respects the user'sprefers-reduced-motionsetting - Step management: Determines step count from
data-step-countand builds label lists for each step - Playback controls: Wires DOM elements marked with
data-motion-actionto play, pause, replay, prev, and next functions - Keyboard navigation: Handles shortcuts for arrow keys (←/→), Home/End, Space, and R
- ARIA management: Updates the
data-motion-statuslive region to announce state changes to screen readers - Render stability: Adds the
motion-readyclass 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:
- HTML Parsing: Instantiates
MotionParserto collect roots, items, actions, controls, statuses, scripts, styles, and SVGs - Structural Validation: Verifies root count, mode validity against
MODES, step count bounds (0-8), and item budget (max 12) - Controlled Mode Checks: Ensures unique control groups, presence of all five actions, and required status element for interactive modes
- Script Verification: Confirms single
data-diagram-controlsscript presence and validates canonical controller equivalence usingnormalized_controller() - CSS Contract Enforcement: Checks for
prefers-reduced-motionandprintmedia queries, control hiding rules, and static overrides - SVG Accessibility Audit: Validates
role="img",aria-labelledbyreferences, 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-rootelement and a validdata-motion-modeselected from theMODESset (none,reveal,step,loop) - Interactive modes mandate a
data-motion-controlsgroup containing five specific actions (play,pause,replay,prev,next) and adata-motion-statuslive 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.pyvalidates all constraints using theMotionParserclass 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →