@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,165 @@
1
+ // canvas-header.ts — Phase 3.6 Task 12a. JSDoc header generator for canvas
2
+ // TSX files. Projects `<Slug>.meta.json` → the leading block comment, so a
3
+ // future Claude (or human) cold-reading the canvas sees DS / opt-out / brief /
4
+ // artboards / handoff command without opening the sidecar.
5
+ //
6
+ // Header is *generated*. `.meta.json` stays the source of truth; this module
7
+ // is a projection. /design:edit step 1 (or scripts/migrate-canvases.ts) calls
8
+ // `applyHeader(canvasPath)` to keep the projection in sync.
9
+ //
10
+ // Idempotency:
11
+ // - First non-empty token is `/**` ending in `*/` → overwrite that block.
12
+ // - Otherwise → prepend a new block.
13
+ //
14
+ // JSX / imports are never touched. The module-level `import` order survives.
15
+
16
+ import path from 'node:path';
17
+
18
+ export interface MetaSidecar {
19
+ title?: string;
20
+ subtitle?: string;
21
+ brief?: string;
22
+ platform?: string;
23
+ designSystem?: string;
24
+ opt_out_scope?: string;
25
+ css_mode?: 'inline' | 'tailwind' | 'modules';
26
+ sections?: Array<{ id: string; artboards?: Array<{ id: string }> }>;
27
+ ai_context?: {
28
+ pinned_decisions?: string[];
29
+ known_quirks?: string[];
30
+ why_this_exists?: string;
31
+ };
32
+ [k: string]: unknown;
33
+ }
34
+
35
+ export interface HeaderOpts {
36
+ /** Bare canvas name (file stem, no extension). Used for @canvas + @handoff. */
37
+ name: string;
38
+ meta: MetaSidecar;
39
+ /** Override the DS slug. Defaults to meta.designSystem ?? 'project'. */
40
+ dsName?: string;
41
+ }
42
+
43
+ /**
44
+ * Build the JSDoc header string (no trailing newline padding — caller picks).
45
+ * Pure function — no fs.
46
+ */
47
+ export function buildHeader(opts: HeaderOpts): string {
48
+ const m = opts.meta;
49
+ const artboardIds =
50
+ (m.sections ?? [])
51
+ .flatMap((s) => s.artboards ?? [])
52
+ .map((a) => a.id)
53
+ .join(' | ') || '—';
54
+ const opt = m.opt_out_scope ?? 'palette';
55
+ const platform = m.platform ?? 'desktop';
56
+ const ds = m.designSystem ?? opts.dsName ?? 'project';
57
+ const cssMode = m.css_mode ?? 'inline';
58
+ const brief = (m.brief ?? '').replace(/\s+/g, ' ').trim() || '—';
59
+ const subtitle = (m.subtitle ?? '').replace(/\s+/g, ' ').trim();
60
+ const slug = kebabSlug(opts.name);
61
+
62
+ const lines = [
63
+ '/**',
64
+ ` * @canvas ${opts.name}${subtitle ? ` — ${subtitle}` : ''}`,
65
+ ` * @ds ${ds}`,
66
+ ` * @platform ${platform}`,
67
+ ` * @opt_out ${opt}`,
68
+ ` * @artboards ${artboardIds}`,
69
+ ` * @brief ${brief}`,
70
+ ` * @stack React 19 · TSX · Bun.build · css_mode=${cssMode}`,
71
+ ` * @history .design/_history/${slug}/`,
72
+ ` * @handoff bunx shadcn add file://./${opts.name}.registry.json`,
73
+ ];
74
+
75
+ const ai = m.ai_context;
76
+ if (ai && (ai.why_this_exists || (ai.pinned_decisions?.length ?? 0) > 0)) {
77
+ lines.push(' *');
78
+ if (ai.why_this_exists) {
79
+ lines.push(` * @notes ${ai.why_this_exists.replace(/\s+/g, ' ').trim()}`);
80
+ }
81
+ for (const dec of ai.pinned_decisions ?? []) {
82
+ lines.push(` * @decision ${dec.replace(/\s+/g, ' ').trim()}`);
83
+ }
84
+ for (const q of ai.known_quirks ?? []) {
85
+ lines.push(` * @quirk ${q.replace(/\s+/g, ' ').trim()}`);
86
+ }
87
+ }
88
+ lines.push(' */');
89
+ return lines.join('\n');
90
+ }
91
+
92
+ /**
93
+ * Pure helper: replace or insert the header in a TSX source string. Returns
94
+ * the new source. If the source already starts with a block comment, that
95
+ * comment is replaced; otherwise a fresh header is prepended.
96
+ */
97
+ export function applyHeaderToSource(source: string, header: string): string {
98
+ const trimmed = source.trimStart();
99
+ const leading = source.length - trimmed.length;
100
+ if (trimmed.startsWith('/**')) {
101
+ const end = trimmed.indexOf('*/');
102
+ if (end > 0) {
103
+ const before = source.slice(0, leading);
104
+ const after = trimmed.slice(end + 2);
105
+ return `${before}${header}${after}`;
106
+ }
107
+ }
108
+ return `${header}\n\n${source}`;
109
+ }
110
+
111
+ /**
112
+ * Read a canvas file + sibling .meta.json, regenerate the JSDoc header, write
113
+ * the canvas back atomically. No-op when content is identical.
114
+ */
115
+ export async function applyHeader(canvasAbsPath: string): Promise<{ changed: boolean }> {
116
+ const ext = path.extname(canvasAbsPath);
117
+ const stem = path.basename(canvasAbsPath, ext);
118
+ const metaPath = path.join(path.dirname(canvasAbsPath), `${stem}.meta.json`);
119
+ const metaFile = Bun.file(metaPath);
120
+ const meta: MetaSidecar = (await metaFile.exists())
121
+ ? ((await metaFile.json()) as MetaSidecar)
122
+ : {};
123
+ const source = await Bun.file(canvasAbsPath).text();
124
+ const header = buildHeader({ name: stem, meta });
125
+ const next = applyHeaderToSource(source, header);
126
+ if (next === source) return { changed: false };
127
+ const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
128
+ await Bun.write(tmp, next);
129
+ const { rename } = await import('node:fs/promises');
130
+ await rename(tmp, canvasAbsPath);
131
+ return { changed: true };
132
+ }
133
+
134
+ function kebabSlug(s: string): string {
135
+ return s
136
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
137
+ .toLowerCase()
138
+ .replace(/[\s_]+/g, '-')
139
+ .replace(/[^a-z0-9-]+/g, '-')
140
+ .replace(/-+/g, '-')
141
+ .replace(/^-|-$/g, '');
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // CLI entry — invoked by /design:edit's pre-flight when canvas-meta has
146
+ // changed since the header was last projected.
147
+
148
+ if (import.meta.main) {
149
+ const argv = process.argv.slice(2);
150
+ if (argv[0] === '--invoke' && argv.length === 2) {
151
+ const canvas = argv[1] as string;
152
+ try {
153
+ const { changed } = await applyHeader(canvas);
154
+ console.log(JSON.stringify({ canvas, changed }));
155
+ process.exit(0);
156
+ } catch (err) {
157
+ const msg = err instanceof Error ? err.message : String(err);
158
+ console.error(`canvas-header: ${msg}`);
159
+ process.exit(2);
160
+ }
161
+ } else {
162
+ console.error('Usage: bun run canvas-header.ts --invoke <canvas-abs-path>');
163
+ process.exit(2);
164
+ }
165
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @file canvas-icons.tsx — Phase 5.1 inline-SVG icon set
3
+ * @scope plugins/design/dev-server/canvas-icons.tsx
4
+ * @purpose Tiny dependency-free Lucide-style icon set for the canvas
5
+ * chrome (tool palette, context toolbar). Each icon is a single
6
+ * `<svg>` with `currentColor` stroke so it inherits the button
7
+ * foreground. Sized 16 px by default; the parent button is
8
+ * 32 × 32, leaving 8 px of optical padding on every side.
9
+ */
10
+
11
+ import type { SVGProps } from 'react';
12
+
13
+ type IconProps = SVGProps<SVGSVGElement> & { size?: number };
14
+
15
+ function Svg({ size = 16, children, ...rest }: IconProps) {
16
+ return (
17
+ <svg
18
+ width={size}
19
+ height={size}
20
+ viewBox="0 0 24 24"
21
+ fill="none"
22
+ stroke="currentColor"
23
+ strokeWidth={1.75}
24
+ strokeLinecap="round"
25
+ strokeLinejoin="round"
26
+ aria-hidden="true"
27
+ focusable="false"
28
+ {...rest}
29
+ >
30
+ {children}
31
+ </svg>
32
+ );
33
+ }
34
+
35
+ export function IconMove(props: IconProps) {
36
+ return (
37
+ <Svg {...props}>
38
+ <path d="M4 4l16 6-7 2-2 7z" />
39
+ </Svg>
40
+ );
41
+ }
42
+
43
+ export function IconHand(props: IconProps) {
44
+ return (
45
+ <Svg {...props}>
46
+ <path d="M8 13V5a1.5 1.5 0 013 0v6" />
47
+ <path d="M11 11V4a1.5 1.5 0 013 0v7" />
48
+ <path d="M14 11V5.5a1.5 1.5 0 013 0V13" />
49
+ <path d="M17 9a1.5 1.5 0 013 0v6a6 6 0 01-6 6h-2a6 6 0 01-5-2.5L4 14a1.5 1.5 0 012-2l2 2" />
50
+ </Svg>
51
+ );
52
+ }
53
+
54
+ export function IconComment(props: IconProps) {
55
+ return (
56
+ <Svg {...props}>
57
+ <path d="M21 12a8 8 0 11-3.3-6.5L21 4l-1 4.3A8 8 0 0121 12z" />
58
+ </Svg>
59
+ );
60
+ }
61
+
62
+ export function IconPen(props: IconProps) {
63
+ return (
64
+ <Svg {...props}>
65
+ <path d="M14.5 4.5l5 5L8 21H3v-5z" />
66
+ <path d="M13 6l5 5" />
67
+ </Svg>
68
+ );
69
+ }
70
+
71
+ export function IconRect(props: IconProps) {
72
+ return (
73
+ <Svg {...props}>
74
+ <rect x="4" y="5" width="16" height="14" rx="1.5" />
75
+ </Svg>
76
+ );
77
+ }
78
+
79
+ export function IconEllipse(props: IconProps) {
80
+ return (
81
+ <Svg {...props}>
82
+ <ellipse cx="12" cy="12" rx="8" ry="6" />
83
+ </Svg>
84
+ );
85
+ }
86
+
87
+ export function IconArrow(props: IconProps) {
88
+ return (
89
+ <Svg {...props}>
90
+ <path d="M4 12h14" />
91
+ <path d="M13 6l6 6-6 6" />
92
+ </Svg>
93
+ );
94
+ }
95
+
96
+ export function IconEraser(props: IconProps) {
97
+ return (
98
+ <Svg {...props}>
99
+ <path d="M16 3l5 5-11 11H4v-6z" />
100
+ <path d="M8 14l5 5" />
101
+ </Svg>
102
+ );
103
+ }
104
+
105
+ export function IconPresentation(props: IconProps) {
106
+ return (
107
+ <Svg {...props}>
108
+ <rect x="3" y="4" width="18" height="12" rx="1.5" />
109
+ <path d="M9 21l3-5 3 5" />
110
+ </Svg>
111
+ );
112
+ }
113
+
114
+ export function IconChevronDown(props: IconProps) {
115
+ return (
116
+ <Svg {...props}>
117
+ <path d="M6 9l6 6 6-6" />
118
+ </Svg>
119
+ );
120
+ }
121
+
122
+ export const TOOL_ICONS: Record<string, (p: IconProps) => JSX.Element> = {
123
+ move: IconMove,
124
+ hand: IconHand,
125
+ comment: IconComment,
126
+ pen: IconPen,
127
+ rect: IconRect,
128
+ ellipse: IconEllipse,
129
+ arrow: IconArrow,
130
+ eraser: IconEraser,
131
+ };
@@ -0,0 +1,260 @@
1
+ // canvas-lib inlining for /design:handoff (Phase 3.6.1 Task 9; per DDR-025 the
2
+ // lib source now ships with the dev-server install).
3
+ //
4
+ // `/design:handoff` emits a self-contained shadcn registry-item.json. Canvases
5
+ // import their envelope + helpers from `@maude/canvas-lib`, which the dev-server
6
+ // resolves to its bundled `plugins/design/dev-server/canvas-lib.tsx`. The
7
+ // handoff drop must inline every used export so the consumer never sees the
8
+ // `@maude/canvas-lib` specifier — it's a dev-time virtual module, not a real
9
+ // npm dep.
10
+ //
11
+ // Strategy:
12
+ // 1. Parse the dev-server-bundled canvas-lib.tsx via oxc-parser. Walk top-level declarations
13
+ // to build a Map<name, { source, deps }>:
14
+ // - source: byte range of the declaration (including JSDoc immediately
15
+ // above, when present).
16
+ // - deps: other top-level identifiers referenced inside the body.
17
+ // 2. Scan the canvas TSX for `import { ... } from "@maude/canvas-lib"`.
18
+ // Collect the named imports.
19
+ // 3. Transitively resolve deps: each import drags in helpers it calls,
20
+ // which drag in helpers THEY call, etc.
21
+ // 4. Strip the import line.
22
+ // 5. Append the resolved function/const bodies AFTER the canvas's default
23
+ // export. (Placing them after default avoids "use before declare" issues
24
+ // in dev-mode tooling — JSX inside the component reads the outer scope.)
25
+ //
26
+ // Pure module — caller persists. Tested.
27
+
28
+ import { parseSync } from 'oxc-parser';
29
+
30
+ // biome-ignore lint/suspicious/noExplicitAny: oxc AST nodes are heterogeneous.
31
+ type AnyNode = any;
32
+
33
+ export interface LibExport {
34
+ name: string;
35
+ /** Full declaration source, including leading JSDoc block. */
36
+ source: string;
37
+ /** Other top-level identifiers this declaration references. */
38
+ deps: string[];
39
+ }
40
+
41
+ export type LibMap = Map<string, LibExport>;
42
+
43
+ /**
44
+ * Parse a canvas-lib TSX source into a Map keyed by named-export identifier.
45
+ * For each entry, `source` is the verbatim declaration text (with JSDoc
46
+ * comment if it precedes the declaration), and `deps` is the set of other
47
+ * top-level names the declaration body references.
48
+ */
49
+ export function buildLibMap(libPath: string, libSource: string): LibMap {
50
+ const parsed = parseSync(libPath, libSource, { sourceType: 'module' });
51
+ if (parsed.errors && parsed.errors.length > 0) {
52
+ const first = parsed.errors[0];
53
+ throw new Error(
54
+ `oxc-parser failed on ${libPath} (${parsed.errors.length} errors). First: ${first?.message ?? 'unknown'}`
55
+ );
56
+ }
57
+
58
+ // First pass — collect every top-level binding name (exports + internals).
59
+ const topLevelNames = new Set<string>();
60
+ for (const node of parsed.program.body as AnyNode[]) {
61
+ for (const n of namesFromTopLevel(node)) topLevelNames.add(n);
62
+ }
63
+
64
+ // Second pass — record exported declarations with their source range +
65
+ // identifier references inside the body.
66
+ const map: LibMap = new Map();
67
+ // Also keep internal (non-exported) declarations indexed for transitive
68
+ // resolution — when an exported helper calls an internal helper, that
69
+ // internal must travel along.
70
+ const internals = new Map<string, LibExport>();
71
+
72
+ for (const node of parsed.program.body as AnyNode[]) {
73
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
74
+ for (const name of namesFromDecl(node.declaration)) {
75
+ const range = nodeRangeWithComment(libSource, node);
76
+ const source = libSource.slice(range.start, range.end);
77
+ // Strip the leading `export` token from the captured source — inlined
78
+ // bodies must NOT re-export.
79
+ const stripped = source.replace(/^export\s+/m, '');
80
+ const deps = collectIdentifierRefs(node.declaration, topLevelNames, name);
81
+ map.set(name, { name, source: stripped, deps: [...deps] });
82
+ }
83
+ } else if (
84
+ node.type === 'FunctionDeclaration' ||
85
+ node.type === 'VariableDeclaration' ||
86
+ node.type === 'ClassDeclaration'
87
+ ) {
88
+ for (const name of namesFromDecl(node)) {
89
+ const range = nodeRangeWithComment(libSource, node);
90
+ const source = libSource.slice(range.start, range.end);
91
+ const deps = collectIdentifierRefs(node, topLevelNames, name);
92
+ internals.set(name, { name, source, deps: [...deps] });
93
+ }
94
+ }
95
+ }
96
+
97
+ // Merge internals into the map under their own names — they're resolvable
98
+ // by the inliner when an export depends on them. Internals are never
99
+ // surfaced as user-requested imports but are reachable transitively.
100
+ for (const [name, info] of internals) {
101
+ if (!map.has(name)) map.set(name, info);
102
+ }
103
+
104
+ return map;
105
+ }
106
+
107
+ function namesFromTopLevel(node: AnyNode): string[] {
108
+ if (!node || typeof node !== 'object') return [];
109
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
110
+ return namesFromDecl(node.declaration);
111
+ }
112
+ if (
113
+ node.type === 'FunctionDeclaration' ||
114
+ node.type === 'ClassDeclaration' ||
115
+ node.type === 'VariableDeclaration'
116
+ ) {
117
+ return namesFromDecl(node);
118
+ }
119
+ return [];
120
+ }
121
+
122
+ function namesFromDecl(decl: AnyNode): string[] {
123
+ if (!decl) return [];
124
+ if (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration') {
125
+ return decl.id?.name ? [decl.id.name as string] : [];
126
+ }
127
+ if (decl.type === 'VariableDeclaration') {
128
+ const out: string[] = [];
129
+ for (const d of decl.declarations ?? []) {
130
+ if (d.id?.type === 'Identifier' && d.id.name) out.push(d.id.name as string);
131
+ }
132
+ return out;
133
+ }
134
+ return [];
135
+ }
136
+
137
+ /**
138
+ * Walk an AST subtree collecting Identifier names that match the top-level
139
+ * registry, excluding `selfName` (no self-reference).
140
+ */
141
+ function collectIdentifierRefs(
142
+ decl: AnyNode,
143
+ registry: Set<string>,
144
+ selfName: string
145
+ ): Set<string> {
146
+ const found = new Set<string>();
147
+ function visit(node: AnyNode): void {
148
+ if (!node || typeof node !== 'object') return;
149
+ if (Array.isArray(node)) {
150
+ for (const c of node) visit(c);
151
+ return;
152
+ }
153
+ if (typeof node.type !== 'string') return;
154
+ if (node.type === 'Identifier' && typeof node.name === 'string') {
155
+ if (node.name !== selfName && registry.has(node.name)) found.add(node.name);
156
+ }
157
+ // JSXIdentifier looks the same shape — treat like Identifier.
158
+ if (node.type === 'JSXIdentifier' && typeof node.name === 'string') {
159
+ if (node.name !== selfName && registry.has(node.name)) found.add(node.name);
160
+ }
161
+ for (const k of Object.keys(node)) {
162
+ if (k === 'loc' || k === 'range' || k === 'start' || k === 'end' || k === 'type') continue;
163
+ visit(node[k]);
164
+ }
165
+ }
166
+ visit(decl);
167
+ return found;
168
+ }
169
+
170
+ /**
171
+ * Find the source-range start of a node, extended backwards over any
172
+ * immediately-preceding block-comment (JSDoc) so the inlined declaration
173
+ * keeps its docs.
174
+ */
175
+ function nodeRangeWithComment(source: string, node: AnyNode): { start: number; end: number } {
176
+ const end = node.end as number;
177
+ let start = node.start as number;
178
+ // Walk back over whitespace; if we hit `*/` we extend through the matching
179
+ // `/*`.
180
+ let i = start - 1;
181
+ while (i >= 0 && /\s/.test(source[i] ?? '')) i--;
182
+ if (i >= 1 && source[i - 1] === '*' && source[i] === '/') {
183
+ // We're at the `*/` — find matching `/*`.
184
+ const open = source.lastIndexOf('/*', i);
185
+ if (open >= 0) start = open;
186
+ }
187
+ return { start, end };
188
+ }
189
+
190
+ export interface InlineResult {
191
+ /** Canvas source with the import stripped + helpers appended. */
192
+ content: string;
193
+ /** True when an `@maude/canvas-lib` import was found + removed. */
194
+ droppedImport: boolean;
195
+ /** Sorted list of helper names inlined (including transitive). */
196
+ inlined: string[];
197
+ }
198
+
199
+ /**
200
+ * Replace `import { ... } from "@maude/canvas-lib"` with the resolved bodies
201
+ * of every named import (+ their transitive dependencies). Returns the
202
+ * rewritten source.
203
+ */
204
+ export function inlineUsedExports(canvasSource: string, libMap: LibMap): InlineResult {
205
+ // 1. Locate the import line. We tolerate single OR double quotes, type-only
206
+ // imports (rare), trailing commas, multi-line shapes.
207
+ const importRe = /\bimport\s+(?:type\s+)?\{([^}]+)\}\s*from\s*["']@maude\/canvas-lib["']\s*;?/m;
208
+ const m = importRe.exec(canvasSource);
209
+ if (!m) {
210
+ return { content: canvasSource, droppedImport: false, inlined: [] };
211
+ }
212
+ const importList = (m[1] ?? '')
213
+ .split(',')
214
+ .map((s) => s.trim())
215
+ .filter(Boolean)
216
+ // Drop `type` prefix on individual names + handle `X as Y` aliases (rare).
217
+ .map((s) => s.replace(/^type\s+/, '').split(/\s+as\s+/)[0])
218
+ .filter((s): s is string => Boolean(s));
219
+
220
+ // 2. Resolve transitive deps. BFS over the libMap.
221
+ const wanted = new Set<string>();
222
+ const queue = [...importList];
223
+ while (queue.length > 0) {
224
+ const name = queue.shift() as string;
225
+ if (wanted.has(name)) continue;
226
+ const info = libMap.get(name);
227
+ if (!info) {
228
+ throw new Error(
229
+ `[canvas-lib-inline] Canvas imports '${name}' from @maude/canvas-lib but the lib has no such export.`
230
+ );
231
+ }
232
+ wanted.add(name);
233
+ for (const dep of info.deps) {
234
+ if (!wanted.has(dep)) queue.push(dep);
235
+ }
236
+ }
237
+
238
+ // 3. Strip the import line.
239
+ let out = canvasSource.slice(0, m.index) + canvasSource.slice(m.index + m[0].length);
240
+ // Cleanup: collapse runs of blank lines we just opened up.
241
+ out = out.replace(/\n{3,}/g, '\n\n');
242
+
243
+ // 4. Append the resolved declarations at the file's tail. The plan calls
244
+ // for "before export default", but a trailing block works in modern JS
245
+ // (function declarations hoist, top-level consts referenced inside the
246
+ // default export are evaluated at module init). Place after a clear
247
+ // separator + section comment so cold readers grok what's happening.
248
+ const ordered = [...wanted].sort();
249
+ const banner =
250
+ '\n\n// ─────────────────────────────────────────────────────────────────────────────\n' +
251
+ '// Canvas-lib helpers (inlined by /design:handoff). Self-contained drop —\n' +
252
+ '// no dev-time specifier is referenced from this file.\n\n';
253
+ const bodies = ordered.map((n) => (libMap.get(n)?.source ?? '').trimEnd()).join('\n\n');
254
+
255
+ return {
256
+ content: `${out.trimEnd()}${banner}${bodies}\n`,
257
+ droppedImport: true,
258
+ inlined: ordered,
259
+ };
260
+ }
@@ -0,0 +1,85 @@
1
+ // Virtual-module resolver for `@maude/canvas-lib` (Phase 3.6.1; relocated 4.0.5).
2
+ //
3
+ // Canvases import the shared canvas library via the specifier `@maude/canvas-lib`.
4
+ // At build time we redirect that to the dev-server-bundled source at
5
+ // `plugins/design/dev-server/canvas-lib.tsx` so:
6
+ //
7
+ // - the lib ships with the dev-server install (single source of truth — see
8
+ // DDR-025; reverses DDR-022's "project-owned source under <designRoot>/_lib/"),
9
+ // - Bun.build bundles the actually-used exports into the canvas module via
10
+ // normal tree-shaking,
11
+ // - `/design:handoff` can strip the import + inline the same exports for the
12
+ // emitted registry-item (see canvas-lib-inline.ts).
13
+ //
14
+ // Two surfaces:
15
+ //
16
+ // - `canvasLibResolver()` — Bun.build plugin. Registered alongside
17
+ // `exact-externals` in canvas-build.ts. Must run FIRST so the bare specifier
18
+ // gets claimed before any other resolver.
19
+ // - `readCanvasLibSource()` — small async helper used by handoff.ts
20
+ // to read the lib source once for inlining.
21
+ //
22
+ // Failure mode: if the dev-server's bundled canvas-lib is missing, the install
23
+ // is corrupt; canvas-build.ts surfaces a re-install hint.
24
+
25
+ import { existsSync } from 'node:fs';
26
+ import path from 'node:path';
27
+
28
+ import type { BunPlugin } from 'bun';
29
+
30
+ export const CANVAS_LIB_SPECIFIER = '@maude/canvas-lib';
31
+
32
+ /**
33
+ * Returns the dev-server-internal canvas-lib path. The `_designRoot` parameter
34
+ * is retained for one minor (back-compat with callers we don't control) but
35
+ * ignored — canvas-lib now ships with the dev-server install (DDR-025).
36
+ */
37
+ export function canvasLibPath(_designRoot?: string): string {
38
+ return path.join(import.meta.dir, 'canvas-lib.tsx');
39
+ }
40
+
41
+ export interface CanvasLibResolverOptions {
42
+ /** Throw at resolve-time when the lib file is missing. Defaults to true. */
43
+ failLoud?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Bun.build plugin factory. Maps `@maude/canvas-lib` → the dev-server-bundled
48
+ * `canvas-lib.tsx`. No-op for any other specifier.
49
+ */
50
+ export function canvasLibResolver(
51
+ _designRoot?: string,
52
+ opts: CanvasLibResolverOptions = {}
53
+ ): BunPlugin {
54
+ const target = canvasLibPath();
55
+ const failLoud = opts.failLoud !== false;
56
+ return {
57
+ name: 'maude-canvas-lib',
58
+ setup(builder) {
59
+ builder.onResolve({ filter: /^@maude\/canvas-lib$/ }, () => {
60
+ if (failLoud && !existsSync(target)) {
61
+ throw new Error(
62
+ `[@maude/canvas-lib] canvas library missing at ${target} — dev-server install is corrupt; re-install @1agh/maude.`
63
+ );
64
+ }
65
+ return { path: target };
66
+ });
67
+ },
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Read the on-disk canvas-lib source. Returns the raw TSX. Throws if the file
73
+ * is missing. Used by handoff.ts to build the export-name → source map for
74
+ * inlining used helpers into the emitted registry-item.
75
+ */
76
+ export async function readCanvasLibSource(_designRoot?: string): Promise<string> {
77
+ const p = canvasLibPath();
78
+ const f = Bun.file(p);
79
+ if (!(await f.exists())) {
80
+ throw new Error(
81
+ `[@maude/canvas-lib] canvas library missing at ${p} — dev-server install is corrupt; re-install @1agh/maude.`
82
+ );
83
+ }
84
+ return f.text();
85
+ }