@1agh/maude 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/package.json +9 -9
  2. package/plugins/design/dev-server/annotations-context-toolbar.tsx +112 -38
  3. package/plugins/design/dev-server/annotations-layer.tsx +204 -95
  4. package/plugins/design/dev-server/artboard-marquee.tsx +8 -4
  5. package/plugins/design/dev-server/bin/asset-sweep.sh +180 -0
  6. package/plugins/design/dev-server/bin/visual-sanity.sh +221 -0
  7. package/plugins/design/dev-server/canvas-lib.tsx +506 -30
  8. package/plugins/design/dev-server/canvas-shell.tsx +352 -20
  9. package/plugins/design/dev-server/client/hmr.mjs +28 -0
  10. package/plugins/design/dev-server/commands/annotation-strokes-command.ts +127 -0
  11. package/plugins/design/dev-server/commands/equal-spacing-command.ts +28 -0
  12. package/plugins/design/dev-server/commands/move-artboards-command.ts +158 -0
  13. package/plugins/design/dev-server/comments-overlay.tsx +12 -4
  14. package/plugins/design/dev-server/contextual-toolbar.tsx +241 -0
  15. package/plugins/design/dev-server/dist/client.bundle.js +3 -3
  16. package/plugins/design/dev-server/dist/runtime/motion.js +165 -0
  17. package/plugins/design/dev-server/dist/runtime/motion_react.js +409 -0
  18. package/plugins/design/dev-server/equal-spacing-detector.ts +98 -0
  19. package/plugins/design/dev-server/equal-spacing-handles.tsx +289 -0
  20. package/plugins/design/dev-server/handoff.ts +24 -0
  21. package/plugins/design/dev-server/http.ts +27 -0
  22. package/plugins/design/dev-server/input-router.tsx +52 -2
  23. package/plugins/design/dev-server/marquee-overlay.tsx +282 -0
  24. package/plugins/design/dev-server/runtime-bundle.ts +7 -0
  25. package/plugins/design/dev-server/test/annotation-strokes-command.test.ts +149 -0
  26. package/plugins/design/dev-server/test/annotations-layer.test.ts +44 -0
  27. package/plugins/design/dev-server/test/canvas-lib-motion.test.ts +131 -0
  28. package/plugins/design/dev-server/test/equal-spacing-detector.test.ts +93 -0
  29. package/plugins/design/dev-server/test/input-router.test.ts +87 -1
  30. package/plugins/design/dev-server/test/marquee-overlay.test.ts +94 -0
  31. package/plugins/design/dev-server/test/move-artboards-command.test.ts +108 -0
  32. package/plugins/design/dev-server/test/undo-stack.test.ts +211 -0
  33. package/plugins/design/dev-server/test/use-cursor-modifiers.test.ts +59 -0
  34. package/plugins/design/dev-server/test/use-keyboard-discipline.test.ts +27 -0
  35. package/plugins/design/dev-server/test/use-undo-stack.test.tsx +193 -0
  36. package/plugins/design/dev-server/undo-hud.tsx +95 -0
  37. package/plugins/design/dev-server/undo-stack.ts +240 -0
  38. package/plugins/design/dev-server/use-artboard-drag.tsx +6 -3
  39. package/plugins/design/dev-server/use-cursor-modifiers.tsx +122 -0
  40. package/plugins/design/dev-server/use-keyboard-discipline.tsx +125 -0
  41. package/plugins/design/dev-server/use-undo-stack.tsx +355 -0
  42. package/plugins/design/templates/_shell.html +17 -6
  43. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +245 -0
  44. package/plugins/design/templates/design-system-inspiration/_MAPPING.md +2 -2
  45. package/plugins/design/templates/design-system-inspiration/core/preview/_components.css.tpl +129 -0
  46. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +63 -0
  47. package/plugins/design/templates/design-system-inspiration/core/preview/motion.css.tpl +106 -0
  48. package/plugins/design/templates/design-system-inspiration/core/preview/motion.tsx.tpl +208 -0
  49. /package/plugins/design/templates/design-system-inspiration/core/preview/{motion.html → .archive/motion.html} +0 -0
@@ -0,0 +1,245 @@
1
+ # Sub-agent prompt templates — `/design:setup-ds` Batch B + C
2
+
3
+ > **Why this file exists.** Per Phase 3.7 Task 4 (and DDR-049), the sub-agent prompt templates that drive Batch B + C fan-out used to live inline in `plugins/design/skills/design-system/SKILL.md` (lines 662-797 pre-extraction). SKILL.md is 1000+ LOC; the prompts were buried. Extracting them here lets us audit + extend the prompts (and especially the three MANDATORY safety blocks below) without scrolling through unrelated bootstrap logic. The skill's "Batches B + C" step loads + interpolates from this file instead of inlining the template.
4
+ >
5
+ > **Sync rule** — when this file changes, the SKILL.md reference must still resolve. The Phase 3.7 risk register mandates a CI grep check: `SKILL.md` must literally contain the string `SUB-AGENT-PROMPTS.md`. Don't rename the file without updating the skill.
6
+
7
+ The prompts are grouped by **slice** (matches the roster's `fanout:` block). Each section is a self-contained sub-agent brief — copy verbatim, substitute placeholders.
8
+
9
+ The three MANDATORY safety blocks (ANIMATION SAFETY, RELATIVE-URL SAFETY, PLACEHOLDER POLICY) appear once at the top because they apply to every slice; sub-agent prompts reference them by name (`Apply ANIMATION SAFETY below`) instead of inlining.
10
+
11
+ ---
12
+
13
+ ## MANDATORY SAFETY BLOCKS (apply to every slice; cited by name in prompts)
14
+
15
+ ### ANIMATION SAFETY (mandatory — applies to motion specimen + any canvas with @keyframes / transitions)
16
+
17
+ - **Bounded geometry.** Every tile that hosts a rotating / scaling animation MUST have `overflow: hidden`. Otherwise the bounding box extends ~√2× at 45°/135° rotations and overflows adjacent rows. **The motion specimen tiles all set `overflow: hidden` explicitly** — codify this with a CSS comment so future agents reading the file see the rationale.
18
+ - **Sparkle / pulse / twinkle = small only.** Keyframes that scale from `0 → 1 → 0` (or similar "appear-and-disappear" patterns) are for elements ≤56px. **Never apply to full-width tiles.** Demo sparkle on a 32×32 chip inside a tile, never on the tile itself. The studyfi imprint retro D-3 caught a sparkle-on-full-tile that exploded the whole row.
19
+ - **Loop motion = `infinite alternate`.** Specimens meant for continuous display use `animation: <kf> <dur> infinite alternate <easing>`. Single-shot animations finish in 150-200ms and leave the demo invisible on the second look — "looks dead until you hover" is the regression mode.
20
+ - **Compositor-only.** Animate `transform` + `opacity` only. Never `width`, `height`, `top`, `left`, `padding`, `margin`. Layout-dirty animations break adjacent geometry AND cost paint cycles.
21
+ - **Reduced motion is mandatory.** Tokens already collapse `--dur-*` to `1ms` when `@media (prefers-reduced-motion: reduce)` is set. Do NOT add `!important` overrides outside the motion specimen itself (the specimen has a `<ReducedMotionToggle>` chrome that reviewers use to flip both branches without OS settings — that's the only documented exception). If using `motion/react`, use `useReducedMotion()` and short-circuit the `animate` prop.
22
+ - **No bouncy springs by default.** Springs say "Toy", not "Pro Tool". Use `spring` role only when the DS brief explicitly asks for it (e.g. brief mentions "playful" / "tactile" / "physical" / "bouncy"). Default to `tween` with the standard easing tokens.
23
+
24
+ ### RELATIVE-URL SAFETY (mandatory — applies to any specimen referencing assets, logos, glyphs)
25
+
26
+ - The dev-server serves canvases via `/_canvas-shell.html?canvas=<rel-path>`. **Relative URLs (`../foo.svg`, `./assets/logo.svg`) resolve against the SHELL's URL, not the canvas file's location.** Result: 404 / broken-image icon. Studyfi imprint retro D-4 was caught by the user mid-flow because of exactly this.
27
+ - **Always inline SVGs** in JSX. Use `useId()` for any `<filter id>` / `<linearGradient id>` so multiple instances of the same component don't collide on a single page. OR
28
+ - **Always use absolute paths rooted at `/assets/...`** served from the dev-server's static mount. The `system/<ds>/assets/` directory is mounted at `/assets/<ds>/` in production-handoff mode.
29
+ - **Forbidden:** `<img src="../foo.svg">`, `<image href="./logo.svg">`, `url('../bg.png')` inside inline `<style>` blocks. Any of these surviving in a specimen is a graphic-design-critic blocker.
30
+
31
+ ### PLACEHOLDER POLICY (mandatory — applies to logo / mark / wordmark / mascot / glyph / illustration claims)
32
+
33
+ - Before writing a placeholder SVG, the orchestrator MUST have run the pre-scaffold real-asset sweep (`bin/asset-sweep.sh`). The sweep populates the roster's `assets:` block with `source: <production path>` for every noun where a real asset exists. **Sub-agents read the roster** — if `source:` is populated for a noun, COPY the asset; do NOT invent a placeholder.
34
+ - If the sweep returned zero candidates for a noun, only THEN may a placeholder be authored. **The placeholder file's name MUST contain `-placeholder`** (e.g. `logo-placeholder.svg`, `mascot-placeholder.svg`) so it's visually obvious in `ls` + `grep` and downstream sub-agents know not to promote it.
35
+ - **Never assume your own placeholder path is authoritative downstream.** When the roster row's `source:` is empty AND you wrote a placeholder, set `source: placeholder` in your roster update (not `source: written`). The brand-critic and the next bootstrap pass both look for `source: placeholder` rows as "consider revisiting later".
36
+ - **Forbidden:** authoring an `S`-shaped wordmark for a product whose first letter isn't S, authoring a "hedgehog mascot" SVG because the README copy mentioned hedgehog energy. The studyfi imprint retro D-2 caught both patterns. If the production-asset sweep returned nothing AND the discovery brief didn't explicitly request a mascot, **rewrite the copy** instead of inventing the artifact.
37
+
38
+ ---
39
+
40
+ ## Sub-agent prompt template (slice-agnostic — used by every Batch B + C slice)
41
+
42
+ ```
43
+ You are scaffolding part of a design system. Write {{N}} specimen TSX files in
44
+ parallel with other sub-agents. Each file lands at the absolute path listed.
45
+
46
+ SHAPE — every specimen TSX is BARE — NO canvas-lib envelope. The shape:
47
+
48
+ /** JSDoc header (see template) */
49
+ import "./<slug>.css"; /* per-specimen bespoke CSS — sibling file */
50
+
51
+ export default function <PascalName>() {
52
+ return (
53
+ <>
54
+ <header className="specimen-hd">
55
+ <span className="sku">MDCC-DSN/01.<slug></span>
56
+ <span className="crumbs">...</span>
57
+ <span className="theme-toggle" ...>...</span>
58
+ </header>
59
+ <main className="specimen">
60
+ <section className="specimen-title">
61
+ <h1>...</h1>
62
+ <p className="lede">...</p>
63
+ </section>
64
+ {/* sections, h2 dividers, tables, etc. */}
65
+ </main>
66
+ </>
67
+ );
68
+ }
69
+
70
+ Specimens are FLOWING reference pages, not viewport mocks. Do NOT wrap in
71
+ `<DesignCanvas><DCSection><DCArtboard>` — `_layout.css` styles `<body>` as
72
+ a flex column directly. The original .html-era specimens had exactly this
73
+ shape; we keep it one-for-one in TSX.
74
+
75
+ Canvas-lib (`@maude/canvas-lib`) is reserved for UI mock canvases with
76
+ multiple fixed-px artboards (Docs Site, Canvas Viewport). Specimens do NOT
77
+ import it. Token/theme hooks (`useTokens`, `useTheme`) ARE available if a
78
+ specimen genuinely needs them — but bare CSS via `var(--*)` is the default.
79
+
80
+ **Motion specimens are the documented exception.** The motion specimen
81
+ (when scaffolded) imports motion helpers from `@maude/canvas-lib`:
82
+ `<MotionDemo role>`, `<MotionTrack>`, `<TokenPlayback>`, `<ReducedMotionToggle>`,
83
+ `useMotionTokens()`. Other specimens stay bare. See ANIMATION SAFETY above.
84
+
85
+ Per-specimen bespoke CSS lives in a sibling `<slug>.css`. The dev-server's
86
+ canvas-build.ts collects this and injects it as a `<style>` at module init
87
+ so the drop is self-contained.
88
+
89
+ PROJECT
90
+ - name: {{project_label}}
91
+ - DS slug: {{ds_dirname}}
92
+ - one-liner: {{Q1}}
93
+ - audience: {{Q2}} · platforms: {{Q3}} · theme default: {{Q4}}
94
+ - voice: {{Q8}} ("{{voice_one_line_summary}}")
95
+ - signature treatment: {{Q9}} ({{Q9_one_line_summary}})
96
+ - hard NOs: {{Q10 csv}}
97
+ - iconography vibe: {{Q11}}
98
+ - density: {{Q12}}
99
+
100
+ REFERENCES (verbatim — read first, do not skim)
101
+ - TOKENS (authoritative): {{absolute path to colors_and_type.css}}
102
+ - CHROME (signature): {{absolute path to _layout.css}}
103
+ - COMPONENTS (when emitted): {{absolute path to _components.css OR "(not emitted — inline styles only)"}}
104
+ - DS README (hard rules): {{absolute path to system/<ds>/README.md}}
105
+ - _MAPPING.md gating rules: {{absolute path to _MAPPING.md}}
106
+ - inspiration library root: {{absolute path to plugins/design/templates/design-system-inspiration/}}
107
+ - ROSTER (your slice + assets): {{absolute path to _scaffold-roster.yaml}}
108
+ {{if peer_DS_references_attached:}}
109
+ - PEER DS gold-standard: {{absolute paths to system/<peer-ds>/preview/<file>.tsx}}
110
+ {{endif}}
111
+
112
+ DOMAIN_NOUNS (authoritative — every specimen MUST use only these nouns)
113
+ {{domain_nouns}} # the 5–10 nouns from discovery payload's domain_nouns field — project-native vocabulary the specimen must use
114
+ {{if peer_DS_references_attached:}}
115
+ DOMAIN NOUN PURGE — peer DS references are COMPOSITION REFERENCES ONLY. The peer
116
+ DS uses different domain nouns (a peer DS built for a different product domain
117
+ will have nouns specific to that domain). When restructuring from a peer reference,
118
+ search-and-replace EVERY peer-DS noun in your output with a noun from this DS's
119
+ DOMAIN_NOUNS list. A single peer-DS noun surviving in your output is a copy-critic
120
+ blocker — leaked domain nouns from a peer reference (e.g. "publish <peer-noun>"
121
+ surviving into this DS's specimen) is the regression mode this purge step prevents.
122
+ {{endif}}
123
+
124
+ YOUR SLICE — write these {{N}} files (absolute paths):
125
+ 1. {{abs path 1}} — reference template: {{abs path to inspiration template 1}}
126
+ 2. {{abs path 2}} — reference template: {{abs path to inspiration template 2}}
127
+ … (etc.)
128
+
129
+ SAFETY BLOCKS — apply to every file in your slice:
130
+ - **ANIMATION SAFETY** (see SUB-AGENT-PROMPTS.md). Mandatory if your slice
131
+ includes the `motion` specimen OR any file that uses `@keyframes` /
132
+ `transition` / `animate`. Bounded geometry + sparkle-≤56px + infinite-alternate
133
+ + compositor-only + reduced-motion + no-bouncy-springs.
134
+ - **RELATIVE-URL SAFETY** (see SUB-AGENT-PROMPTS.md). Mandatory if your slice
135
+ references ANY asset (logo, glyph, illustration, background image). Inline
136
+ SVG with `useId()`, OR absolute `/assets/<ds>/...` path. NEVER relative.
137
+ - **PLACEHOLDER POLICY** (see SUB-AGENT-PROMPTS.md). Mandatory if your slice
138
+ scaffolds the `logo` / `empty-state` specimen or any brand-asset surface.
139
+ Read the roster `assets:` block FIRST. If `source:` is populated, copy the
140
+ real asset; do NOT invent. If `source:` is empty AND the noun is genuinely
141
+ warranted, author a `-placeholder` SVG and set `source: placeholder` in your
142
+ roster update.
143
+
144
+ CREATIVITY RUBRIC — do NOT token-swap. RESTRUCTURE.
145
+ - Read the reference template. Understand the SPECIMEN comment header (what it
146
+ demonstrates). Then write a project-flavored equivalent of 1.5×–6× the reference
147
+ LOC. Same demonstration intent, ORIGINAL composition, project's voice, project's
148
+ domain nouns.
149
+ - Every specimen earns at least ONE compositional choice that's not in the
150
+ template. Examples that landed well in the studio 2026-05-13 re-bootstrap (use
151
+ these as gold-standard creativity moves):
152
+ - colors-accent.tsx (48 LOC template → ~5× LOC): added a brand-spotlight
153
+ hero with masked gradient border, a "wrong" anti-pattern teaching device,
154
+ chroma annotations on swatches.
155
+ - empty-state.tsx (~6× LOC): added a "Voice — keep or kill" panel
156
+ comparing good vs corporate copy side by side; added a variants grid for
157
+ multiple empty-state cases.
158
+ - ui_kits-<platform>-showcase.tsx (~3× LOC): replaced the template's
159
+ generic mock screens with project-specific reality from the discovery
160
+ payload's domain_nouns; added a live-presence layer + a token-row
161
+ inspector panel where the brief warranted it.
162
+ Anti-example: a components-buttons.tsx at 1.3× the template LOC that kept
163
+ the template's fake-state grid almost verbatim. **Don't do this.** Add
164
+ icon-only + kbd-hint variants, push hierarchy contrast, surface the signature
165
+ treatment on the primary's hover state.
166
+ - Copy is project-specific. NO "Lorem", NO "Acme Corp.", NO "Get Started". Use
167
+ the project's actual nouns ({{domain_nouns}}). Match the voice — {{Q8 voice}}.
168
+ - Tokens only. No hardcoded hex / px / rem. If you reach for an off-ladder
169
+ value, STOP and use the nearest `var(--*)` token. The typography-critic
170
+ catches off-ladder px and will return a blocker.
171
+ - Hard NOs from Q10 are guardrails. {{if "no animations" in hard_nos: "Do not add hover/active transitions; static states only."}} {{if "no gradients" in hard_nos: "No linear-gradient / radial-gradient anywhere."}} (etc.)
172
+ - Carry the SPECIMEN comment header from the reference template at the top of
173
+ your output so future agents can identify what each file demonstrates.
174
+
175
+ ANTI-PATTERNS (graphic-design-critic blockers — guaranteed rejection)
176
+ - Multiplayer cursors / annotation pins / presence avatars MUST anchor to
177
+ SPECIFIC content (a toolbar button, a list row, a panel, a swatch). Pins
178
+ floating over canvas void are a fail mode. If you don't have content for a
179
+ pin to anchor, REDUCE the pin count to match available anchor count. (Caught
180
+ on studio-2 showcase, retro BAD-8.)
181
+ - Cropmarks / signature framing devices must be visually thicker than internal
182
+ panel borders, otherwise they read as "another panel" not "this is the
183
+ staging frame".
184
+ - Sections in long specimens (≥ 4 sections) must vary in vertical weight — the
185
+ hero / signature section earns 25–30%, not equal share with policy sections.
186
+
187
+ WHEN DONE
188
+ After writing all {{N}} files:
189
+ 1. Update each row in {{absolute path to _scaffold-roster.yaml}}: change
190
+ `status: pending` → `status: written, loc: <line-count>`.
191
+ 2. **Do NOT add new rows.** If you discover a missing claim (e.g. wordmark
192
+ referenced but no logo.tsx in roster), include "ROSTER GAP: <description>"
193
+ in your completion message. The main agent reconciles new rows.
194
+ 3. Return a one-line confirmation per file with LOC.
195
+ ```
196
+
197
+ Sub-agents are stateless — give each a complete brief, do not assume shared context. Three-line confirmation back is enough; the roster is the source of truth.
198
+
199
+ ---
200
+
201
+ ## Per-slice prompt addenda
202
+
203
+ The base template above applies to every slice. Each slice gets a small addendum to steer the sub-agent toward the slice-specific anti-patterns and the safety blocks that apply.
204
+
205
+ ### Slice: `foundations` (color tokens, type + spacing, motion, radii/elevation/focus, iconography/borders/grid)
206
+
207
+ **Apply ANIMATION SAFETY in full.** The `motion` specimen is the highest-friction file in this slice — D-3 from the imprint retro landed here. Specifically:
208
+ - Motion specimen MUST use `<MotionDemo>` from `@maude/canvas-lib`, NOT inline `@keyframes` literals (DDR-049 enforces the canonical library).
209
+ - 4 duration tokens (`--dur-flip`, `--dur-panel`, `--dur-route`, `--dur-soft`) MUST each be referenced ≥ 1× by the motion specimen — completeness-critic asserts coverage post-scaffold.
210
+ - The specimen MUST animate on first paint. Hover-driven CSS transitions alone fail the "looks dead at rest" smell test.
211
+
212
+ **Apply RELATIVE-URL SAFETY in full.** The `iconography` specimen tends to reference glyph SVGs; the `focus` specimen sometimes references focus-ring fixtures. Inline ALL SVGs.
213
+
214
+ ### Slice: `brand + voice` (empty-state, logo)
215
+
216
+ **Apply PLACEHOLDER POLICY in full.** This slice is the highest-risk for placeholder-bleed. The `logo` specimen MUST start by reading the roster's `assets:` block:
217
+ - If `assets.logo.source` is a real path, the specimen `<img>`s / inlines the real asset.
218
+ - If `assets.logo.source` is empty, fall back to a `-placeholder` SVG with the rationale in the JSX comment block. Set `source: placeholder` on roster update.
219
+
220
+ **Apply RELATIVE-URL SAFETY in full.** Logo specimens are 100% asset-bound. Inline SVG is the default; absolute `/assets/<ds>/logo.svg` is the alternative.
221
+
222
+ ### Slice: `core components` (components-buttons, components-cards, components-inputs)
223
+
224
+ Buttons frequently get hover transitions — apply ANIMATION SAFETY (compositor-only). Cards frequently get a hover lift — same constraint, transform-only.
225
+
226
+ ### Slice: `universal` / `audience-*` / `platform-*`
227
+
228
+ No slice-specific addenda — the base template covers them.
229
+
230
+ ### Slice: `ui_kits-*-showcase` (main agent OR signature sub-agent)
231
+
232
+ **Apply ANIMATION SAFETY.** Showcase is the highest-density "DS in use" artifact; presence cursors, route transitions, and panel slides all show up here. Every animation must be bounded + reduced-motion-safe + token-derived.
233
+
234
+ **Apply RELATIVE-URL SAFETY.** Showcase often references domain product imagery. Inline SVG mocks, never `<img src="../assets/...">`.
235
+
236
+ ---
237
+
238
+ ## Cross-links
239
+
240
+ - Owner skill: `plugins/design/skills/design-system/SKILL.md` (BOOTSTRAP flow → "Batches B + C — parallel fan-out via sub-agents" section loads this file)
241
+ - Discovery probe templates (sibling): `plugins/design/skills/design-system/_pastier-probe-templates.md`
242
+ - Motion library decision: `.ai/decisions/DDR-049-motion-one-as-canonical-motion-library.md`
243
+ - Imprint-bootstrap retro (source of D-1 … D-5): `.ai/logs/system-reviews/imprint-bootstrap-review-2026-05-26.md`
244
+ - Asset sweep helper: `plugins/design/dev-server/bin/asset-sweep.sh`
245
+ - Visual sanity helper: `plugins/design/dev-server/bin/visual-sanity.sh`
@@ -37,7 +37,7 @@ Every project — regardless of discovery — gets:
37
37
  - `core/preview/colors-{text,surfaces,accent}.html` → 3 token specimens
38
38
  - `core/preview/type-scale.html` → 1 typography specimen
39
39
  - `core/preview/spacing-scale.html` → 1 spacing specimen
40
- - `core/preview/motion.html` → 1 motion specimen
40
+ - `core/preview/motion.tsx.tpl` + `motion.css.tpl` + `_motion-readme.md.tpl` → 1 motion specimen (Phase 3.7 / DDR-049 — TSX playground with 8 role tiles via `<MotionDemo>` from `@maude/canvas-lib`; legacy `.html` archived to `.archive/`)
41
41
  - `core/preview/components-{buttons,cards,inputs}.html` → 3 component specimens
42
42
 
43
43
  **Core minimum: 10 preview specimens (excluding `.tpl` docs and `_layout.css`).**
@@ -248,7 +248,7 @@ The skill batches scaffold writes by dependency closure so independent files can
248
248
  | Dir | `dependency_closure` |
249
249
  |---|---|
250
250
  | `core/colors_and_type.css.tpl`, `core/SKILL.md.tpl`, `core/README.*.tpl`, `core/config.json.tpl`, `core/preview/_layout.css` | `root` |
251
- | `core/preview/colors-{text,surfaces,accent}.html`, `type-scale.html`, `spacing-scale.html`, `motion.html` | `tokens-only` |
251
+ | `core/preview/colors-{text,surfaces,accent}.html`, `type-scale.html`, `spacing-scale.html`, `motion.tsx.tpl` + `motion.css.tpl` | `tokens-only` |
252
252
  | `foundations/*.html` | `tokens-only` |
253
253
  | `core/preview/components-{buttons,cards,inputs}.html` | `tokens + chrome + template` |
254
254
  | `universal/*.html`, `universal/empty-state.html.tpl`, `universal/logo.html` | `tokens + chrome + template` |
@@ -0,0 +1,129 @@
1
+ /* _components.css — shared component anatomy (Phase 3.6.1 emit-conditionally).
2
+ *
3
+ * This file is OPTIONAL. The bootstrap scaffolder emits it when Q9 signature
4
+ * treatment ≠ `none` AND the treatment repeats across 3+ components — at that
5
+ * point promoting class anatomy out of per-specimen <style> blocks reduces
6
+ * drift and makes critic-panel scans cleaner.
7
+ *
8
+ * Project-specific component classes (.btn / .tile / .input / etc.) are
9
+ * authored by the scaffolder for the specific Q9 family. The MOTION ROLE
10
+ * CLASSES below are the universally-shared piece — they ship verbatim
11
+ * regardless of Q9 family, because the motion vocabulary is fixed at 8 roles
12
+ * per DDR-049.
13
+ */
14
+
15
+ /* ─────────────────────────────────────────────────────────────────────── */
16
+ /* Motion role classes — CSS-only escape hatch for the canvas-lib motion
17
+ * vocabulary (Phase 3.7 / DDR-049). Use when a canvas wants the role
18
+ * vocabulary without importing motion/react. The shape MUST stay in sync
19
+ * with `<MotionDemo role>` in canvas-lib.tsx.
20
+ *
21
+ * Usage: <div className="motion-flip">…</div>
22
+ * <div className="motion-soft">…</div>
23
+ *
24
+ * Each class is `animation: <kf> <dur> <ease> infinite alternate` so the
25
+ * element loops on first paint (matching the canvas-lib helper's default
26
+ * loop="always"). To opt out of the loop, set `animation-iteration-count: 1`
27
+ * on the host or use `animation-play-state: paused`.
28
+ *
29
+ * Reduced-motion is enforced by the colors_and_type.css token collapse
30
+ * (--dur-*: 1ms under prefers-reduced-motion: reduce) — these classes
31
+ * inherit that for free.
32
+ */
33
+
34
+ /* flip — press-down, hover lift. */
35
+ @keyframes motion-flip-kf {
36
+ 0% { transform: translateY(0); }
37
+ 100% { transform: translateY(-12px); }
38
+ }
39
+ .motion-flip {
40
+ animation: motion-flip-kf var(--dur-flip, 220ms) var(--ease-out, ease-out) infinite alternate;
41
+ overflow: hidden;
42
+ }
43
+
44
+ /* panel — drawer / sidebar slide. */
45
+ @keyframes motion-panel-kf {
46
+ 0% { transform: translateX(-80px); }
47
+ 100% { transform: translateX(0); }
48
+ }
49
+ .motion-panel {
50
+ animation: motion-panel-kf var(--dur-panel, 320ms) var(--ease-in-out, ease-in-out) infinite alternate;
51
+ overflow: hidden;
52
+ }
53
+
54
+ /* route — page transition (opacity + tiny scale). */
55
+ @keyframes motion-route-kf {
56
+ 0% { opacity: 0; transform: scale(0.92); }
57
+ 100% { opacity: 1; transform: scale(1); }
58
+ }
59
+ .motion-route {
60
+ animation: motion-route-kf var(--dur-route, 480ms) var(--ease-out, ease-out) infinite alternate;
61
+ overflow: hidden;
62
+ }
63
+
64
+ /* soft — toast / tooltip / soft reveal. */
65
+ @keyframes motion-soft-kf {
66
+ 0% { opacity: 0; }
67
+ 100% { opacity: 1; }
68
+ }
69
+ .motion-soft {
70
+ animation: motion-soft-kf var(--dur-soft, 160ms) var(--ease-out, ease-out) infinite alternate;
71
+ }
72
+
73
+ /* spring — tactile/playful settle. CSS spring approximation only — for
74
+ * production spring fidelity, lift <MotionDemo role="spring" /> instead. */
75
+ @keyframes motion-spring-kf {
76
+ 0% { transform: translateY(0); }
77
+ 60% { transform: translateY(-20px); }
78
+ 78% { transform: translateY(-12px); }
79
+ 90% { transform: translateY(-16px); }
80
+ 100% { transform: translateY(-14px); }
81
+ }
82
+ .motion-spring {
83
+ animation: motion-spring-kf var(--dur-panel, 320ms) cubic-bezier(0.34, 1.56, 0.64, 1) infinite alternate;
84
+ overflow: hidden;
85
+ }
86
+
87
+ /* scroll — scroll-progress-bound entry. CSS-only fallback; real scroll-link
88
+ * needs JS (motion/react useScroll). */
89
+ @keyframes motion-scroll-kf {
90
+ 0% { transform: translateX(0); }
91
+ 100% { transform: translateX(24px); }
92
+ }
93
+ .motion-scroll {
94
+ animation: motion-scroll-kf var(--dur-route, 480ms) var(--ease-in-out, ease-in-out) infinite alternate;
95
+ overflow: hidden;
96
+ }
97
+
98
+ /* drag — pick-up + release rotational settle. CSS-only fallback. */
99
+ @keyframes motion-drag-kf {
100
+ 0% { transform: rotate(0); }
101
+ 50% { transform: rotate(4deg); }
102
+ 100% { transform: rotate(0); }
103
+ }
104
+ .motion-drag {
105
+ animation: motion-drag-kf var(--dur-flip, 220ms) var(--ease-out, ease-out) infinite alternate;
106
+ overflow: hidden;
107
+ }
108
+
109
+ /* presence — sparkle / pulse. Demo on ≤56px elements ONLY (bounded geometry —
110
+ * a full-width tile pulsing scale: 0→1 will overflow adjacent rows). */
111
+ @keyframes motion-presence-kf {
112
+ 0% { opacity: 0; transform: scale(0.9); }
113
+ 100% { opacity: 1; transform: scale(1); }
114
+ }
115
+ .motion-presence {
116
+ animation: motion-presence-kf var(--dur-soft, 160ms) var(--ease-out, ease-out) infinite alternate;
117
+ overflow: hidden;
118
+ /* Bounded-geometry guard — the class is intended for elements ≤56px.
119
+ * Sites that violate this earn a motion-critic blocker. */
120
+ max-width: 56px;
121
+ max-height: 56px;
122
+ }
123
+
124
+ /* Honor explicit data-reduced-motion toggle (specimen chrome) AND the
125
+ * standing OS-level prefers-reduced-motion (the token collapse already
126
+ * neutralises the durations; the JS-set attribute is for inspection symmetry). */
127
+ :root[data-reduced-motion="true"] [class*="motion-"] {
128
+ animation: none !important;
129
+ }
@@ -0,0 +1,63 @@
1
+ # Motion specimen — author notes
2
+
3
+ > A cold-reader's guide to `motion.tsx` + `motion.css`. Lives alongside the specimen so a future Claude opening this folder can answer "why is this file shaped this way?" without grepping the plan archive.
4
+
5
+ ## The 8 roles
6
+
7
+ The canvas-lib `<MotionDemo role>` accepts a fixed 8-value union. Each role binds to a DS duration + easing token from `colors_and_type.css`:
8
+
9
+ | Role | Token | Easing | Use when |
10
+ | --- | --- | --- | --- |
11
+ | `flip` | `--dur-flip` (~220 ms) | `--ease-out` | Press-down, hover lift, single-card flip. Smallest, snappiest. |
12
+ | `panel` | `--dur-panel` (~320 ms) | `--ease-in-out` | Drawer, sidebar, segmented control transitions. |
13
+ | `route` | `--dur-route` (~480 ms) | `--ease-out` | Page / route transitions. Opacity + tiny scale. |
14
+ | `soft` | `--dur-soft` (~160 ms) | `--ease-out` | Toast, tooltip, soft fades. Fastest. |
15
+ | `spring` | `--dur-panel` | `spring` | **Use only when the brief asks for tactile / playful / bouncy.** Springs say "Toy" by default. |
16
+ | `scroll` | `--dur-route` | `--ease-in-out` | Scroll-progress-bound entries (parallax hint, on-scroll reveals). |
17
+ | `drag` | `--dur-flip` | `--ease-out` | Pick-up + release rotational settle for draggables. |
18
+ | `presence` | `--dur-soft` | `--ease-out` | The sparkle / pulse / twinkle. **Demo on 32×32 chips only.** Setting `small={true}` enforces the bounded size. |
19
+
20
+ ## The loop policy
21
+
22
+ Every role defaults to `loop="always"` with `repeatType: 'reverse'`. **Initial paint = full motion vocabulary playing.** No hover required.
23
+
24
+ Why: the studyfi imprint retro caught a hover-driven specimen that looked dead at rest. Reviewers (rightly) assumed the DS had no motion. The fix isn't more documentation; it's making the specimen self-evident at first paint.
25
+
26
+ `loop="hover"` is reserved for canvases that mock real product chrome where a constant loop would be visually noisy. `loop="once"` is for one-shot reveals (rare in specimens; common in real components).
27
+
28
+ ## The bounded-geometry rule
29
+
30
+ Every `<MotionDemo>` root has `overflow: hidden` set inline by the canvas-lib component itself. This is non-negotiable and intentionally hard to bypass.
31
+
32
+ The reason: a 32×32 chip rotating 45° has a bounding box of ~45×45. A tile rotating to 45° has a bounding box of ~√2× its width and overflows adjacent rows. The studyfi imprint retro D-3 was caused by a sparkle keyframe (`scale: 0 → 1 → 0`) applied to a full-width tile — the chip itself was fine, but the chrome's `overflow: visible` let the scale ripple out of the row.
33
+
34
+ If a canvas legitimately needs visible overflow (e.g. an asymmetric brand mark that bleeds intentionally), it composes outside `<MotionDemo>` — not by removing the clip.
35
+
36
+ ## The reduced-motion contract
37
+
38
+ Two layers:
39
+
40
+ 1. **CSS** — `colors_and_type.css` collapses every `--dur-*` to `1ms` under `@media (prefers-reduced-motion: reduce)`. This is the canonical layer; any canvas-lib helper that reads the live token value inherits the collapse for free.
41
+ 2. **JS** — `<MotionDemo>` calls `useReducedMotion()` from `motion/react` and short-circuits the `animate` prop. Belt-and-suspenders for a hard a11y invariant; the cost is one boolean check per render.
42
+
43
+ The motion specimen is the documented exception. `<ReducedMotionToggle>` flips `data-reduced-motion="true"` on `<html>` so reviewers can eyeball both branches without OS settings. **No other specimen ships this toggle.**
44
+
45
+ ## The handoff path
46
+
47
+ Per DDR-049: a canvas that imports `<MotionDemo>` or any sibling motion helper from `@maude/canvas-lib` gets `"motion": "^11"` declared in its `registry-item.json` dependencies block at `/design:handoff` time. The motion primitives are inlined as `type: "registry:component"` entries in `files[]`; the consumer's TSX imports them from a relative path. No `@maude/canvas-lib` reference survives.
48
+
49
+ `bunx shadcn add file:///path/to/canvas.registry.json` in a scratch Next.js project pulls `motion` from npm, drops the primitives inline, and the canvas animates with zero manual wiring.
50
+
51
+ ## Critics that watch this specimen
52
+
53
+ - **`motion-critic`** — auto-routed when `motion.tsx` exists, regardless of `--opt-out=motion` scope. Validates: bounded geometry, compositor-only properties, loop policy (`infinite alternate` for "always" roles), reduced-motion handling, role-vocabulary fidelity.
54
+ - **`design-system-completeness-critic`** — adds two Phase 3.7 checks: (1) motion specimen renders without console errors; (2) every `--dur-*` token defined in `colors_and_type.css` is referenced at least once by the motion specimen (the "orphan token" check).
55
+ - **`design-system-keeper`** — when auditing a non-specimen canvas, greps for `@keyframes` literals that match a canvas-lib role; warns "pattern reinvention — lift `<MotionDemo role='…'>` instead". ≥3 warnings on a single canvas promotes to blocker.
56
+
57
+ ## Cross-links
58
+
59
+ - Library source: `plugins/design/dev-server/canvas-lib.tsx` (`MotionDemo`, `MotionTrack`, `TokenPlayback`, `ReducedMotionToggle`, `useMotionTokens`, `easingFromToken`)
60
+ - Library decision: `.ai/decisions/DDR-049-motion-one-as-canonical-motion-library.md`
61
+ - Sub-agent safety block: `plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md` → ANIMATION SAFETY
62
+ - Tokens: `colors_and_type.css.tpl` (lines around `--dur-*` + `--ease-*`)
63
+ - CSS escape hatch: `_components.css.tpl` (`.motion-flip`, `.motion-panel`, ... `.motion-presence`)
@@ -0,0 +1,106 @@
1
+ /* motion.css — per-specimen CSS for motion.tsx.
2
+ *
3
+ * Scoped to the motion specimen. Tile chrome + curve layout only — the
4
+ * actual animation keyframes/transitions are owned by canvas-lib's
5
+ * <MotionDemo>. This file MUST NOT redefine motion role behavior.
6
+ *
7
+ * Reduced-motion exception (DDR-049, ANIMATION SAFETY block):
8
+ * This specimen is the documented exception to the @media reduced-motion
9
+ * killswitch in _layout.css. We use !important here because _layout.css's
10
+ * universal `animation: none !important` would kill the <MotionDemo>'s
11
+ * own animate prop. Scoped to .motion-card / .motion-curve only — other
12
+ * surfaces still honor OS-level reduced-motion.
13
+ */
14
+
15
+ .motion-grid {
16
+ display: grid;
17
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
18
+ gap: var(--space-3, 16px);
19
+ }
20
+
21
+ .motion-card {
22
+ background: var(--bg-1);
23
+ padding: var(--space-3, 20px);
24
+ border-radius: var(--radius-md, 6px);
25
+ border: 1px solid var(--border-default, currentColor);
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: var(--space-2, 12px);
29
+ }
30
+
31
+ .motion-card__stage {
32
+ height: 80px;
33
+ background: var(--bg-2, var(--bg-1));
34
+ border-radius: var(--radius-sm, 4px);
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ overflow: hidden;
39
+ }
40
+
41
+ .motion-card__stage .motion-demo {
42
+ width: 100%;
43
+ height: 100%;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ }
48
+
49
+ .motion-card__stage .motion-demo__chip {
50
+ width: 32px;
51
+ height: 32px;
52
+ background: var(--accent, currentColor);
53
+ border-radius: var(--radius-sm, 4px);
54
+ }
55
+
56
+ .motion-card__tokens {
57
+ font-family: var(--font-mono, ui-monospace, monospace);
58
+ font-size: var(--type-sm, 12px);
59
+ color: var(--fg-2, currentColor);
60
+ }
61
+
62
+ .motion-card__note {
63
+ font-size: var(--type-sm, 12px);
64
+ color: var(--fg-1, currentColor);
65
+ }
66
+
67
+ .motion-curves__row {
68
+ display: flex;
69
+ gap: var(--space-3, 16px);
70
+ flex-wrap: wrap;
71
+ align-items: flex-end;
72
+ }
73
+
74
+ .motion-curve {
75
+ margin: 0;
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: var(--space-1, 6px);
79
+ }
80
+
81
+ .motion-curve figcaption {
82
+ font-family: var(--font-mono, ui-monospace, monospace);
83
+ font-size: var(--type-xs, 11px);
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: 2px;
87
+ }
88
+
89
+ .motion-curve__values {
90
+ color: var(--fg-2, currentColor);
91
+ font-variant-numeric: tabular-nums;
92
+ }
93
+
94
+ /* Reduced-motion: scoped specimen exception (see header). */
95
+ @media (prefers-reduced-motion: reduce) {
96
+ .motion-card .motion-demo__target {
97
+ animation: none !important;
98
+ transition: none !important;
99
+ }
100
+ }
101
+
102
+ /* Specimen honors the chrome toggle for inspection symmetry. */
103
+ :root[data-reduced-motion="true"] .motion-card .motion-demo__target {
104
+ animation: none !important;
105
+ transition: none !important;
106
+ }