@1agh/maude 0.15.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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/cli/bin/maude.exe +15 -0
  4. package/cli/bin/maude.mjs +45 -0
  5. package/cli/bin/mdcc.exe +10 -0
  6. package/cli/bin/mdcc.mjs +7 -0
  7. package/cli/cli-wrapper.cjs +67 -0
  8. package/cli/commands/config.mjs +94 -0
  9. package/cli/commands/design.mjs +386 -0
  10. package/cli/commands/help.mjs +57 -0
  11. package/cli/commands/init.mjs +178 -0
  12. package/cli/commands/version.mjs +7 -0
  13. package/cli/install.cjs +113 -0
  14. package/cli/lib/argv.mjs +37 -0
  15. package/cli/lib/argv.test.mjs +46 -0
  16. package/cli/lib/copy-tree.mjs +78 -0
  17. package/package.json +94 -0
  18. package/plugins/design/dev-server/annotations-context-toolbar.tsx +397 -0
  19. package/plugins/design/dev-server/annotations-layer.tsx +1717 -0
  20. package/plugins/design/dev-server/api.ts +674 -0
  21. package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +50 -0
  22. package/plugins/design/dev-server/bin/bootstrap-check.sh +83 -0
  23. package/plugins/design/dev-server/bin/canvas-edit.sh +48 -0
  24. package/plugins/design/dev-server/bin/handoff.sh +27 -0
  25. package/plugins/design/dev-server/bin/screenshot.sh +232 -0
  26. package/plugins/design/dev-server/bin/server-up.sh +135 -0
  27. package/plugins/design/dev-server/bin/slug.sh +22 -0
  28. package/plugins/design/dev-server/bin/smoke.sh +272 -0
  29. package/plugins/design/dev-server/build.ts +267 -0
  30. package/plugins/design/dev-server/canvas-build.ts +219 -0
  31. package/plugins/design/dev-server/canvas-edit.ts +388 -0
  32. package/plugins/design/dev-server/canvas-header.ts +165 -0
  33. package/plugins/design/dev-server/canvas-icons.tsx +131 -0
  34. package/plugins/design/dev-server/canvas-lib-inline.ts +260 -0
  35. package/plugins/design/dev-server/canvas-lib-resolver.ts +85 -0
  36. package/plugins/design/dev-server/canvas-lib.tsx +1995 -0
  37. package/plugins/design/dev-server/canvas-meta.schema.json +181 -0
  38. package/plugins/design/dev-server/canvas-pipeline.ts +270 -0
  39. package/plugins/design/dev-server/canvas-shell.tsx +813 -0
  40. package/plugins/design/dev-server/client/app.jsx +2027 -0
  41. package/plugins/design/dev-server/client/hmr.mjs +85 -0
  42. package/plugins/design/dev-server/client/iframe-lazy.mjs +121 -0
  43. package/plugins/design/dev-server/client/index.html +15 -0
  44. package/plugins/design/dev-server/client/styles/0-reset.css +18 -0
  45. package/plugins/design/dev-server/client/styles/1-tokens.css +297 -0
  46. package/plugins/design/dev-server/client/styles/2-layout.css +35 -0
  47. package/plugins/design/dev-server/client/styles/3-shell.css +906 -0
  48. package/plugins/design/dev-server/client/styles/4-components.css +1268 -0
  49. package/plugins/design/dev-server/client/styles/5-utilities.css +4 -0
  50. package/plugins/design/dev-server/client/styles/_index.css +24 -0
  51. package/plugins/design/dev-server/client/styles.css +1419 -0
  52. package/plugins/design/dev-server/config.schema.json +147 -0
  53. package/plugins/design/dev-server/context-menu.tsx +343 -0
  54. package/plugins/design/dev-server/context.ts +173 -0
  55. package/plugins/design/dev-server/dist/client.bundle.js +20323 -0
  56. package/plugins/design/dev-server/dist/styles.css +2875 -0
  57. package/plugins/design/dev-server/examples/README.md +9 -0
  58. package/plugins/design/dev-server/examples/perf-100-artboards.tsx +113 -0
  59. package/plugins/design/dev-server/fs-watch.ts +63 -0
  60. package/plugins/design/dev-server/handoff.ts +721 -0
  61. package/plugins/design/dev-server/history.ts +125 -0
  62. package/plugins/design/dev-server/hmr-broadcast.ts +114 -0
  63. package/plugins/design/dev-server/http.ts +413 -0
  64. package/plugins/design/dev-server/input-router.tsx +485 -0
  65. package/plugins/design/dev-server/inspect.ts +365 -0
  66. package/plugins/design/dev-server/locator.ts +159 -0
  67. package/plugins/design/dev-server/mem.ts +97 -0
  68. package/plugins/design/dev-server/runtime-bundle.ts +235 -0
  69. package/plugins/design/dev-server/server.mjs +1246 -0
  70. package/plugins/design/dev-server/server.ts +131 -0
  71. package/plugins/design/dev-server/test/_helpers.ts +81 -0
  72. package/plugins/design/dev-server/test/active-state.test.ts +145 -0
  73. package/plugins/design/dev-server/test/annotations-api.test.ts +146 -0
  74. package/plugins/design/dev-server/test/annotations-layer.test.ts +419 -0
  75. package/plugins/design/dev-server/test/binary-smoke.test.ts +47 -0
  76. package/plugins/design/dev-server/test/bundle-smoke.test.ts +29 -0
  77. package/plugins/design/dev-server/test/canvas-build.test.ts +78 -0
  78. package/plugins/design/dev-server/test/canvas-edit.test.ts +139 -0
  79. package/plugins/design/dev-server/test/canvas-header.test.ts +127 -0
  80. package/plugins/design/dev-server/test/canvas-lib-inline.test.ts +146 -0
  81. package/plugins/design/dev-server/test/canvas-lib-resolver.test.ts +112 -0
  82. package/plugins/design/dev-server/test/canvas-meta-api.test.ts +236 -0
  83. package/plugins/design/dev-server/test/canvas-pipeline.test.ts +180 -0
  84. package/plugins/design/dev-server/test/canvas-route.test.ts +176 -0
  85. package/plugins/design/dev-server/test/fs-watch.test.ts +41 -0
  86. package/plugins/design/dev-server/test/handoff-static-frames.test.ts +215 -0
  87. package/plugins/design/dev-server/test/handoff.test.ts +281 -0
  88. package/plugins/design/dev-server/test/history-rollback.test.ts +62 -0
  89. package/plugins/design/dev-server/test/hmr-broadcast.test.ts +108 -0
  90. package/plugins/design/dev-server/test/input-router.test.ts +316 -0
  91. package/plugins/design/dev-server/test/locator.test.ts +214 -0
  92. package/plugins/design/dev-server/test/perf-harness.ts +193 -0
  93. package/plugins/design/dev-server/test/phase-3.6-smoke.test.ts +77 -0
  94. package/plugins/design/dev-server/test/runtime-bundle.test.ts +69 -0
  95. package/plugins/design/dev-server/test/server-lifecycle.test.ts +28 -0
  96. package/plugins/design/dev-server/test/tool-palette.test.tsx +55 -0
  97. package/plugins/design/dev-server/test/use-annotation-selection.test.tsx +77 -0
  98. package/plugins/design/dev-server/test/use-artboard-drag.test.ts +325 -0
  99. package/plugins/design/dev-server/test/use-selection-set.test.tsx +166 -0
  100. package/plugins/design/dev-server/test/use-snap-guides.test.ts +190 -0
  101. package/plugins/design/dev-server/test/use-tool-mode.test.tsx +93 -0
  102. package/plugins/design/dev-server/test/ws-handshake.test.ts +33 -0
  103. package/plugins/design/dev-server/tool-palette.tsx +278 -0
  104. package/plugins/design/dev-server/tsconfig.json +26 -0
  105. package/plugins/design/dev-server/use-annotation-selection.tsx +92 -0
  106. package/plugins/design/dev-server/use-annotations-visibility.tsx +43 -0
  107. package/plugins/design/dev-server/use-artboard-drag.tsx +445 -0
  108. package/plugins/design/dev-server/use-selection-set.tsx +224 -0
  109. package/plugins/design/dev-server/use-snap-guides.tsx +215 -0
  110. package/plugins/design/dev-server/use-tool-mode.tsx +114 -0
  111. package/plugins/design/dev-server/ws.ts +90 -0
  112. package/plugins/design/templates/_shell.html +177 -0
  113. package/plugins/design/templates/canvas.tsx.template +54 -0
  114. package/plugins/design/templates/design-system-inspiration/_MAPPING.md +277 -0
  115. package/plugins/design/templates/design-system-inspiration/_README.md +71 -0
  116. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-banner.html +68 -0
  117. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-empty-state-generous.html +39 -0
  118. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-feature-grid.html +62 -0
  119. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-marketing-card.html +63 -0
  120. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-testimonial.html +80 -0
  121. package/plugins/design/templates/design-system-inspiration/audience-developer/components-code-block.html +71 -0
  122. package/plugins/design/templates/design-system-inspiration/audience-developer/components-diff-view.html +65 -0
  123. package/plugins/design/templates/design-system-inspiration/audience-developer/components-log-stream.html +62 -0
  124. package/plugins/design/templates/design-system-inspiration/audience-developer/components-monospace-table.html +57 -0
  125. package/plugins/design/templates/design-system-inspiration/audience-developer/components-terminal-pane.html +67 -0
  126. package/plugins/design/templates/design-system-inspiration/audience-developer/type-mono.html +57 -0
  127. package/plugins/design/templates/design-system-inspiration/audience-pro/colors-presence.html +74 -0
  128. package/plugins/design/templates/design-system-inspiration/audience-pro/components-command-palette.html +90 -0
  129. package/plugins/design/templates/design-system-inspiration/audience-pro/components-keyboard.html +51 -0
  130. package/plugins/design/templates/design-system-inspiration/audience-pro/components-list.html +89 -0
  131. package/plugins/design/templates/design-system-inspiration/audience-pro/components-shortcuts-overlay.html +85 -0
  132. package/plugins/design/templates/design-system-inspiration/audience-pro/components-toast-menu.html +80 -0
  133. package/plugins/design/templates/design-system-inspiration/core/INDEX.md.tpl +25 -0
  134. package/plugins/design/templates/design-system-inspiration/core/README.orchestration.md.tpl +71 -0
  135. package/plugins/design/templates/design-system-inspiration/core/README.philosophy.md.tpl +77 -0
  136. package/plugins/design/templates/design-system-inspiration/core/SKILL.md.tpl +50 -0
  137. package/plugins/design/templates/design-system-inspiration/core/colors_and_type.css.tpl +111 -0
  138. package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +28 -0
  139. package/plugins/design/templates/design-system-inspiration/core/preview/_layout.css +113 -0
  140. package/plugins/design/templates/design-system-inspiration/core/preview/colors-accent.html +48 -0
  141. package/plugins/design/templates/design-system-inspiration/core/preview/colors-surfaces.html +49 -0
  142. package/plugins/design/templates/design-system-inspiration/core/preview/colors-text.html +52 -0
  143. package/plugins/design/templates/design-system-inspiration/core/preview/components-buttons.html +83 -0
  144. package/plugins/design/templates/design-system-inspiration/core/preview/components-cards.html +79 -0
  145. package/plugins/design/templates/design-system-inspiration/core/preview/components-inputs.html +82 -0
  146. package/plugins/design/templates/design-system-inspiration/core/preview/motion.html +66 -0
  147. package/plugins/design/templates/design-system-inspiration/core/preview/spacing-scale.html +53 -0
  148. package/plugins/design/templates/design-system-inspiration/core/preview/type-scale.html +62 -0
  149. package/plugins/design/templates/design-system-inspiration/foundations/borders.html +39 -0
  150. package/plugins/design/templates/design-system-inspiration/foundations/elevation.html +46 -0
  151. package/plugins/design/templates/design-system-inspiration/foundations/focus.html +48 -0
  152. package/plugins/design/templates/design-system-inspiration/foundations/grid.html +51 -0
  153. package/plugins/design/templates/design-system-inspiration/foundations/iconography.html +73 -0
  154. package/plugins/design/templates/design-system-inspiration/foundations/opacity.html +45 -0
  155. package/plugins/design/templates/design-system-inspiration/foundations/radii.html +40 -0
  156. package/plugins/design/templates/design-system-inspiration/foundations/selection.html +45 -0
  157. package/plugins/design/templates/design-system-inspiration/meta/accessibility.html +62 -0
  158. package/plugins/design/templates/design-system-inspiration/meta/i18n.html +73 -0
  159. package/plugins/design/templates/design-system-inspiration/meta/presence-multiplayer.html +71 -0
  160. package/plugins/design/templates/design-system-inspiration/meta/tokens-index.html +80 -0
  161. package/plugins/design/templates/design-system-inspiration/patterns/patterns-auth.html +78 -0
  162. package/plugins/design/templates/design-system-inspiration/patterns/patterns-data-density.html +61 -0
  163. package/plugins/design/templates/design-system-inspiration/patterns/patterns-error-pages.html +70 -0
  164. package/plugins/design/templates/design-system-inspiration/patterns/patterns-form-layouts.html +70 -0
  165. package/plugins/design/templates/design-system-inspiration/patterns/patterns-onboarding.html +71 -0
  166. package/plugins/design/templates/design-system-inspiration/patterns/patterns-pricing.html +83 -0
  167. package/plugins/design/templates/design-system-inspiration/platform-desktop/components-resize-panels.html +63 -0
  168. package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-index.html +55 -0
  169. package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-showcase.html +302 -0
  170. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-bottom-sheet.html +63 -0
  171. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-pull-to-refresh.html +74 -0
  172. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-segmented-control.html +51 -0
  173. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-tab-bar.html +57 -0
  174. package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-index.html +58 -0
  175. package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-showcase.html +237 -0
  176. package/plugins/design/templates/design-system-inspiration/status/colors-status.html +49 -0
  177. package/plugins/design/templates/design-system-inspiration/status/components-status.html +63 -0
  178. package/plugins/design/templates/design-system-inspiration/status/skeletons.html +74 -0
  179. package/plugins/design/templates/design-system-inspiration/theme-both/colors-themes-side-by-side.html +59 -0
  180. package/plugins/design/templates/design-system-inspiration/universal/components-callout.html +74 -0
  181. package/plugins/design/templates/design-system-inspiration/universal/components-dialogs.html +81 -0
  182. package/plugins/design/templates/design-system-inspiration/universal/components-tables.html +101 -0
  183. package/plugins/design/templates/design-system-inspiration/universal/components-toggles.html +74 -0
  184. package/plugins/design/templates/design-system-inspiration/universal/components-tooltips.html +74 -0
  185. package/plugins/design/templates/design-system-inspiration/universal/empty-state.html.tpl +34 -0
  186. package/plugins/design/templates/design-system-inspiration/universal/logo.html +42 -0
  187. package/plugins/design/templates/ds-specimen.tsx.template +59 -0
  188. package/plugins/flow/.claude-plugin/config.schema.json +398 -0
  189. package/plugins/flow/README.md +45 -0
  190. package/plugins/flow/templates/ai-skeleton/INDEX.md +50 -0
  191. package/plugins/flow/templates/ai-skeleton/README.md +46 -0
  192. package/plugins/flow/templates/ai-skeleton/browser/har/.gitkeep +0 -0
  193. package/plugins/flow/templates/ai-skeleton/browser/snapshots/.gitkeep +0 -0
  194. package/plugins/flow/templates/ai-skeleton/business/README.md +12 -0
  195. package/plugins/flow/templates/ai-skeleton/context/README.md +12 -0
  196. package/plugins/flow/templates/ai-skeleton/decisions/README.md +20 -0
  197. package/plugins/flow/templates/ai-skeleton/design-import/.gitkeep +0 -0
  198. package/plugins/flow/templates/ai-skeleton/dev-logs/.gitkeep +0 -0
  199. package/plugins/flow/templates/ai-skeleton/device/.gitkeep +0 -0
  200. package/plugins/flow/templates/ai-skeleton/docs/README.md +5 -0
  201. package/plugins/flow/templates/ai-skeleton/logs/.gitkeep +0 -0
  202. package/plugins/flow/templates/ai-skeleton/logs/README.md +13 -0
  203. package/plugins/flow/templates/ai-skeleton/plans/README.md +16 -0
  204. package/plugins/flow/templates/ai-skeleton/plans/archive/.gitkeep +0 -0
  205. package/plugins/flow/templates/ai-skeleton/release-guide.md +35 -0
  206. package/plugins/flow/templates/ai-skeleton/reviews/README.md +15 -0
  207. package/plugins/flow/templates/ai-skeleton/scenarios/README.md +26 -0
  208. package/plugins/flow/templates/ai-skeleton/scenarios/_lib/.gitkeep +0 -0
  209. package/plugins/flow/templates/ai-skeleton/state/STATE.md +25 -0
  210. package/plugins/flow/templates/ai-skeleton/templates/HANDOFF.md +32 -0
  211. package/plugins/flow/templates/ai-skeleton/workflows.config.json +74 -0
@@ -0,0 +1,181 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Canvas metadata sidecar",
4
+ "description": "Per-canvas .meta.json sidecar that mirrors Claude Design's `.design-canvas.state.json` pattern. Stores section + artboard labels, brief, and provenance separate from the HTML so file tree and handoff bundle can render rich descriptions without parsing JSX.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["title", "sections"],
8
+ "properties": {
9
+ "title": {
10
+ "type": "string",
11
+ "description": "Canvas project title (e.g. 'Match Recap' or 'Sparta Studio'). Shown as H1 in handoff bundle."
12
+ },
13
+ "subtitle": {
14
+ "type": "string",
15
+ "description": "One-line description of the canvas project's purpose."
16
+ },
17
+ "brief": {
18
+ "type": "string",
19
+ "description": "The full brief that produced this canvas (from /design:new). Preserved for handoff."
20
+ },
21
+ "platform": {
22
+ "type": "string",
23
+ "enum": ["desktop", "mobile", "tablet", "responsive", "tv", "watch", "other"],
24
+ "description": "Primary intended platform for this canvas project."
25
+ },
26
+ "created": {
27
+ "type": "string",
28
+ "format": "date-time",
29
+ "description": "ISO timestamp when /design:new generated the canvas."
30
+ },
31
+ "last_modified": {
32
+ "type": "string",
33
+ "format": "date-time",
34
+ "description": "ISO timestamp of the last /design edit."
35
+ },
36
+ "sections": {
37
+ "type": "array",
38
+ "description": "Top-level sections inside the canvas (DCSection wrapper instances). Order matches reading order in the canvas.",
39
+ "items": {
40
+ "type": "object",
41
+ "required": ["id", "label"],
42
+ "additionalProperties": false,
43
+ "properties": {
44
+ "id": {
45
+ "type": "string",
46
+ "description": "Stable slug for the section (matches DCSection id prop)."
47
+ },
48
+ "label": { "type": "string", "description": "Human-readable section title." },
49
+ "subtitle": {
50
+ "type": "string",
51
+ "description": "Optional one-line subtitle / brief for the section."
52
+ },
53
+ "artboards": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": "object",
57
+ "required": ["id", "label"],
58
+ "additionalProperties": false,
59
+ "properties": {
60
+ "id": {
61
+ "type": "string",
62
+ "description": "Stable slug (matches DCArtboard id prop)."
63
+ },
64
+ "label": {
65
+ "type": "string",
66
+ "description": "Artboard title shown in canvas + handoff."
67
+ },
68
+ "subtitle": { "type": "string", "description": "Optional short description." },
69
+ "platform": {
70
+ "type": "string",
71
+ "enum": ["desktop", "mobile", "tablet", "watch", "tv"]
72
+ },
73
+ "width": { "type": "number", "description": "Design width in CSS px." },
74
+ "height": { "type": "number", "description": "Design height in CSS px." },
75
+ "screen_route": {
76
+ "type": "string",
77
+ "description": "Production route this artboard maps to (e.g. '/team/[id]/roster'). Optional — used by /design:handoff."
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ },
85
+ "iteration_count": {
86
+ "type": "integer",
87
+ "description": "Total /design iterations applied to this canvas. Maintained by the orchestrator."
88
+ },
89
+ "tokens_used": {
90
+ "type": "array",
91
+ "description": "CSS variables referenced by the canvas (auto-extracted on each /design:edit). Used by /design:handoff to know which tokens the canvas depends on.",
92
+ "items": { "type": "string" }
93
+ },
94
+ "designSystem": {
95
+ "type": "string",
96
+ "pattern": "^[a-z][a-z0-9-]*$",
97
+ "description": "Which design system this canvas was built against. Kebab-case slug matching a name in config.designSystems[]. Read by skill 'design-system' (read mode) + design-system-completeness-critic + flow's design-system-guard to scope rules to the right tokens / philosophy. Optional in single-DS projects (falls back to config.defaultDesignSystem); REQUIRED in multi-DS projects to avoid cross-DS contamination."
98
+ },
99
+ "opt_out_scope": {
100
+ "type": "string",
101
+ "enum": ["palette", "aesthetic", "full"],
102
+ "description": "Persisted DS opt-out scope for this canvas. Subsequent /design:edit iterations inherit this; --opt-out=<scope> on the flag overrides for that iteration and persists the new value. a11y is enforced at every scope.",
103
+ "default": "palette"
104
+ },
105
+ "css_mode": {
106
+ "type": "string",
107
+ "enum": ["inline", "tailwind", "modules"],
108
+ "description": "How this canvas writes CSS. 'inline' (MDCC-DSN/01 default) uses bespoke classes from `_components.css` + ad-hoc `style={{}}` for one-off values. 'tailwind' opts the canvas into Tailwind utilities (requires `bun-plugin-tailwind` on the dev-server). 'modules' uses a sibling `<Slug>.module.css`. The /design:handoff CSS-bundling path only fires for 'inline' (Phase 3.6 Task 12b).",
109
+ "default": "inline"
110
+ },
111
+ "data_cd_id_version": {
112
+ "type": "integer",
113
+ "description": "Major version of the data-cd-id scheme this canvas was last transpiled against. Bumped when the ID derivation rule changes (renumbering the universe of IDs). Future-proofing for v2 of the inspector; v1 = current Bun.hash(componentName + ':' + idx) → 8 hex.",
114
+ "default": 1,
115
+ "minimum": 1
116
+ },
117
+ "layout": {
118
+ "type": "object",
119
+ "additionalProperties": false,
120
+ "description": "Phase 4 canvas-v2 spatial layout. Optional — when absent the canvas runtime synthesizes a default grid (3 cols × 1280 × 820, 80 px gutter, alphabetical by DCArtboard id). Authoring state only — stripped from /design:handoff emitted registry items. Phase 4.2 (DDR-027): artboard size is JSX-authoritative (props.width / props.height on <DCArtboard>); `layout.artboards[]` persists positions only. Writes strip any incoming `w`/`h`; reads tolerate them for back-compat with Phase 4 default-grid snapshots.",
121
+ "properties": {
122
+ "artboards": {
123
+ "type": "array",
124
+ "description": "Per-DCArtboard world-coord rects keyed by `id`. ids not listed fall back to the default grid slot for their render-order index.",
125
+ "items": {
126
+ "type": "object",
127
+ "required": ["id", "x", "y"],
128
+ "additionalProperties": false,
129
+ "properties": {
130
+ "id": { "type": "string" },
131
+ "x": { "type": "number" },
132
+ "y": { "type": "number" },
133
+ "w": {
134
+ "type": "number",
135
+ "exclusiveMinimum": 0,
136
+ "description": "Read-only legacy field — Phase 4 default-grid snapshots wrote this; Phase 4.2 writers strip it. JSX width prop is the source of truth."
137
+ },
138
+ "h": {
139
+ "type": "number",
140
+ "exclusiveMinimum": 0,
141
+ "description": "Read-only legacy field — see `w`."
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ },
148
+ "viewport": {
149
+ "type": "object",
150
+ "required": ["x", "y", "zoom"],
151
+ "additionalProperties": false,
152
+ "description": "Phase 4 canvas-v2 pan/zoom state for the world plane. Optional — when absent the canvas runtime fits-to-screen on the artboard union. Authoring state — stripped from /design:handoff.",
153
+ "properties": {
154
+ "x": { "type": "number" },
155
+ "y": { "type": "number" },
156
+ "zoom": { "type": "number", "minimum": 0.1, "maximum": 4.0 }
157
+ }
158
+ },
159
+ "ai_context": {
160
+ "type": "object",
161
+ "description": "Opt-in metadata for AI cold-read. Persists hard-won context that doesn't live in the canvas source itself — pinned decisions, known quirks, rationale for why this canvas exists. Read by /design:edit's pre-flight to seed the model. Hand-edited; never auto-filled (auto-fill would defeat the purpose).",
162
+ "additionalProperties": false,
163
+ "properties": {
164
+ "pinned_decisions": {
165
+ "type": "array",
166
+ "items": { "type": "string" },
167
+ "description": "Short one-liners — decisions tried, accepted or rejected, that shaped the canvas (e.g. 'tried gradient hero — rejected: phosphor-dark theme clashed')."
168
+ },
169
+ "known_quirks": {
170
+ "type": "array",
171
+ "items": { "type": "string" },
172
+ "description": "Non-obvious behaviour callers might trip over (e.g. 'docs-article artboard intentionally taller than the rest — reserves room for fold-line annotations')."
173
+ },
174
+ "why_this_exists": {
175
+ "type": "string",
176
+ "description": "One paragraph explaining the canvas's reason for being. Surfaced in the generated JSDoc header as `@notes` when present."
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,270 @@
1
+ // Two-pass canvas TSX transform (DDR-019, Phase 3.6 Task 1).
2
+ //
3
+ // Pass 1 — parse the source with oxc-parser, walk JSX elements in pre-order,
4
+ // inject ` data-cd-id="<8 hex>"` on every opening element via magic-string
5
+ // byte-range append. The ID is derived from `Bun.hash(componentName + ":" + idx)`
6
+ // so it survives whitespace edits inside the same component but renumbers on
7
+ // sibling insertion (documented contract — see DDR-019 "Identity stability").
8
+ //
9
+ // Pass 2 — Bun.Transpiler lowers TSX to JS. The output is re-parsed by the
10
+ // caller's smoke test to confirm parseability; making it BROWSER-loadable is
11
+ // the _shell.html + react-runtime bundle's job (Task 6, future session).
12
+ //
13
+ // Exports are pure: no fs writes, no side effects. The route handler (http.ts)
14
+ // calls writeLocator(...) and serves the JS body itself.
15
+ //
16
+ // Toolchain matches scripts/migrate-canvases.ts (codemod) and canvas-edit.ts
17
+ // (AST-aware /design:edit element edits) — same oxc-parser + magic-string pair
18
+ // in three call sites, one mental model.
19
+
20
+ import MagicString from 'magic-string';
21
+ import { parseSync } from 'oxc-parser';
22
+
23
+ import type { LocatorEntry, LocatorMap } from './locator.ts';
24
+
25
+ export interface TranspileResult {
26
+ /** TSX -> JS output from Bun.Transpiler. Browser-loadable contract is Task 6's job. */
27
+ js: string;
28
+ /** Map of injected `data-cd-id` -> source location, one entry per JSX element. */
29
+ locator: LocatorMap;
30
+ /** ETag derived from the post-pass-1 source (the source the browser would see decompiled). */
31
+ etag: string;
32
+ /** Source after Pass 1 (data-cd-id injected, JSX still). Useful for tests + debugging. */
33
+ withIds: string;
34
+ }
35
+
36
+ let cachedTranspiler: InstanceType<typeof Bun.Transpiler> | null = null;
37
+ function getTranspiler(): InstanceType<typeof Bun.Transpiler> {
38
+ if (cachedTranspiler) return cachedTranspiler;
39
+ // jsx: "automatic" pairs with jsxImportSource: "react" — produces calls to
40
+ // jsx/jsxDEV/Fragment from "react/jsx-dev-runtime". Resolution is the loader's
41
+ // problem (Task 6); Pass 2 here just lowers syntax.
42
+ cachedTranspiler = new Bun.Transpiler({
43
+ loader: 'tsx',
44
+ target: 'browser',
45
+ tsconfig: JSON.stringify({
46
+ compilerOptions: { jsx: 'react-jsx', jsxImportSource: 'react' },
47
+ }),
48
+ });
49
+ return cachedTranspiler;
50
+ }
51
+
52
+ /**
53
+ * Transpile a canvas TSX file end-to-end (parse -> inject IDs -> JSX lower).
54
+ * Pure — no fs writes. Caller persists the locator map via writeLocator().
55
+ *
56
+ * @param canvasAbsPath Absolute path of the .tsx file (used as the locator key + diagnostics).
57
+ * @param source Raw TSX source. Caller is responsible for reading it via Bun.file().
58
+ */
59
+ export function transpileCanvasSource(canvasAbsPath: string, source: string): TranspileResult {
60
+ const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
61
+ if (parsed.errors && parsed.errors.length > 0) {
62
+ const first = parsed.errors[0];
63
+ const where = first?.labels?.[0]?.start ?? 0;
64
+ throw new TranspileError(
65
+ `oxc-parser failed on ${canvasAbsPath} (${parsed.errors.length} errors). First: ${first?.message ?? 'unknown'} at byte ${where}.`,
66
+ { canvas: canvasAbsPath, byte: where }
67
+ );
68
+ }
69
+
70
+ const s = new MagicString(source);
71
+ const locator: LocatorMap = {};
72
+
73
+ walkInjectIds(parsed.program, source, canvasAbsPath, s, locator);
74
+
75
+ const withIds = s.toString();
76
+ const js = getTranspiler().transformSync(withIds);
77
+ const etag = Bun.hash(withIds).toString(16);
78
+
79
+ return { js, locator, etag, withIds };
80
+ }
81
+
82
+ export class TranspileError extends Error {
83
+ readonly canvas: string;
84
+ readonly byte: number;
85
+ constructor(message: string, info: { canvas: string; byte: number }) {
86
+ super(message);
87
+ this.name = 'TranspileError';
88
+ this.canvas = info.canvas;
89
+ this.byte = info.byte;
90
+ }
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // AST walker — pre-order over the parsed program; tracks the enclosing
95
+ // component (PascalCase FunctionDeclaration / Variable+ArrowFunction) so each
96
+ // JSX element's ID is scoped to its component.
97
+
98
+ interface ComponentFrame {
99
+ componentName: string;
100
+ /** Pre-order JSX index within the component. */
101
+ jsxIndex: number;
102
+ /** Stack of element-type names from the component root down to current. */
103
+ jsxPath: string[];
104
+ }
105
+
106
+ // biome-ignore lint/suspicious/noExplicitAny: oxc-parser AST nodes are heterogeneous; we shape-check at use site.
107
+ type AnyNode = any;
108
+
109
+ const PASCAL_CASE = /^[A-Z][A-Za-z0-9_]*$/;
110
+
111
+ function isPascalIdent(name: unknown): name is string {
112
+ return typeof name === 'string' && PASCAL_CASE.test(name);
113
+ }
114
+
115
+ function componentNameOf(node: AnyNode): string | null {
116
+ if (!node || typeof node !== 'object') return null;
117
+ // function Foo() { ... }
118
+ if (node.type === 'FunctionDeclaration' && isPascalIdent(node.id?.name)) {
119
+ return node.id.name;
120
+ }
121
+ // const Foo = () => ... / const Foo = function() { ... }
122
+ if (node.type === 'VariableDeclarator' && isPascalIdent(node.id?.name)) {
123
+ const init = node.init;
124
+ if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression')) {
125
+ return node.id.name;
126
+ }
127
+ }
128
+ // export default function Foo() { ... }
129
+ if (node.type === 'FunctionExpression' && isPascalIdent(node.id?.name)) {
130
+ return node.id.name;
131
+ }
132
+ return null;
133
+ }
134
+
135
+ function jsxElementName(opening: AnyNode): string {
136
+ const name = opening?.name;
137
+ if (!name) return '?';
138
+ if (name.type === 'JSXIdentifier') return name.name ?? '?';
139
+ if (name.type === 'JSXMemberExpression') {
140
+ // e.g. <motion.div> -> "motion.div"
141
+ const parts: string[] = [];
142
+ let cur: AnyNode = name;
143
+ while (cur?.type === 'JSXMemberExpression') {
144
+ if (cur.property?.name) parts.unshift(cur.property.name);
145
+ cur = cur.object;
146
+ }
147
+ if (cur?.type === 'JSXIdentifier') parts.unshift(cur.name);
148
+ return parts.join('.') || '?';
149
+ }
150
+ if (name.type === 'JSXNamespacedName') {
151
+ return `${name.namespace?.name ?? '?'}:${name.name?.name ?? '?'}`;
152
+ }
153
+ return '?';
154
+ }
155
+
156
+ function hasDataCdIdAttr(opening: AnyNode): boolean {
157
+ const attrs = opening?.attributes;
158
+ if (!Array.isArray(attrs)) return false;
159
+ for (const a of attrs) {
160
+ if (
161
+ a?.type === 'JSXAttribute' &&
162
+ a.name?.type === 'JSXIdentifier' &&
163
+ a.name.name === 'data-cd-id'
164
+ ) {
165
+ return true;
166
+ }
167
+ }
168
+ return false;
169
+ }
170
+
171
+ function computeId(componentName: string, idx: number): string {
172
+ // 8 hex chars from Bun.hash — 32 bits of entropy. Per-component collision
173
+ // probability for the canvas-scale we ship (≤ ~300 JSX elements) is < 1e-5.
174
+ // Documented in DDR-019; 16 chars available as a future migration knob.
175
+ return Bun.hash(`${componentName}:${idx}`).toString(16).padStart(16, '0').slice(0, 8);
176
+ }
177
+
178
+ function walkInjectIds(
179
+ program: AnyNode,
180
+ source: string,
181
+ canvasAbsPath: string,
182
+ s: MagicString,
183
+ locator: LocatorMap
184
+ ): void {
185
+ // Stack of enclosing components. JSX outside any component (e.g. a top-level
186
+ // `const x = <div/>;` constant) attributes to "" -- still gets an ID, just
187
+ // scoped under the empty-string bucket. Rare in practice.
188
+ const stack: ComponentFrame[] = [{ componentName: '', jsxIndex: 0, jsxPath: [] }];
189
+ const top = (): ComponentFrame => stack[stack.length - 1] as ComponentFrame;
190
+
191
+ function visit(node: AnyNode): void {
192
+ if (!node || typeof node !== 'object') return;
193
+ if (Array.isArray(node)) {
194
+ for (const c of node) visit(c);
195
+ return;
196
+ }
197
+ if (typeof node.type !== 'string') return;
198
+
199
+ const newComp = componentNameOf(node);
200
+ let pushed = false;
201
+ if (newComp !== null) {
202
+ stack.push({ componentName: newComp, jsxIndex: 0, jsxPath: [] });
203
+ pushed = true;
204
+ }
205
+
206
+ if (node.type === 'JSXElement') {
207
+ const frame = top();
208
+ const opening = node.openingElement;
209
+ const elName = jsxElementName(opening);
210
+ const idx = frame.jsxIndex;
211
+ frame.jsxIndex += 1;
212
+
213
+ if (!hasDataCdIdAttr(opening)) {
214
+ const id = computeId(frame.componentName, idx);
215
+ // Insert " data-cd-id=\"<id>\"" right after the tag name. magic-string's
216
+ // appendLeft puts the new text before any subsequent attribute. JSX
217
+ // attribute order is irrelevant at runtime; visually the new attr lands
218
+ // first, which makes it easy to spot in devtools.
219
+ const insertAt: number | undefined = opening?.name?.end;
220
+ if (typeof insertAt === 'number') {
221
+ s.appendLeft(insertAt, ` data-cd-id="${id}"`);
222
+ const entry: LocatorEntry = {
223
+ canvas: canvasAbsPath,
224
+ line: node.loc?.start?.line ?? lineColFromByte(source, node.start).line,
225
+ col: node.loc?.start?.column ?? lineColFromByte(source, node.start).col,
226
+ jsxPath: [...frame.jsxPath, elName],
227
+ componentName: frame.componentName,
228
+ };
229
+ locator[id] = entry;
230
+ }
231
+ }
232
+
233
+ // Recurse into opening attributes + children, with the jsxPath extended.
234
+ frame.jsxPath.push(elName);
235
+ if (opening) visit(opening.attributes);
236
+ visit(node.children);
237
+ frame.jsxPath.pop();
238
+
239
+ if (pushed) stack.pop();
240
+ return;
241
+ }
242
+
243
+ // Generic recursion — visit every own value that isn't location metadata.
244
+ for (const k of Object.keys(node)) {
245
+ if (k === 'loc' || k === 'range' || k === 'start' || k === 'end' || k === 'type') continue;
246
+ visit(node[k]);
247
+ }
248
+
249
+ if (pushed) stack.pop();
250
+ }
251
+
252
+ visit(program);
253
+ }
254
+
255
+ // oxc-parser populates node.loc on most nodes, but defensively recompute from
256
+ // the byte offset if it's missing (some attribute children skip it).
257
+ function lineColFromByte(source: string, byte: number | undefined): { line: number; col: number } {
258
+ if (typeof byte !== 'number' || byte < 0) return { line: 1, col: 0 };
259
+ let line = 1;
260
+ let col = 0;
261
+ for (let i = 0; i < byte && i < source.length; i++) {
262
+ if (source.charCodeAt(i) === 10) {
263
+ line += 1;
264
+ col = 0;
265
+ } else {
266
+ col += 1;
267
+ }
268
+ }
269
+ return { line, col };
270
+ }