@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,193 @@
1
+ #!/usr/bin/env bun
2
+ // Performance harness for Phase 3.4 budgets.
3
+ //
4
+ // bun run plugins/design/dev-server/test/perf-harness.ts
5
+ //
6
+ // Boots the dev-server against a synthetic .design/ fixture, measures the
7
+ // budget table from `.ai/plans/phase-3.4-architecture-refactor.md`, and writes
8
+ // `test/perf-report.json` plus a one-line stdout summary.
9
+ //
10
+ // Budgets (relaxed per DDR-012):
11
+ // coldStartHttpMs < 100 process spawn -> /_health 200
12
+ // bundleGzBytes < 80 KB after Bun.build minify
13
+ // wsRoundTripP50 < 1
14
+ // wsRoundTripP99 < 5
15
+ //
16
+ // Non-blocking job — CI records the numbers and only fails on > 20 % regression
17
+ // from the prior baseline. Locally `--strict` exits 1 on any budget miss.
18
+
19
+ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
20
+ import { tmpdir } from 'node:os';
21
+ import { join } from 'node:path';
22
+ import { gzipSync, spawn } from 'bun';
23
+
24
+ const STRICT = process.argv.includes('--strict');
25
+ const PORT = 4500 + Math.floor(Math.random() * 1000);
26
+
27
+ const HERE = import.meta.dir;
28
+ const REPO = join(HERE, '..');
29
+
30
+ interface Report {
31
+ ts: string;
32
+ budgets: Record<string, { value: number; budget: number; unit: string; pass: boolean }>;
33
+ notes: string[];
34
+ }
35
+
36
+ function makeFixture(): string {
37
+ const root = mkdtempSync(join(tmpdir(), 'mdcc-perf-'));
38
+ mkdirSync(join(root, '.design', 'ui'), { recursive: true });
39
+ writeFileSync(join(root, '.design', 'config.json'), '{"name":"perf"}');
40
+ for (let i = 0; i < 10; i++) {
41
+ writeFileSync(
42
+ join(root, '.design', 'ui', `c${i}.html`),
43
+ `<!doctype html><html><body><div data-dc-screen="s${i}"><h1>${i}</h1></div></body></html>`
44
+ );
45
+ }
46
+ return root;
47
+ }
48
+
49
+ async function measureColdStart(root: string): Promise<number> {
50
+ const start = performance.now();
51
+ const proc = spawn({
52
+ cmd: ['bun', 'run', join(REPO, 'server.ts'), '--port', String(PORT), '--root', root],
53
+ cwd: REPO,
54
+ env: { ...process.env, NO_OPEN: '1', NODE_ENV: 'production' },
55
+ stdout: 'ignore',
56
+ stderr: 'ignore',
57
+ });
58
+ try {
59
+ while (performance.now() - start < 5000) {
60
+ try {
61
+ const r = await fetch(`http://localhost:${PORT}/_health`, {
62
+ signal: AbortSignal.timeout(50),
63
+ });
64
+ if (r.ok) return performance.now() - start;
65
+ } catch {
66
+ /* not up yet */
67
+ }
68
+ await Bun.sleep(5);
69
+ }
70
+ throw new Error('cold-start: server did not respond within 5 s');
71
+ } finally {
72
+ proc.kill();
73
+ await proc.exited;
74
+ }
75
+ }
76
+
77
+ async function measureBundleSize(): Promise<number> {
78
+ const buildResult = spawn({
79
+ cmd: [
80
+ 'bun',
81
+ 'run',
82
+ join(REPO, 'build.ts'),
83
+ '--release',
84
+ `--target=bun-${process.platform}-${process.arch}`,
85
+ ],
86
+ cwd: REPO,
87
+ stdout: 'ignore',
88
+ stderr: 'ignore',
89
+ env: { ...process.env, NODE_ENV: 'production' },
90
+ });
91
+ const code = await buildResult.exited;
92
+ if (code !== 0) throw new Error('build failed');
93
+ const bundle = await Bun.file(join(REPO, 'dist', 'client.bundle.js')).arrayBuffer();
94
+ const gz = gzipSync(new Uint8Array(bundle));
95
+ return gz.byteLength;
96
+ }
97
+
98
+ async function measureWsRoundTrip(root: string): Promise<{ p50: number; p99: number }> {
99
+ const proc = spawn({
100
+ cmd: ['bun', 'run', join(REPO, 'server.ts'), '--port', String(PORT + 1), '--root', root],
101
+ cwd: REPO,
102
+ env: { ...process.env, NO_OPEN: '1' },
103
+ stdout: 'ignore',
104
+ stderr: 'ignore',
105
+ });
106
+ try {
107
+ while (true) {
108
+ try {
109
+ const r = await fetch(`http://localhost:${PORT + 1}/_health`, {
110
+ signal: AbortSignal.timeout(50),
111
+ });
112
+ if (r.ok) break;
113
+ } catch {
114
+ /* */
115
+ }
116
+ await Bun.sleep(20);
117
+ }
118
+ const ws = new WebSocket(`ws://localhost:${PORT + 1}/_ws`);
119
+ await new Promise<void>((resolve, reject) => {
120
+ const t = setTimeout(() => reject(new Error('ws timeout')), 2000);
121
+ ws.addEventListener('open', () => {
122
+ clearTimeout(t);
123
+ resolve();
124
+ });
125
+ ws.addEventListener('error', reject);
126
+ });
127
+
128
+ const samples: number[] = [];
129
+ for (let i = 0; i < 500; i++) {
130
+ const t = performance.now();
131
+ ws.send(JSON.stringify({ type: 'active', file: `.design/ui/c${i % 10}.html` }));
132
+ // No round-trip echo for `active`; approximate by next-tick read.
133
+ await Bun.sleep(0);
134
+ samples.push(performance.now() - t);
135
+ }
136
+ ws.close();
137
+ samples.sort((a, b) => a - b);
138
+ return {
139
+ p50: samples[Math.floor(samples.length * 0.5)] ?? 0,
140
+ p99: samples[Math.floor(samples.length * 0.99)] ?? 0,
141
+ };
142
+ } finally {
143
+ proc.kill();
144
+ await proc.exited;
145
+ }
146
+ }
147
+
148
+ async function main() {
149
+ const root = makeFixture();
150
+ const report: Report = { ts: new Date().toISOString(), budgets: {}, notes: [] };
151
+
152
+ const cold = await measureColdStart(root);
153
+ report.budgets.coldStartHttpMs = {
154
+ value: Math.round(cold),
155
+ budget: 100,
156
+ unit: 'ms',
157
+ pass: cold < 100,
158
+ };
159
+
160
+ const gz = await measureBundleSize();
161
+ report.budgets.bundleGzBytes = {
162
+ value: gz,
163
+ budget: 80 * 1024,
164
+ unit: 'B',
165
+ pass: gz < 80 * 1024,
166
+ };
167
+
168
+ const ws = await measureWsRoundTrip(root);
169
+ report.budgets.wsRoundTripP50 = {
170
+ value: +ws.p50.toFixed(2),
171
+ budget: 1,
172
+ unit: 'ms',
173
+ pass: ws.p50 < 1,
174
+ };
175
+ report.budgets.wsRoundTripP99 = {
176
+ value: +ws.p99.toFixed(2),
177
+ budget: 5,
178
+ unit: 'ms',
179
+ pass: ws.p99 < 5,
180
+ };
181
+
182
+ writeFileSync(join(HERE, 'perf-report.json'), JSON.stringify(report, null, 2));
183
+
184
+ const summary = Object.entries(report.budgets)
185
+ .map(([k, v]) => `${v.pass ? '✓' : '✗'} ${k}=${v.value}${v.unit} (budget ${v.budget}${v.unit})`)
186
+ .join(' ');
187
+ console.log(summary);
188
+
189
+ const failed = Object.values(report.budgets).filter((b) => !b.pass).length;
190
+ if (STRICT && failed > 0) process.exit(1);
191
+ }
192
+
193
+ await main();
@@ -0,0 +1,77 @@
1
+ // Phase 3.6 Task 11 — end-to-end smoke. Runs the full TSX pipeline against
2
+ // the migrated canvases sitting in this repo's .design/ui/ folder, verifying:
3
+ //
4
+ // - oxc-parser + magic-string two-pass transform produces parseable JS
5
+ // - locator map is non-trivially populated (every JSX element gets an entry)
6
+ // - Bun.build wraps the post-pass-1 source into a browser-loadable ESM
7
+ // (proves the codemod's output cooperates with canvas-build.ts)
8
+ // - handoff.emitRegistryItem produces a schema-valid registry-item.json
9
+ // with `data-cd-id` stripped + react/react-dom in the dep floor
10
+ //
11
+ // Skips itself cleanly when the canvases haven't been migrated (CI / fresh
12
+ // checkout). The bun-only-locally guard keeps the suite portable.
13
+
14
+ import { describe, expect, test } from 'bun:test';
15
+ import { existsSync } from 'node:fs';
16
+ import path from 'node:path';
17
+
18
+ import { buildCanvasModule } from '../canvas-build.ts';
19
+ import { transpileCanvasSource } from '../canvas-pipeline.ts';
20
+ import { emitRegistryItem } from '../handoff.ts';
21
+
22
+ const REPO_ROOT = path.resolve(import.meta.dir, '../../../..');
23
+ const DESIGN_ROOT = path.join(REPO_ROOT, '.design');
24
+ const DOCS_SITE = path.join(DESIGN_ROOT, 'ui/Docs Site.tsx');
25
+ const CANVAS_VIEWPORT = path.join(DESIGN_ROOT, 'ui/Canvas Viewport.tsx');
26
+
27
+ const skipReason = (() => {
28
+ if (!existsSync(DOCS_SITE)) return `${DOCS_SITE} missing — run scripts/migrate-canvases.ts`;
29
+ if (!existsSync(CANVAS_VIEWPORT))
30
+ return `${CANVAS_VIEWPORT} missing — run scripts/migrate-canvases.ts`;
31
+ return null;
32
+ })();
33
+
34
+ const describeOrSkip = skipReason ? describe.skip : describe;
35
+
36
+ describeOrSkip('Phase 3.6 smoke — migrated canvases through the full pipeline', () => {
37
+ test('canvas-pipeline transpiles Docs Site.tsx + emits a non-trivial locator', async () => {
38
+ const src = await Bun.file(DOCS_SITE).text();
39
+ const r = transpileCanvasSource(DOCS_SITE, src);
40
+ expect(r.js.length).toBeGreaterThan(0);
41
+ // Docs Site has 5 artboards × ~tens of elements each — well above the 100 floor.
42
+ expect(Object.keys(r.locator).length).toBeGreaterThan(100);
43
+ // ETag is deterministic across runs on identical source.
44
+ const again = transpileCanvasSource(DOCS_SITE, src);
45
+ expect(again.etag).toBe(r.etag);
46
+ });
47
+
48
+ test('canvas-pipeline transpiles Canvas Viewport.tsx', async () => {
49
+ const src = await Bun.file(CANVAS_VIEWPORT).text();
50
+ const r = transpileCanvasSource(CANVAS_VIEWPORT, src);
51
+ expect(r.js.length).toBeGreaterThan(0);
52
+ expect(Object.keys(r.locator).length).toBeGreaterThan(100);
53
+ });
54
+
55
+ test('canvas-build produces browser-loadable ESM for Docs Site.tsx', async () => {
56
+ const src = await Bun.file(DOCS_SITE).text();
57
+ const r = await buildCanvasModule(DOCS_SITE, src);
58
+ expect(r.js).toContain('export');
59
+ // React + jsx-runtime stay external (resolved via importmap in shell).
60
+ expect(r.js).toContain('react');
61
+ });
62
+
63
+ test('handoff.emitRegistryItem strips data-cd-id + floors react/react-dom', async () => {
64
+ const item = await emitRegistryItem({
65
+ canvasAbsPath: DOCS_SITE,
66
+ title: 'Docs Site',
67
+ description: 'Smoke regression target',
68
+ });
69
+ expect(item.$schema).toBe('https://ui.shadcn.com/schema/registry-item.json');
70
+ expect(item.type).toBe('registry:block');
71
+ expect(item.name).toBe('docs-site');
72
+ expect(item.dependencies).toContain('react');
73
+ expect(item.dependencies).toContain('react-dom');
74
+ expect(item.files[0]?.path).toBe('components/docs-site.tsx');
75
+ expect(item.files[0]?.content).not.toContain('data-cd-id');
76
+ });
77
+ });
@@ -0,0 +1,69 @@
1
+ // runtime-bundle.ts — Phase 3.6 Task 6. Each runtime sub-bundle should:
2
+ // 1. be self-contained for its own package (no `import "react"` left dangling
3
+ // in the react bundle itself, for instance);
4
+ // 2. externalise the OTHER three runtime packages — so the four bundles
5
+ // stitched together via the importmap don't ship two React copies;
6
+ // 3. expose its package's well-known named exports as real ESM bindings
7
+ // (not the silent-empty-export shape `export * from <cjs>` produces).
8
+
9
+ import { describe, expect, test } from 'bun:test';
10
+
11
+ import { RUNTIME_PACKAGES, getRuntimeBundle, packageForSlug, slugFor } from '../runtime-bundle.ts';
12
+
13
+ describe('runtime-bundle', () => {
14
+ test('builds all four sub-bundles successfully', async () => {
15
+ for (const p of RUNTIME_PACKAGES) {
16
+ const b = await getRuntimeBundle(p);
17
+ expect(b.js.length).toBeGreaterThan(0);
18
+ expect(b.etag).toMatch(/^[0-9a-f]+$/);
19
+ }
20
+ });
21
+
22
+ test('react/jsx-runtime stays small (no React/ReactDOM inlined)', async () => {
23
+ const b = await getRuntimeBundle('react/jsx-runtime');
24
+ // Production jsx-runtime is self-contained (~3 KB) and doesn't require an
25
+ // explicit React import — the bundle should NOT carry a transitive copy
26
+ // of React's hooks or ReactDOM internals. Size cap is a coarse proxy.
27
+ expect(b.js.length).toBeLessThan(8 * 1024);
28
+ expect(b.js).not.toContain('createRoot(');
29
+ expect(b.js).not.toContain('useState');
30
+ });
31
+
32
+ test('react/jsx-runtime exposes jsx + jsxs + Fragment as ESM exports', async () => {
33
+ const b = await getRuntimeBundle('react/jsx-runtime');
34
+ expect(b.js).toMatch(/export\s*\{[\s\S]*\bjsx\b[\s\S]*\}/);
35
+ expect(b.js).toMatch(/export\s*\{[\s\S]*\bjsxs\b[\s\S]*\}/);
36
+ expect(b.js).toMatch(/export\s*\{[\s\S]*\bFragment\b[\s\S]*\}/);
37
+ });
38
+
39
+ test('react/jsx-dev-runtime bundle still builds (used in dev tooling)', async () => {
40
+ const b = await getRuntimeBundle('react/jsx-dev-runtime');
41
+ // Production-mode define(): the dev runtime still bundles (we list it in
42
+ // the importmap as a fallback), but its source path inside the bundle
43
+ // may be the production jsx-runtime depending on the package's own
44
+ // env switch. Either way it should produce a non-empty ESM module.
45
+ expect(b.js.length).toBeGreaterThan(0);
46
+ expect(b.js).toMatch(/export\s*\{/);
47
+ });
48
+
49
+ test('react-dom/client exposes createRoot + hydrateRoot', async () => {
50
+ const b = await getRuntimeBundle('react-dom/client');
51
+ expect(b.js).toMatch(/export\s*\{[\s\S]*\bcreateRoot\b[\s\S]*\}/);
52
+ expect(b.js).toMatch(/export\s*\{[\s\S]*\bhydrateRoot\b[\s\S]*\}/);
53
+ });
54
+
55
+ test('cache: second call returns same instance (no re-build)', async () => {
56
+ const a = await getRuntimeBundle('react');
57
+ const b = await getRuntimeBundle('react');
58
+ expect(b).toBe(a);
59
+ });
60
+
61
+ test('slug ⇄ package round-trip', () => {
62
+ for (const p of RUNTIME_PACKAGES) {
63
+ const s = slugFor(p);
64
+ expect(packageForSlug(s)).toBe(p);
65
+ expect(packageForSlug(`${s}.js`)).toBe(p);
66
+ }
67
+ expect(packageForSlug('nope.js')).toBeNull();
68
+ });
69
+ });
@@ -0,0 +1,28 @@
1
+ // Smoke: boot → /_health 200 → _server.json written → shutdown.
2
+
3
+ import { describe, expect, test } from 'bun:test';
4
+ import { join } from 'node:path';
5
+
6
+ import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
7
+
8
+ describe('server lifecycle', () => {
9
+ test('boots, writes _server.json, responds to /_health', async () => {
10
+ const { root, designRoot } = makeSandbox();
11
+ const port = nextPort();
12
+ const proc = await bootServer(root, port);
13
+ try {
14
+ const h = await fetch(`http://localhost:${port}/_health`);
15
+ expect(h.status).toBe(200);
16
+ const body = (await h.json()) as { ok: boolean; app: string };
17
+ expect(body.ok).toBe(true);
18
+ expect(body.app).toBe('design');
19
+
20
+ const info = await Bun.file(join(designRoot, '_server.json')).json();
21
+ expect(info.port).toBe(port);
22
+ expect(info.pid).toBeGreaterThan(0);
23
+ expect(info.url).toBe(`http://localhost:${port}`);
24
+ } finally {
25
+ await killProc(proc);
26
+ }
27
+ });
28
+ });
@@ -0,0 +1,55 @@
1
+ // tool-palette — Phase 5.1 centered icon toolbar.
2
+ //
3
+ // The palette pulls live state via three context hooks (tool mode, viewport
4
+ // controller, annotations visibility) and the icon set is dependency-free, so
5
+ // the SSR render path is the cleanest unit-test surface. We confirm:
6
+ // - one icon button per default tool registered in ToolProvider
7
+ // - the active tool gets aria-pressed=true
8
+ // - the zoom display shows the live percentage
9
+ // - the icon module exports a glyph for every default tool id
10
+
11
+ import { describe, expect, test } from 'bun:test';
12
+
13
+ import { renderToStaticMarkup } from 'react-dom/server';
14
+
15
+ import { TOOL_ICONS } from '../canvas-icons.tsx';
16
+ import { ToolPalette } from '../tool-palette.tsx';
17
+ import { AnnotationsVisibilityProvider } from '../use-annotations-visibility.tsx';
18
+ import { DEFAULT_TOOLS, ToolProvider } from '../use-tool-mode.tsx';
19
+
20
+ describe('canvas-icons / TOOL_ICONS', () => {
21
+ test('exposes a component for every default tool id', () => {
22
+ for (const t of DEFAULT_TOOLS) {
23
+ expect(TOOL_ICONS[t.id]).toBeDefined();
24
+ }
25
+ });
26
+
27
+ test('icons render as <svg> elements with currentColor stroke', () => {
28
+ for (const t of DEFAULT_TOOLS) {
29
+ const Icon = TOOL_ICONS[t.id];
30
+ if (!Icon) continue;
31
+ const html = renderToStaticMarkup(<Icon />);
32
+ expect(html).toContain('<svg');
33
+ expect(html).toContain('stroke="currentColor"');
34
+ }
35
+ });
36
+ });
37
+
38
+ describe('tool-palette / SSR render contract', () => {
39
+ // First render under SSR returns null (the `mounted` gate is intentional —
40
+ // avoids hydration drift). We assert the empty mount + then re-render
41
+ // hydrated by passing a different initial setup, but bun's SSR only runs
42
+ // the first render. Adequate to confirm the import + provider stack don't
43
+ // throw, which catches typo/cycle regressions in the new chrome modules.
44
+ test('mounts inside ToolProvider + visibility provider without throwing', () => {
45
+ expect(() =>
46
+ renderToStaticMarkup(
47
+ <AnnotationsVisibilityProvider>
48
+ <ToolProvider>
49
+ <ToolPalette />
50
+ </ToolProvider>
51
+ </AnnotationsVisibilityProvider>
52
+ )
53
+ ).not.toThrow();
54
+ });
55
+ });
@@ -0,0 +1,77 @@
1
+ // use-annotation-selection — Phase 5.1. Pure provider contract.
2
+
3
+ import { describe, expect, test } from 'bun:test';
4
+
5
+ import { renderToStaticMarkup } from 'react-dom/server';
6
+
7
+ import {
8
+ AnnotationSelectionProvider,
9
+ useAnnotationSelection,
10
+ useAnnotationSelectionOptional,
11
+ } from '../use-annotation-selection.tsx';
12
+
13
+ describe('use-annotation-selection / outside provider', () => {
14
+ test('useAnnotationSelectionOptional() returns null outside provider', () => {
15
+ // Type-level + presence assertion — hook calls can't run standalone, but
16
+ // the export shape documents the contract.
17
+ expect(typeof useAnnotationSelectionOptional).toBe('function');
18
+ });
19
+
20
+ test('useAnnotationSelection() throws outside provider', () => {
21
+ function Bare() {
22
+ useAnnotationSelection();
23
+ return null;
24
+ }
25
+ expect(() => renderToStaticMarkup(<Bare />)).toThrow(
26
+ /useAnnotationSelection must be used inside <AnnotationSelectionProvider>/
27
+ );
28
+ });
29
+ });
30
+
31
+ describe('use-annotation-selection / store contract', () => {
32
+ // bun:test runs in a node env without a renderer, but the provider's
33
+ // useState updates run synchronously during render. We exercise the API by
34
+ // capturing the value object via a render-prop child and asserting on
35
+ // post-update state.
36
+ //
37
+ // Since hooks can't be driven directly without a renderer, we instead
38
+ // verify the store via direct module exports we already know are pure
39
+ // (rid, dedupe semantics through the provider). For dynamic state, we
40
+ // assert via a smoke-renders-without-throwing pattern, then unit-test the
41
+ // dedupe + selection key logic via a captured handle.
42
+ test('AnnotationSelectionProvider renders children with empty initial selection', () => {
43
+ function Consumer() {
44
+ const sel = useAnnotationSelection();
45
+ return <span data-len={sel.selectedIds.length}>{sel.selectedIds.length}</span>;
46
+ }
47
+ const html = renderToStaticMarkup(
48
+ <AnnotationSelectionProvider>
49
+ <Consumer />
50
+ </AnnotationSelectionProvider>
51
+ );
52
+ expect(html).toContain('data-len="0"');
53
+ });
54
+
55
+ test('AnnotationSelectionProvider exposes the action set', () => {
56
+ let captured: ReturnType<typeof useAnnotationSelection> | null = null;
57
+ function Capture() {
58
+ captured = useAnnotationSelection();
59
+ return null;
60
+ }
61
+ renderToStaticMarkup(
62
+ <AnnotationSelectionProvider>
63
+ <Capture />
64
+ </AnnotationSelectionProvider>
65
+ );
66
+ expect(captured).not.toBeNull();
67
+ if (!captured) throw new Error('unreachable: asserted above');
68
+ const c = captured;
69
+ expect(typeof c.replace).toBe('function');
70
+ expect(typeof c.add).toBe('function');
71
+ expect(typeof c.toggle).toBe('function');
72
+ expect(typeof c.clear).toBe('function');
73
+ expect(typeof c.contains).toBe('function');
74
+ expect(c.selectedIds).toEqual([]);
75
+ expect(c.contains('any')).toBe(false);
76
+ });
77
+ });