@agent-native/core 0.85.5 → 0.85.7

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 (86) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AssistantChat.tsx +1 -1
  5. package/corpus/core/src/client/use-pinch-zoom.ts +76 -11
  6. package/corpus/core/src/collab/presence.ts +63 -3
  7. package/corpus/templates/clips/desktop/src/lib/recorder.ts +245 -77
  8. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +190 -13
  9. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +16 -4
  10. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +46 -3
  11. package/corpus/templates/design/AGENTS.md +40 -4
  12. package/corpus/templates/design/actions/apply-component-prop-edit.ts +20 -73
  13. package/corpus/templates/design/actions/apply-motion-edit.ts +29 -2
  14. package/corpus/templates/design/actions/apply-visual-edit.ts +4 -2
  15. package/corpus/templates/design/actions/connect-localhost.ts +25 -18
  16. package/corpus/templates/design/actions/edit-design.ts +5 -3
  17. package/corpus/templates/design/actions/export-pdf.ts +8 -1
  18. package/corpus/templates/design/actions/export-zip.ts +10 -1
  19. package/corpus/templates/design/actions/get-motion-timeline.ts +26 -20
  20. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +10 -0
  21. package/corpus/templates/design/actions/insert-asset.ts +190 -7
  22. package/corpus/templates/design/actions/open-visual-edit.ts +27 -5
  23. package/corpus/templates/design/actions/present-design-variants.ts +12 -8
  24. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +18 -14
  25. package/corpus/templates/design/actions/run-design-audit.ts +7 -4
  26. package/corpus/templates/design/actions/write-local-file.ts +47 -22
  27. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +221 -66
  28. package/corpus/templates/design/app/components/design/EditPanel.tsx +650 -237
  29. package/corpus/templates/design/app/components/design/LayersPanel.tsx +526 -127
  30. package/corpus/templates/design/app/components/design/MotionDock.tsx +234 -46
  31. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1520 -410
  32. package/corpus/templates/design/app/components/design/StatesPanel.tsx +25 -2
  33. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +640 -72
  34. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +605 -0
  35. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +50 -26
  36. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +16 -4
  37. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +90 -103
  38. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +111 -5
  39. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -2
  40. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +42 -2
  41. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +11 -2
  42. package/corpus/templates/design/app/components/design/types.ts +16 -0
  43. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +24 -6
  44. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +15 -1
  45. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  46. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +77 -0
  47. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  48. package/corpus/templates/design/app/i18n-data.ts +18 -0
  49. package/corpus/templates/design/app/lib/design-import.ts +95 -0
  50. package/corpus/templates/design/app/pages/DesignEditor.tsx +4015 -840
  51. package/corpus/templates/design/changelog/2026-07-02-creating-two-frames-in-a-row-no-longer-freezes-and-reloads-t.md +6 -0
  52. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +6 -0
  53. package/corpus/templates/design/changelog/2026-07-03-design-retries-stalled-screen-edits-with-a-smaller-safer-fol.md +6 -0
  54. package/corpus/templates/design/changelog/2026-07-03-inspector-fixes-mixed-selections-show-mixed-instead-of-wrong.md +6 -0
  55. package/corpus/templates/design/changelog/2026-07-03-keyframe-timeline-works-reliably-drag-keyframes-smoothly-pic.md +6 -0
  56. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +6 -0
  57. package/corpus/templates/design/changelog/2026-07-03-much-faster-editor-smooth-dragging-zooming-and-panel-updates.md +6 -0
  58. package/corpus/templates/design/changelog/2026-07-03-pen-tool-refinements-click-the-first-point-to-close-with-a-d.md +6 -0
  59. package/corpus/templates/design/changelog/2026-07-03-text-editing-enter-adds-a-line-break-international-ime-typin.md +6 -0
  60. package/corpus/templates/design/changelog/2026-07-03-undo-and-redo-are-dependable-across-agent-edits-screen-switc.md +6 -0
  61. package/corpus/templates/design/server/lib/design-export.ts +43 -1
  62. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
  63. package/corpus/templates/design/shared/board-file.ts +25 -5
  64. package/corpus/templates/design/shared/canvas-math.ts +754 -9
  65. package/corpus/templates/design/shared/code-layer.ts +304 -26
  66. package/corpus/templates/design/shared/color-utils.ts +84 -0
  67. package/corpus/templates/design/shared/design-source-capabilities.ts +16 -7
  68. package/corpus/templates/design/shared/motion-compiler.ts +75 -31
  69. package/corpus/templates/design/shared/motion-timeline.ts +335 -0
  70. package/corpus/templates/design/shared/pen-path.ts +200 -23
  71. package/dist/client/AssistantChat.js +1 -1
  72. package/dist/client/AssistantChat.js.map +1 -1
  73. package/dist/client/use-pinch-zoom.d.ts.map +1 -1
  74. package/dist/client/use-pinch-zoom.js +76 -11
  75. package/dist/client/use-pinch-zoom.js.map +1 -1
  76. package/dist/collab/awareness.d.ts +2 -2
  77. package/dist/collab/awareness.d.ts.map +1 -1
  78. package/dist/collab/presence.d.ts.map +1 -1
  79. package/dist/collab/presence.js +54 -3
  80. package/dist/collab/presence.js.map +1 -1
  81. package/dist/collab/routes.d.ts +1 -1
  82. package/dist/collab/struct-routes.d.ts +1 -1
  83. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  84. package/dist/observability/routes.d.ts +2 -2
  85. package/dist/secrets/routes.d.ts +3 -3
  86. package/package.json +1 -1
@@ -110,6 +110,50 @@ Pick a preset by `projectType`:
110
110
  `:root` CSS variable. Never hardcode `text-white` / `bg-black` / hex literals
111
111
  in the markup — that's what keeps brand + multi-screen consistency automatic.
112
112
 
113
+ ### Type-scale recipe
114
+
115
+ Use this as a starting scale, then adjust to the chosen Aesthetic:
116
+
117
+ - Display: 56-96px · H1: 40-64px · H2: 28-36px · Body: 16-18px · Caption: 12-13px.
118
+ - Each adjacent step should be at least 1.25× the one below it — smaller jumps
119
+ read as "almost the same size" rather than a deliberate hierarchy.
120
+ - A hero/display line should be at least 3× the body size.
121
+ - Line-heights: display/H1 tight at 1.05-1.15, H2/H3 at 1.2-1.3, body relaxed
122
+ at 1.5-1.7.
123
+ - Measure (line length) for body copy: 60-75 characters; constrain with
124
+ `max-width` in `ch` units, not a raw pixel guess.
125
+
126
+ ### Section rhythm
127
+
128
+ Pick one section padding value and repeat it for every top-level section on
129
+ the page/screen: 96-128px on desktop, 48-64px on mobile. Don't let each
130
+ section invent its own padding — that's what makes a page feel unplanned.
131
+ Use spacing to encode grouping: gaps *inside* a card or cluster should be
132
+ visibly smaller than the gap *between* cards/sections — if inside-group and
133
+ between-group spacing match, the eye can't tell where one group ends and the
134
+ next begins.
135
+
136
+ ### Verifiable contrast pairs
137
+
138
+ Don't just assert "WCAG AA" — check the actual token pairs the design ships.
139
+ The most common real failure is muted text (`--color-text-muted`) directly on
140
+ a card/surface background rather than the page background; verify that pair
141
+ specifically, not just text-on-page. If an accent color doubles as text (a
142
+ link, an active nav item, a price), it usually fails 4.5:1 against typical
143
+ surfaces — add a separate `--color-accent-text` variant tuned for text-on-
144
+ background contrast rather than reusing the decorative accent for copy.
145
+
146
+ ### Richer tokens
147
+
148
+ Go beyond the minimal `:root` block in the HTML Structure Requirements below
149
+ when the design needs it — add `--space-section` (see Section rhythm),
150
+ `--color-border`, `--color-accent-text` (see contrast pairs above),
151
+ `--shadow-card`, and a success/warning/danger trio
152
+ (`--color-success` / `--color-warning` / `--color-danger`) once the design has
153
+ status states, alerts, or form validation to express. Keep font tokens as
154
+ placeholders you fill per design (see HTML Structure Requirements) rather than
155
+ hardcoding a concrete family in a shared template.
156
+
113
157
  ## Building on existing code, screens, or a design system
114
158
 
115
159
  When a design system, tokens, current screens, or a connected codebase already
@@ -127,7 +171,7 @@ defaults. The banned-defaults list above still applies, plus:
127
171
  your colors". Keep structure and layout genuinely varied per screen while the
128
172
  palette, type, and components stay on-brand.
129
173
 
130
- ## Generation Workflow — the canonical 4-phase flow
174
+ ## Generation Workflow — the canonical 5-phase flow
131
175
 
132
176
  This flow mirrors Claude Design's UX: ask → show variants → user picks → refine. Don't skip phases for new designs.
133
177
 
@@ -204,12 +248,21 @@ pnpm action generate-design \
204
248
 
205
249
  `generate-design` accepts a `--tweaks` array — pass 3-6 of the most impactful knobs bound to CSS custom properties the design's `:root` block actually defines. Surface controls users will actually want to adjust (accent color, density, radius, dark-mode toggle, font choice). Don't ship a generic preset; let the design's structure pick the knobs.
206
250
 
207
- ### Phase 5 — Review before calling it ready
251
+ ### Phase 5 — Audit, fix, and eyeball before calling it ready
252
+
253
+ Run `run-design-audit` against each screen (`designId` + `fileId`/`filename`).
254
+ It returns `A11yFinding[]` covering missing alt/labels, tap-target size,
255
+ focus-visibility, reduced-motion coverage, and a contrast hint. For every
256
+ `error`-severity finding with `fixAvailable: true`, call `apply-a11y-fix`;
257
+ for findings that aren't auto-fixable (missing alt text, structural issues),
258
+ fix them directly with `edit-design`. **A design with audit errors is not
259
+ ready** — don't report a design as done while `run-design-audit` still
260
+ returns unresolved errors.
208
261
 
209
- Open the generated screen or overview and inspect it like a design review:
210
- hierarchy, overflow, mobile fit, contrast, keyboard focus, empty/loading/error
211
- states for app UI, and whether the copy/content still sounds real. Fix obvious
212
- issues before reporting the design as ready.
262
+ After the audit is clean, do one visual pass by eye at both a mobile width
263
+ (375px) and a desktop width (1280px): check for overflow/clipping, broken
264
+ hierarchy, empty/loading/error states for app UI, and whether the copy/content
265
+ still sounds real. Fix obvious issues before reporting the design as ready.
213
266
 
214
267
  ## HTML Structure Requirements
215
268
 
@@ -247,8 +300,8 @@ Every `index.html` must include:
247
300
  --color-surface: #1E293B;
248
301
  --color-text: #F8FAFC;
249
302
  --color-text-muted: #94A3B8;
250
- --font-heading: 'Space Grotesk', sans-serif;
251
- --font-body: 'DM Sans', sans-serif;
303
+ --font-heading: '<HEADING_FONT>', sans-serif; /* pick per Font Recommendations below — do not default to Space Grotesk */
304
+ --font-body: '<BODY_FONT>', sans-serif; /* pick a pairing, not a repeat of every other generation */
252
305
  --radius: 12px;
253
306
  }
254
307
 
@@ -484,11 +537,20 @@ to the chosen Aesthetic layer is what keeps designs from sharing a fingerprint:
484
537
  | Space Grotesk | DM Sans | Modern tech |
485
538
  | Playfair Display | Source Sans 3 | Editorial luxury |
486
539
  | Sora | Outfit | Clean geometric |
487
- | Cabinet Grotesk | Satoshi | Contemporary startup |
540
+ | Bricolage Grotesque | Schibsted Grotesk | Contemporary startup |
488
541
  | Fraunces | Work Sans | Warm editorial |
489
542
  | JetBrains Mono | IBM Plex Sans | Developer tool |
490
- | Clash Display | General Sans | Bold statement |
543
+ | Unbounded | Sora | Bold statement |
491
544
  | Archivo | Nunito Sans | Friendly SaaS |
545
+ | Instrument Serif | Schibsted Grotesk | Editorial minimal |
546
+
547
+ All of the above are served by Google Fonts (`fonts.googleapis.com/css2`). The
548
+ mandatory `<head>` only loads the Google Fonts CDN (see HTML Structure
549
+ Requirements) — Fontshare-only families (Cabinet Grotesk, Satoshi, Clash
550
+ Display, General Sans) are not on Google Fonts and will silently fail to load,
551
+ so the browser falls back to a system sans and quietly reintroduces the exact
552
+ slop this skill bans. Only use a Fontshare family if you also add its
553
+ Fontshare `<link>`/`@import` and confirm it renders.
492
554
 
493
555
  ## Multi-screen prototypes & navigation
494
556
 
@@ -569,7 +631,122 @@ regeneration is slow, expensive, and regresses unrelated parts.
569
631
  - Never use `<script>` blocks with raw DOM manipulation — use Alpine.js directives
570
632
  - Never inline `onclick="..."` handlers — use `@click`
571
633
  - Never use `!important` except in `[x-cloak]`
572
- - Never use position: fixed for modals — wrap in a portal-like pattern with Alpine.js
573
634
  - Never forget `cursor-pointer` on interactive elements
574
- - Never use `<img>` with placeholder URLs — use colored divs or gradients
575
- - Never set font-size below 14px for body text or 12px for labels
635
+ - Never use `<img>` with placeholder/stock URLs — generate real imagery (see
636
+ Imagery below) or use tokened colored divs/gradients only for pure UI
637
+ chrome (icons, avatars-as-initials, decorative fills), never as a stand-in
638
+ for a hero, product shot, or portrait that should be a generated image
639
+ - Never set font-size below 16px for body text or 12px for labels
640
+
641
+ ## Multi-screen consistency contract
642
+
643
+ When a design has more than one screen, the shared system must be
644
+ byte-identical across every screen file, not just similar: the `:root` token
645
+ block, the Google Fonts `<link>`, the nav, and the footer should match exactly
646
+ between `index.html` and every other screen. Before saving a new or edited
647
+ screen, diff its `:root` block against `index.html`'s (or the design system's
648
+ tokens) and reconcile any drift instead of letting each screen accumulate its
649
+ own slightly-different palette. Consistency is not sameness — keep structure
650
+ and layout varied per screen (see "Building on existing code" above) while the
651
+ token layer, typography, nav, and footer stay identical.
652
+
653
+ ## Breakpoints & screen states
654
+
655
+ - **Breakpoints**: `add-breakpoint` adds a device-width frame (Mobile 390 /
656
+ Tablet 768 / Desktop 1280, or a custom width) to the design's breakpoint set
657
+ stored in `designs.data`; the Tailwind prefix is derived from the width and
658
+ duplicate widths are ignored. `remove-breakpoint` removes one by id.
659
+ `set-active-breakpoint` sets which frame is the current edit scope — editing
660
+ a layer while `base` is active writes unprefixed Tailwind classes, editing
661
+ while `md` is active writes `md:`-prefixed classes. Always check the active
662
+ breakpoint before making a responsive-only edit so the class lands at the
663
+ right prefix.
664
+ - **Design states**: `create-design-state` creates a named alternate
665
+ DOM/Alpine snapshot (`kind: "state"` — Loading, Empty, Error), a static data
666
+ fixture (`kind: "fixture"`), or a placeholder for a live capture
667
+ (`kind: "capture"`). `apply-design-state` updates an existing state row
668
+ (rename, change breakpoint, update fixture/capture data, set the preview
669
+ reference). `capture-design-state` records a running app's current route,
670
+ props, and API data into a `capture` row — it requires the design's source
671
+ to advertise the `captureState` bridge capability (localhost/fusion); for
672
+ inline designs without a live bridge, use `create-design-state` instead.
673
+ `list-design-states` lists all states/fixtures/captures for a design, and
674
+ `delete-design-state` removes one (irreversible; the design itself is
675
+ unaffected).
676
+
677
+ ## Component reuse
678
+
679
+ Before hand-rolling another near-duplicate card/button/nav item, check
680
+ whether the pattern already exists as a recognised component. Once a visual
681
+ pattern repeats 3+ times in a design, promote it: call `create-component` on
682
+ the selected root element to stamp deterministic
683
+ `data-agent-native-component="<Name>"` and `data-agent-native-prop-*`
684
+ annotations, so it becomes a recognised component instance for the canvas
685
+ outline and the Component inspector section. Use `index-components` to scan a
686
+ design's HTML for existing `data-agent-native-component` annotations and
687
+ persist the discovered component list before inventing something that may
688
+ already exist. `get-component-details` returns a selected instance's name,
689
+ props, variants, and source info. `preview-component-prop-edit` previews a
690
+ prop/class change on the canvas without saving; `apply-component-prop-edit`
691
+ persists it. `open-component-source` navigates to the component's source
692
+ location (the design file for inline/Alpine designs, or the resolved external
693
+ file for localhost/fusion sources).
694
+
695
+ ## Realistic app-state content
696
+
697
+ For app/product UI (not marketing pages), populate lists and tables with
698
+ plausible mid-life data — not a pristine "just signed up" empty account and
699
+ not obviously fake placeholder rows (avoid "Lorem Ipsum User", "Item 1", "Item
700
+ 2"). Include at least one realistically long name/title/label so truncation
701
+ and wrapping behavior is visible. Always design the empty state and a loading
702
+ skeleton for the screen's primary data surface — don't only show the
703
+ happy-path populated state.
704
+
705
+ ## Motion craft
706
+
707
+ Push motion the same way you push type and color: on purpose, not as a
708
+ uniform default. Duration bands: 150-250ms for micro-interactions (hover,
709
+ toggle, button press), 300-500ms for panel/sheet/modal transitions, and
710
+ 500-800ms reserved for exactly one orchestrated page-load reveal (stagger
711
+ individual elements 60-100ms apart, capped at about 6 staggered elements —
712
+ more than that reads as sluggish, not polished). Ease functions: `ease-out`
713
+ for elements entering, `ease-in` for elements leaving. Animate only
714
+ `transform` and `opacity` for performance; avoid animating `width`/`height`/
715
+ `top`/`left`. Every non-essential animation must respect
716
+ `prefers-reduced-motion` (see the mandatory `<style>` block below). For
717
+ inline/Alpine screens, persist motion as durable timeline metadata: inspect
718
+ the current file's timeline with `get-motion-timeline`, then write changes
719
+ with `apply-motion-edit` using the same `sourceRef`/`fileId` — this is not a
720
+ one-way export, edits stay editable.
721
+
722
+ ## Imagery
723
+
724
+ Generate real images for anything a real product would photograph or
725
+ illustrate: hero backgrounds, product shots, portraits/avatars, testimonial
726
+ photos, marketing/editorial imagery. Don't generate images for utility UI —
727
+ icons, data tables, form chrome, and dashboard widgets should stay as
728
+ tokened SVG/CSS, not photos.
729
+
730
+ - **Use the Assets generation tool** (`generate-asset`, or `insert-asset` once
731
+ an asset is chosen) instead of `<img>` placeholder URLs or colored-div
732
+ stand-ins. See the Core Rules image-generation bullet in `AGENTS.md` for the
733
+ full calling convention (default `tier: "fast"`, `callerAppId: "design"`,
734
+ matching `aspectRatio`).
735
+ - **Write image prompts as art direction, not a one-line label.** Specify
736
+ subject, composition, lens/framing, lighting, and palette, and tie the
737
+ palette/mood back to the design's own `:root` tokens so the image reads as
738
+ part of the same system rather than a stock photo dropped in. If a design
739
+ system is linked, fold its `imageStyle.styleDescription` into the prompt
740
+ (see `design-systems` skill) so generated imagery matches the brand's
741
+ established photographic/illustration style.
742
+ - **Default to `tier: "fast"`** (the cheap Gemini Flash "nanobanana"-class
743
+ model) for exploration and every non-final variant. Only request
744
+ `tier: "best"` for the final, user-approved hero image — not for every pass.
745
+ - **Match `aspectRatio` to the layout slot**: `21:9` for a full-bleed hero,
746
+ `4:3` for a card/feature image, `1:1` for an avatar or square thumbnail.
747
+ Mismatched aspect ratios force ugly crops in the browser.
748
+ - **Always write real `alt` text** describing the image's content — never
749
+ leave `alt=""` on a meaningful (non-decorative) image.
750
+ - **Placement is a two-step pass**: call `insert-asset` to place the chosen
751
+ image, then do one `edit-design` pass to adjust surrounding layout/spacing
752
+ if the inserted figure doesn't sit flush with the rest of the design.
@@ -73,6 +73,12 @@ interface DesignSystemData {
73
73
  }
74
74
  ```
75
75
 
76
+ `imageStyle.styleDescription` is not just metadata — fold it into every
77
+ `generate-asset` prompt for a design linked to this system, alongside the
78
+ subject/composition/lighting direction described in the design-generation
79
+ skill's Imagery section. This is what keeps generated photography/illustration
80
+ on-brand instead of drifting to a generic stock look each time.
81
+
76
82
  ## Actions
77
83
 
78
84
  ### Creating a Design System
@@ -92,8 +98,8 @@ pnpm action create-design-system \
92
98
  "textMuted": "#94A3B8"
93
99
  },
94
100
  "typography": {
95
- "headingFont": "Space Grotesk",
96
- "bodyFont": "DM Sans",
101
+ "headingFont": "<HEADING_FONT>",
102
+ "bodyFont": "<BODY_FONT>",
97
103
  "headingWeight": "700",
98
104
  "bodyWeight": "400",
99
105
  "headingSizes": { "h1": "64px", "h2": "40px", "h3": "28px" }
@@ -105,6 +111,12 @@ pnpm action create-design-system \
105
111
  }'
106
112
  ```
107
113
 
114
+ `<HEADING_FONT>` / `<BODY_FONT>` are placeholders — pick a real Google Fonts
115
+ pairing per the design-generation skill's Font Recommendations table (or the
116
+ brand's actual extracted fonts) rather than defaulting to Space Grotesk/DM
117
+ Sans every time; that pairing is this skill's own most common convergence
118
+ fingerprint.
119
+
108
120
  If this is the user's first design system, it is automatically set as the default.
109
121
 
110
122
  ### Reading a Design System
@@ -257,8 +269,8 @@ When the user provides multiple sources, call all applicable import actions in p
257
269
  4. **Documents supplement** — presentations may reveal brand colors not in code
258
270
  5. **Images inform mood** — color temperature, density, visual style
259
271
  6. **Aggregate into DesignSystemData** — merge all extracted tokens, resolve conflicts
260
- 6. **Call `create-design-system`** with the combined result
261
- 7. **Link to design** via `update-design --designSystemId`
272
+ 7. **Call `create-design-system`** with the combined result
273
+ 8. **Link to design** via `update-design --designSystemId`
262
274
 
263
275
  ## Applying Design System to Generated HTML
264
276
 
@@ -10,6 +10,24 @@ description: >-
10
10
 
11
11
  How to export designs and generate handoff documentation for developers converting prototypes to production code.
12
12
 
13
+ ## Other export actions
14
+
15
+ - **SVG**: `export-svg` exports a design project as an SVG document (a
16
+ `foreignObject` wrapper around the standalone HTML), giving agent parity
17
+ with the editor's Download SVG command. The editor's own Download SVG
18
+ command captures the live browser DOM for the most faithful snapshot; use
19
+ the action when you need agent-side SVG export without a live browser.
20
+ - **PNG**: there is no PNG export action. Point the user to the editor's
21
+ download menu (Download PNG) — PNG export is a client-side rasterization of
22
+ the live canvas and is not exposed as an agent action.
23
+ - **Deploy preview**: `deploy-design-preview` triggers a preview deploy for a
24
+ fusion-backed design branch. It requires the design's source to advertise
25
+ the `deployPreview` capability (fusion tier) and Builder.io to be connected;
26
+ a branch must already exist via `create-design-branch`. For inline/localhost
27
+ designs it returns `ctaRequired: true` with a Make-it-real CTA instead of
28
+ faking a deploy. This triggers a *preview* deploy only — production
29
+ publishing goes through the Builder Visual Editor's Publish flow.
30
+
13
31
  ## Export Formats
14
32
 
15
33
  ### HTML Export
@@ -70,9 +88,30 @@ pnpm action export-pdf --id <designId>
70
88
 
71
89
  Returns all design data and files needed for the client to render a PDF.
72
90
 
73
- ## Claude Code Handoff
91
+ ## Coding Handoff
92
+
93
+ When a user wants to convert an Alpine.js + Tailwind prototype into production
94
+ code, use the canonical `export-coding-handoff` action instead of hand-writing
95
+ a handoff message:
96
+
97
+ ```bash
98
+ pnpm action export-coding-handoff --id <designId>
99
+ ```
100
+
101
+ This returns tokenized raw and ZIP URLs any external coding agent can fetch,
102
+ plus a ready-to-copy prompt. The bundle reflects the design's **current**
103
+ state — live editor (collab) content plus the user's applied visual tweaks
104
+ resolved into the HTML `:root` — so the generated code matches what the user
105
+ actually tuned, not the original generated tokens. Pass `format: "json"` if the
106
+ receiving agent wants structured data instead of markdown, and `origin` to get
107
+ an absolute raw-code URL for a specific app origin. This is the canonical
108
+ design-to-code tool; prefer it over composing a handoff message by hand.
109
+
110
+ ### Manual handoff template (fallback)
74
111
 
75
- When a user wants to convert an Alpine.js + Tailwind prototype into production code, generate a detailed handoff prompt. This is not an action — it is a structured message you compose based on the design.
112
+ Use the template below only when `export-coding-handoff` isn't available or
113
+ the user explicitly wants a hand-composed summary instead of the action's
114
+ bundle. Compose it based on the design's actual HTML/tokens.
76
115
 
77
116
  ### Handoff Prompt Template
78
117
 
@@ -159,7 +198,11 @@ pnpm action export-html --id [designId]
159
198
 
160
199
  ### Generating the Handoff
161
200
 
162
- When the user asks to "hand off" or "convert to production code":
201
+ When the user asks to "hand off" or "convert to production code", call
202
+ `export-coding-handoff --id <designId>` first (see Coding Handoff above) — it
203
+ already extracts tokens, resolves applied tweaks, and returns a ready-to-copy
204
+ prompt plus fetchable URLs. Only fall back to composing the manual template
205
+ by hand if that action is unavailable:
163
206
 
164
207
  1. Read the design: `get-design --id <designId>`
165
208
  2. Extract all CSS custom properties from the HTML
@@ -39,7 +39,12 @@ patterns live in `.agents/skills/`.
39
39
  supports our own primitives.
40
40
  - For raster image generation, restyling, or editing existing screenshots/photos,
41
41
  use the available first-party Assets MCP tool such as `generate-asset` instead
42
- of placeholders or generic stock imagery. When the Assets picker returns a
42
+ of placeholders or generic stock imagery. Default to `tier: "fast"` (the cheap
43
+ Gemini flash "nanobanana"-class model) unless the user explicitly asks for
44
+ best quality; pass `aspectRatio` matching the layout slot (e.g. `21:9` hero,
45
+ `4:3` card, `1:1` avatar); include the linked design system's
46
+ `imageStyle.styleDescription` in the prompt when one is linked; and always
47
+ pass `callerAppId: "design"`. When the Assets picker returns a
43
48
  selected asset, preserve `assetId`, `runId`, and URLs verbatim; if a design is
44
49
  open, call `insert-asset` with the chosen URL/id, then refine placement with
45
50
  `get-design-snapshot` and `edit-design` as needed. If no Assets MCP tool is
@@ -223,6 +228,33 @@ patterns live in `.agents/skills/`.
223
228
  with `path`/`url` when visualizing a flow; pass `paths` for a concise route
224
229
  list. Then call `navigate --view editor --designId <id> --editorView overview`.
225
230
 
231
+ ## Review, Breakpoints, Screen States & Components
232
+
233
+ - **Review**: `run-design-audit` runs a read-only accessibility audit over a
234
+ design's rendered HTML (missing alt/labels, tap-target size,
235
+ focus-visibility, reduced-motion coverage, a contrast hint) and returns
236
+ `A11yFinding[]`. `apply-a11y-fix` applies one deterministic inline fix for a
237
+ finding (contrast, tap-target size, focus ring) when `fixAvailable: true`.
238
+ `get-design-review` compares two design snapshots/branches and returns a
239
+ file-level visual diff (added/removed/modified). See the `design-generation`
240
+ skill's Phase 5 for when to run these.
241
+ - **Breakpoints**: `add-breakpoint`, `remove-breakpoint`, and
242
+ `set-active-breakpoint` manage the design's device-width frame set and which
243
+ frame new edits target. See the `design-generation` skill's "Breakpoints &
244
+ screen states" section.
245
+ - **Design states**: `create-design-state`, `apply-design-state`,
246
+ `capture-design-state`, `list-design-states`, and `delete-design-state`
247
+ manage named DOM/Alpine states (Loading/Empty/Error), static data fixtures,
248
+ and live app captures. See the same skill section.
249
+ - **Components**: `create-component` promotes a selected element into a
250
+ recognised reusable component; `index-components` scans a design's HTML for
251
+ existing component annotations; `get-component-details`,
252
+ `preview-component-prop-edit`, `apply-component-prop-edit`, and
253
+ `open-component-source` inspect, preview, persist, and navigate to a
254
+ component instance. See the `design-generation` skill's "Component reuse"
255
+ section — promote a 3+ times repeated pattern instead of inventing another
256
+ near-duplicate.
257
+
226
258
  ## App-Backed Skill Distribution
227
259
 
228
260
  - The preferred hosted install path is
@@ -246,9 +278,13 @@ patterns live in `.agents/skills/`.
246
278
  on that same `fileId` for follow-up refinement. The kept variant screen is a
247
279
  representative direction, not the final deliverable: use `mode:
248
280
  "replace-file"` to replace it with the actual requested app/product UI in the
249
- chosen visual style. Do not leave a direction board, variant brief, summary
250
- card, or prose description as the final screen. Do not repeat delete/snapshot
251
- cycles, and do not call `generate-design` after a variant pick.
281
+ chosen visual style. Keep the replacement complete but compact: prioritize
282
+ the primary workflow, and if the requested feature list is too large for one
283
+ reliable edit, represent secondary details as visible controls, states, or
284
+ affordances instead of expanding the action input. Do not leave a direction
285
+ board, variant brief, summary card, or prose description as the final screen.
286
+ Do not repeat delete/snapshot cycles, and do not call `generate-design` after
287
+ a variant pick.
252
288
  - If inline chat choice buttons are unavailable, the user can tell you the
253
289
  preferred screen name. Do not show a separate variant picker or ask them to
254
290
  paste a copyable handoff summary.
@@ -13,8 +13,10 @@
13
13
  * - `classReplace` — replaces one Tailwind class with another on the root node.
14
14
  *
15
15
  * **Tier B (real-app, localhost / fusion):** prop writes require the
16
- * `applyEdit` source capability (bridge write hardening). Until that lands the
17
- * action returns a `ctaRequired: true` response and does not modify any source.
16
+ * `applyEdit` source capability. Localhost sources have it (design bridge +
17
+ * user write consent); fusion sources gain it after bridge hardening. For
18
+ * sources without it the action returns a `ctaRequired: true` response and
19
+ * does not modify any source.
18
20
  *
19
21
  * See DESIGN-STUDIO-PLAN.md §6.1, §7 (preview/apply contract), §11 phase 2.
20
22
  */
@@ -43,11 +45,7 @@ import {
43
45
  applyVisualEdit,
44
46
  buildCodeLayerProjection,
45
47
  } from "../shared/code-layer.js";
46
- import type {
47
- CodeLayerSource,
48
- ClassEditIntent,
49
- StyleEditIntent,
50
- } from "../shared/code-layer.js";
48
+ import type { CodeLayerSource, ClassEditIntent } from "../shared/code-layer.js";
51
49
  import {
52
50
  componentNameFor,
53
51
  componentNodeIdMatches,
@@ -342,70 +340,12 @@ export default defineAction({
342
340
  );
343
341
  }
344
342
 
345
- // ── Build edit intent ────────────────────────────────────────────────────
346
- // Map the component prop edit kind to an EditIntent that apply-visual-edit
347
- // understands. We use the same deterministic patch path for all kinds.
348
-
349
- const target = { nodeId };
350
-
351
- let intent: ClassEditIntent | StyleEditIntent;
352
-
353
- if (edit.kind === "alpineData") {
354
- // x-data is not a standard CSS property or Tailwind class — we write it
355
- // as a style-like "attribute" edit by embedding it in the class edit path
356
- // via the `set` operation on a synthetic class string. Because the HTML
357
- // patcher writes raw attribute values we use the attribute-set approach
358
- // that `apply-visual-edit` already supports through the `style` path
359
- // (targeting `x-data` as a custom property in an inline `style`
360
- // attribute would corrupt the DOM, so instead we encode the value in a
361
- // `data-agent-native-alpine-data` attribute and let the bridge pick it
362
- // up). For maximum compatibility with the existing apply-visual-edit
363
- // path we use a class operation to manipulate the x-data value through a
364
- // well-known pattern the bridge understands.
365
- //
366
- // The cleanest path is to write `data-agent-native-alpine-data` as an
367
- // attribute so the iframe bridge can relay it to Alpine as the effective
368
- // x-data — but since the bridge postMessage layer handles x-data edits
369
- // on preview, for the persist path we do a direct HTML attribute patch.
370
- // We accomplish this by treating it as a `style` edit on a sentinel
371
- // property that the patcher will place as an attribute. However the
372
- // current patcher only handles CSS properties, so we write the x-data
373
- // value through the attribute approach: stamp `data-agent-native-prop-x-data`.
374
- intent = {
375
- kind: "class",
376
- target,
377
- // Use the `set` operation with a minimal token list to mark the node's
378
- // Alpine data without touching real layout classes. The actual x-data
379
- // attribute is written below via a direct HTML splice.
380
- operation: "add",
381
- className: `data-[x-data=${JSON.stringify(edit.value)}]:hidden`, // sentinel (will not apply visually)
382
- } as ClassEditIntent;
383
- // Fall through to the direct HTML splice below.
384
- } else if (edit.kind === "classReplace") {
385
- intent = {
386
- kind: "class",
387
- target,
388
- operation: "replace",
389
- from: edit.from,
390
- to: edit.to,
391
- } as ClassEditIntent;
392
- } else {
393
- // attribute kind — write as a class add of a data-attribute sentinel so
394
- // the existing patcher path handles it, then fall through to the direct
395
- // splice for the actual attribute patch.
396
- intent = {
397
- kind: "class",
398
- target,
399
- operation: "add",
400
- className: `data-[prop-${edit.attribute}]:hidden`, // sentinel
401
- } as ClassEditIntent;
402
- }
403
-
404
- // ── Direct HTML splice for attribute edits (alpineData + attribute) ──────
405
- // The existing apply-visual-edit patcher is class/style/text focused. For
406
- // attribute mutations on the component root we splice the raw HTML directly
407
- // using the node's source span (the same technique the patcher uses for
408
- // attribute stamping).
343
+ // ── Apply the edit ───────────────────────────────────────────────────────
344
+ // - alpineData / attribute: attribute mutations on the component root are
345
+ // applied with a direct HTML splice using the node's source span — the
346
+ // deterministic patcher is class/style/text focused.
347
+ // - classReplace: routed through the deterministic apply-visual-edit
348
+ // patcher (same seam as all other class edits).
409
349
 
410
350
  let patchedContent = html;
411
351
  let changed = false;
@@ -420,8 +360,15 @@ export default defineAction({
420
360
  );
421
361
  patchedContent = result.content;
422
362
  changed = result.changed;
423
- } else if (edit.kind === "classReplace") {
424
- // Use the deterministic patcher for class edits.
363
+ } else {
364
+ // classReplace — use the deterministic patcher for class edits.
365
+ const intent: ClassEditIntent = {
366
+ kind: "class",
367
+ target: { nodeId },
368
+ operation: "replace",
369
+ from: edit.from,
370
+ to: edit.to,
371
+ };
425
372
  const patch = applyVisualEdit(html, intent, { source: codeLayerSource });
426
373
  if (patch.result.status === "applied" && patch.result.changed) {
427
374
  patchedContent = patch.content;
@@ -97,6 +97,18 @@ export function canPatchManagedMotionCss(content: string): boolean {
97
97
  return /<\s*(?:!doctype|[a-z][a-z0-9:-]*(?:\s|>|\/>))/i.test(content);
98
98
  }
99
99
 
100
+ // Composite key for detecting duplicate (targetNodeId, property) track pairs.
101
+ // Uses the ASCII Unit Separator (U+001F) — an escape in source, so the file
102
+ // stays plain text (a literal NUL made tooling treat it as binary). U+001F
103
+ // cannot appear in a valid data-agent-native-node-id or CSS property name
104
+ // (both are validated to CSS-safe identifiers), so it can never collide with
105
+ // real content and remains an unambiguous field delimiter.
106
+ const MOTION_TRACK_KEY_SEPARATOR = "\x1f";
107
+
108
+ export function motionTrackKey(targetNodeId: string, property: string): string {
109
+ return `${targetNodeId}${MOTION_TRACK_KEY_SEPARATOR}${property}`;
110
+ }
111
+
100
112
  async function persistFileContent(
101
113
  fileId: string,
102
114
  designId: string,
@@ -168,7 +180,10 @@ export default defineAction({
168
180
  .number()
169
181
  .int()
170
182
  .positive()
171
- .default(300)
183
+ // Keep in sync with the MotionDock default (DesignEditor
184
+ // motionDurationMs) and get-motion-timeline's CSS-recovery fallback so
185
+ // an omitted duration means the same thing on every surface.
186
+ .default(1000)
172
187
  .describe("Total animation duration in milliseconds."),
173
188
  defaultEase: z
174
189
  .string()
@@ -254,9 +269,21 @@ export default defineAction({
254
269
 
255
270
  // Reject CSS-injection vectors in caller-supplied track properties,
256
271
  // keyframe values, and easing strings before they are compiled into the
257
- // managed <style> block.
272
+ // managed <style> block. Also reject duplicate (targetNodeId, property)
273
+ // pairs: the compiler derives the animation name from that pair, so a
274
+ // duplicate would silently overwrite the earlier track's keyframes.
275
+ const seenTrackKeys = new Set<string>();
258
276
  for (const track of typedTracks) {
259
277
  assertSafeMotionCssProperty(track.property, "track.property");
278
+ const trackKey = motionTrackKey(track.targetNodeId, track.property);
279
+ if (seenTrackKeys.has(trackKey)) {
280
+ throw new Error(
281
+ `Duplicate motion track for targetNodeId "${track.targetNodeId}" ` +
282
+ `and property "${track.property}". Each (targetNodeId, property) ` +
283
+ "pair may appear at most once — merge the keyframes into a single track.",
284
+ );
285
+ }
286
+ seenTrackKeys.add(trackKey);
260
287
  for (const kf of track.keyframes) {
261
288
  assertSafeMotionCssToken(kf.value, "keyframe value");
262
289
  if (kf.ease !== undefined) {
@@ -106,6 +106,7 @@ function scopeClassIntentToBreakpoint(
106
106
  prefix,
107
107
  operation: "replace",
108
108
  utility: intent.to,
109
+ from: intent.from,
109
110
  };
110
111
  }
111
112
 
@@ -474,11 +475,12 @@ export default defineAction({
474
475
  const patch = applyVisualEdit("", editIntent, {
475
476
  source: codeLayerSource,
476
477
  });
478
+ // local-file / remote-url sources are not editable here (the engine
479
+ // reports "unsupported"), so no byte counts are returned — a 0/0 pair
480
+ // would misleadingly suggest an empty file was measured.
477
481
  return {
478
482
  result: patch.result,
479
483
  projection: patch.projection,
480
- bytesBefore: 0,
481
- bytesAfter: 0,
482
484
  };
483
485
  }
484
486