@pixel-point/toolcraft 0.0.2

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 (257) hide show
  1. package/LICENSE.md +98 -0
  2. package/README.md +41 -0
  3. package/bin/create-toolcraft-app.mjs +8 -0
  4. package/bin/toolcraft.mjs +8 -0
  5. package/package.json +24 -0
  6. package/scripts/prepare-pack.mjs +29 -0
  7. package/src/cli.mjs +392 -0
  8. package/src/cli.test.mjs +284 -0
  9. package/src/copy-recursive.mjs +86 -0
  10. package/src/generate.mjs +212 -0
  11. package/src/generate.test.mjs +322 -0
  12. package/src/import-map.mjs +14 -0
  13. package/src/package-json.mjs +80 -0
  14. package/src/package-json.test.mjs +67 -0
  15. package/src/rewrite-imports.mjs +85 -0
  16. package/src/rewrite-imports.test.mjs +58 -0
  17. package/templates/runtime/contracts/component-contracts.test.ts +1165 -0
  18. package/templates/runtime/contracts/component-contracts.ts +1340 -0
  19. package/templates/runtime/contracts/decision-contracts.test.ts +206 -0
  20. package/templates/runtime/contracts/decision-contracts.ts +283 -0
  21. package/templates/runtime/contracts/index.test.ts +14 -0
  22. package/templates/runtime/contracts/index.ts +3 -0
  23. package/templates/runtime/contracts/types.ts +56 -0
  24. package/templates/runtime/export/export.test.ts +203 -0
  25. package/templates/runtime/export/export.ts +132 -0
  26. package/templates/runtime/export/index.ts +1 -0
  27. package/templates/runtime/index.ts +14 -0
  28. package/templates/runtime/react/canvas-shell.test.tsx +424 -0
  29. package/templates/runtime/react/canvas-shell.tsx +408 -0
  30. package/templates/runtime/react/control-renderers.ts +31 -0
  31. package/templates/runtime/react/controls-panel.test.tsx +3736 -0
  32. package/templates/runtime/react/controls-panel.tsx +2327 -0
  33. package/templates/runtime/react/curve-geometry.test.ts +70 -0
  34. package/templates/runtime/react/index.ts +15 -0
  35. package/templates/runtime/react/layer-tree.ts +96 -0
  36. package/templates/runtime/react/layers-panel.test.tsx +487 -0
  37. package/templates/runtime/react/layers-panel.tsx +1348 -0
  38. package/templates/runtime/react/media-file.ts +82 -0
  39. package/templates/runtime/react/panel-host-config.ts +80 -0
  40. package/templates/runtime/react/panel-host-geometry.test.ts +66 -0
  41. package/templates/runtime/react/panel-host-geometry.ts +109 -0
  42. package/templates/runtime/react/panel-host-types.ts +74 -0
  43. package/templates/runtime/react/panel-host.test.tsx +102 -0
  44. package/templates/runtime/react/panel-host.tsx +353 -0
  45. package/templates/runtime/react/runtime-public-api.test.tsx +132 -0
  46. package/templates/runtime/react/settings-transfer.test.ts +150 -0
  47. package/templates/runtime/react/settings-transfer.ts +279 -0
  48. package/templates/runtime/react/storage-key-migration.ts +48 -0
  49. package/templates/runtime/react/theme-runtime.tsx +177 -0
  50. package/templates/runtime/react/timeline-panel.test.tsx +668 -0
  51. package/templates/runtime/react/timeline-panel.tsx +2953 -0
  52. package/templates/runtime/react/toolbar-panel.test.tsx +212 -0
  53. package/templates/runtime/react/toolbar-panel.tsx +205 -0
  54. package/templates/runtime/react/toolcraft-app.integration.test.tsx +350 -0
  55. package/templates/runtime/react/toolcraft-app.test.tsx +339 -0
  56. package/templates/runtime/react/toolcraft-app.tsx +81 -0
  57. package/templates/runtime/react/toolcraft-root.test.tsx +347 -0
  58. package/templates/runtime/react/toolcraft-root.tsx +203 -0
  59. package/templates/runtime/react/use-toolcraft.ts +41 -0
  60. package/templates/runtime/schema/define-toolcraft.test.ts +1524 -0
  61. package/templates/runtime/schema/define-toolcraft.ts +1442 -0
  62. package/templates/runtime/schema/keyframe-capability.test.ts +90 -0
  63. package/templates/runtime/schema/keyframe-capability.ts +51 -0
  64. package/templates/runtime/schema/runtime-targets.ts +40 -0
  65. package/templates/runtime/schema/types.ts +370 -0
  66. package/templates/runtime/state/canvas-zoom.ts +8 -0
  67. package/templates/runtime/state/create-template-state.test.ts +242 -0
  68. package/templates/runtime/state/create-template-state.ts +95 -0
  69. package/templates/runtime/state/keyframe-evaluation.test.ts +141 -0
  70. package/templates/runtime/state/keyframe-evaluation.ts +203 -0
  71. package/templates/runtime/state/persistence.test.ts +217 -0
  72. package/templates/runtime/state/persistence.ts +511 -0
  73. package/templates/runtime/state/reducer.test.ts +937 -0
  74. package/templates/runtime/state/reducer.ts +1212 -0
  75. package/templates/runtime/state/timeline-readiness.ts +43 -0
  76. package/templates/runtime/state/types.ts +242 -0
  77. package/templates/runtime/styles.css +125 -0
  78. package/templates/runtime/testing/performance.test.ts +1058 -0
  79. package/templates/runtime/testing/performance.ts +1078 -0
  80. package/templates/starter/AGENTS.md +186 -0
  81. package/templates/starter/LICENSE.md +98 -0
  82. package/templates/starter/NOTICE.md +8 -0
  83. package/templates/starter/docs/toolcraft/README.md +41 -0
  84. package/templates/starter/docs/toolcraft/acceptance-testing.md +205 -0
  85. package/templates/starter/docs/toolcraft/agent-worklog.md +81 -0
  86. package/templates/starter/docs/toolcraft/assembly-workflow.md +206 -0
  87. package/templates/starter/docs/toolcraft/component-rules.md +299 -0
  88. package/templates/starter/docs/toolcraft/custom-controls.md +71 -0
  89. package/templates/starter/docs/toolcraft/decision-contract.md +71 -0
  90. package/templates/starter/docs/toolcraft/performance.md +112 -0
  91. package/templates/starter/docs/toolcraft/renderer-technique.md +48 -0
  92. package/templates/starter/docs/toolcraft/schema-reference.md +265 -0
  93. package/templates/starter/docs/toolcraft/workflow.md +87 -0
  94. package/templates/starter/e2e/app-browser-acceptance.spec.ts +785 -0
  95. package/templates/starter/e2e/app-controls.spec.ts +41 -0
  96. package/templates/starter/e2e/app-performance.spec.ts +326 -0
  97. package/templates/starter/e2e/canvas-handle-helpers.ts +244 -0
  98. package/templates/starter/e2e/performance-helpers.ts +612 -0
  99. package/templates/starter/e2e/product-observable-helpers.ts +170 -0
  100. package/templates/starter/index.html +12 -0
  101. package/templates/starter/package.json +52 -0
  102. package/templates/starter/playwright.config.ts +43 -0
  103. package/templates/starter/scripts/check-ai-skills.mjs +95 -0
  104. package/templates/starter/scripts/check-toolcraft-docs.mjs +159 -0
  105. package/templates/starter/scripts/check-toolcraft-integrity.mjs +232 -0
  106. package/templates/starter/scripts/run-vite-on-free-port.mjs +48 -0
  107. package/templates/starter/scripts/toolcraft-port.mjs +54 -0
  108. package/templates/starter/scripts/toolcraft-port.test.mjs +73 -0
  109. package/templates/starter/src/app/starter-acceptance.test.ts +5959 -0
  110. package/templates/starter/src/app/starter-acceptance.ts +2646 -0
  111. package/templates/starter/src/app/starter-performance.test.ts +1390 -0
  112. package/templates/starter/src/app/starter-performance.ts +12 -0
  113. package/templates/starter/src/app/starter-schema.test.ts +70 -0
  114. package/templates/starter/src/app/starter-schema.ts +15 -0
  115. package/templates/starter/src/main.tsx +18 -0
  116. package/templates/starter/src/router.tsx +16 -0
  117. package/templates/starter/src/routes/index.tsx +7 -0
  118. package/templates/starter/src/routes/root.tsx +19 -0
  119. package/templates/starter/src/styles.css +120 -0
  120. package/templates/starter/tsconfig.json +11 -0
  121. package/templates/starter/vite.config.ts +13 -0
  122. package/templates/ui/components/composites/accordion.tsx +73 -0
  123. package/templates/ui/components/composites/alert-dialog.tsx +190 -0
  124. package/templates/ui/components/composites/alert.tsx +74 -0
  125. package/templates/ui/components/composites/aspect-ratio.tsx +22 -0
  126. package/templates/ui/components/composites/avatar.tsx +98 -0
  127. package/templates/ui/components/composites/badge.tsx +69 -0
  128. package/templates/ui/components/composites/breadcrumb.tsx +106 -0
  129. package/templates/ui/components/composites/card.tsx +91 -0
  130. package/templates/ui/components/composites/combobox.tsx +486 -0
  131. package/templates/ui/components/composites/command.tsx +296 -0
  132. package/templates/ui/components/composites/context-menu.tsx +247 -0
  133. package/templates/ui/components/composites/dialog.tsx +282 -0
  134. package/templates/ui/components/composites/dropdown-menu.tsx +299 -0
  135. package/templates/ui/components/composites/empty.tsx +110 -0
  136. package/templates/ui/components/composites/hover-card.tsx +44 -0
  137. package/templates/ui/components/composites/index.ts +30 -0
  138. package/templates/ui/components/composites/menubar.tsx +214 -0
  139. package/templates/ui/components/composites/navigation-menu.tsx +167 -0
  140. package/templates/ui/components/composites/pagination.tsx +131 -0
  141. package/templates/ui/components/composites/progress.tsx +72 -0
  142. package/templates/ui/components/composites/radio-group.tsx +84 -0
  143. package/templates/ui/components/composites/resizable.tsx +42 -0
  144. package/templates/ui/components/composites/sheet.tsx +153 -0
  145. package/templates/ui/components/composites/sidebar-structural.tsx +310 -0
  146. package/templates/ui/components/composites/sidebar.tsx +431 -0
  147. package/templates/ui/components/composites/sonner.tsx +35 -0
  148. package/templates/ui/components/composites/spinner.tsx +43 -0
  149. package/templates/ui/components/composites/table.tsx +108 -0
  150. package/templates/ui/components/composites/tabs.tsx +83 -0
  151. package/templates/ui/components/control-layout/index.tsx +437 -0
  152. package/templates/ui/components/controls/actions/actions-control.tsx +139 -0
  153. package/templates/ui/components/controls/actions/index.ts +9 -0
  154. package/templates/ui/components/controls/anchor-grid/anchor-grid-control.tsx +107 -0
  155. package/templates/ui/components/controls/anchor-grid/index.ts +4 -0
  156. package/templates/ui/components/controls/boolean/boolean-controls.tsx +79 -0
  157. package/templates/ui/components/controls/boolean/index.ts +4 -0
  158. package/templates/ui/components/controls/channel-mixer/channel-mixer-control.tsx +95 -0
  159. package/templates/ui/components/controls/channel-mixer/index.ts +4 -0
  160. package/templates/ui/components/controls/channel-tabs/channel-tabs.tsx +42 -0
  161. package/templates/ui/components/controls/channel-tabs/index.ts +6 -0
  162. package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +90 -0
  163. package/templates/ui/components/controls/code-textarea/index.ts +4 -0
  164. package/templates/ui/components/controls/color/color-control.tsx +571 -0
  165. package/templates/ui/components/controls/color/color-picker-popover.tsx +104 -0
  166. package/templates/ui/components/controls/color/index.ts +41 -0
  167. package/templates/ui/components/controls/color/palette-control-data.ts +436 -0
  168. package/templates/ui/components/controls/color/palette-control.tsx +535 -0
  169. package/templates/ui/components/controls/color/style-guide-color-picker-channel-utils.ts +162 -0
  170. package/templates/ui/components/controls/color/style-guide-color-picker-interactions.ts +190 -0
  171. package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +485 -0
  172. package/templates/ui/components/controls/color/style-guide-color-picker-parts.tsx +710 -0
  173. package/templates/ui/components/controls/color/style-guide-color-picker.tsx +503 -0
  174. package/templates/ui/components/controls/control-types.ts +43 -0
  175. package/templates/ui/components/controls/curves/curve-geometry.ts +355 -0
  176. package/templates/ui/components/controls/curves/curve-graph.tsx +390 -0
  177. package/templates/ui/components/controls/curves/curves-control.tsx +445 -0
  178. package/templates/ui/components/controls/curves/index.ts +6 -0
  179. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +191 -0
  180. package/templates/ui/components/controls/file-drop/index.ts +5 -0
  181. package/templates/ui/components/controls/font-picker/font-catalog.json +15360 -0
  182. package/templates/ui/components/controls/font-picker/font-catalog.ts +116 -0
  183. package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1202 -0
  184. package/templates/ui/components/controls/font-picker/font-preview-loader.ts +336 -0
  185. package/templates/ui/components/controls/font-picker/index.ts +24 -0
  186. package/templates/ui/components/controls/font-picker/use-hover-intent.ts +46 -0
  187. package/templates/ui/components/controls/gradient/gradient-control-utils.ts +190 -0
  188. package/templates/ui/components/controls/gradient/gradient-control.tsx +612 -0
  189. package/templates/ui/components/controls/gradient/gradient-stop-list.tsx +400 -0
  190. package/templates/ui/components/controls/gradient/gradient-toolbar.tsx +152 -0
  191. package/templates/ui/components/controls/gradient/index.ts +4 -0
  192. package/templates/ui/components/controls/image-picker/image-picker-control.tsx +139 -0
  193. package/templates/ui/components/controls/image-picker/index.ts +7 -0
  194. package/templates/ui/components/controls/index.ts +192 -0
  195. package/templates/ui/components/controls/range-input/index.ts +4 -0
  196. package/templates/ui/components/controls/range-input/range-input-control.tsx +173 -0
  197. package/templates/ui/components/controls/range-slider/index.ts +4 -0
  198. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +122 -0
  199. package/templates/ui/components/controls/range-slider/range-slider-value.ts +61 -0
  200. package/templates/ui/components/controls/segmented/index.ts +8 -0
  201. package/templates/ui/components/controls/segmented/segmented-control.tsx +94 -0
  202. package/templates/ui/components/controls/select/index.ts +4 -0
  203. package/templates/ui/components/controls/select/select-control.tsx +223 -0
  204. package/templates/ui/components/controls/slider/index.ts +4 -0
  205. package/templates/ui/components/controls/slider/slider-control.tsx +150 -0
  206. package/templates/ui/components/controls/slider/slider-value.ts +56 -0
  207. package/templates/ui/components/controls/text-input/index.ts +4 -0
  208. package/templates/ui/components/controls/text-input/text-input-control.tsx +158 -0
  209. package/templates/ui/components/controls/use-measured-element-width.ts +42 -0
  210. package/templates/ui/components/controls/vector/index.ts +8 -0
  211. package/templates/ui/components/controls/vector/vector-control.tsx +401 -0
  212. package/templates/ui/components/panel/index.ts +19 -0
  213. package/templates/ui/components/panel/panel-actions.tsx +165 -0
  214. package/templates/ui/components/panel/panel-header.tsx +61 -0
  215. package/templates/ui/components/panel/panel-icon-button.tsx +96 -0
  216. package/templates/ui/components/panel/panel-section.tsx +168 -0
  217. package/templates/ui/components/panel/panel-surface.tsx +206 -0
  218. package/templates/ui/components/panel/panel.tsx +210 -0
  219. package/templates/ui/components/primitives/animated-loader.tsx +61 -0
  220. package/templates/ui/components/primitives/button-group.tsx +134 -0
  221. package/templates/ui/components/primitives/button.tsx +429 -0
  222. package/templates/ui/components/primitives/checkbox.tsx +62 -0
  223. package/templates/ui/components/primitives/editable-slider-value-label.tsx +337 -0
  224. package/templates/ui/components/primitives/field.tsx +225 -0
  225. package/templates/ui/components/primitives/index.ts +82 -0
  226. package/templates/ui/components/primitives/input-group.tsx +298 -0
  227. package/templates/ui/components/primitives/input.tsx +61 -0
  228. package/templates/ui/components/primitives/internal/button-loading.tsx +178 -0
  229. package/templates/ui/components/primitives/label.tsx +16 -0
  230. package/templates/ui/components/primitives/popover.tsx +126 -0
  231. package/templates/ui/components/primitives/portal-layer-context.tsx +33 -0
  232. package/templates/ui/components/primitives/primitive-arrow-icon.tsx +38 -0
  233. package/templates/ui/components/primitives/scroll-fade-logic.ts +441 -0
  234. package/templates/ui/components/primitives/scroll-fade-render.tsx +75 -0
  235. package/templates/ui/components/primitives/scroll-fade-types.ts +41 -0
  236. package/templates/ui/components/primitives/scroll-fade.tsx +72 -0
  237. package/templates/ui/components/primitives/select.tsx +408 -0
  238. package/templates/ui/components/primitives/selection-state.ts +31 -0
  239. package/templates/ui/components/primitives/separator.tsx +21 -0
  240. package/templates/ui/components/primitives/slider/index.ts +4 -0
  241. package/templates/ui/components/primitives/slider/slider-interaction.tsx +96 -0
  242. package/templates/ui/components/primitives/slider/slider-parts.tsx +303 -0
  243. package/templates/ui/components/primitives/slider/slider-reset.ts +152 -0
  244. package/templates/ui/components/primitives/slider/slider-value.ts +114 -0
  245. package/templates/ui/components/primitives/slider/slider.tsx +511 -0
  246. package/templates/ui/components/primitives/switch.tsx +35 -0
  247. package/templates/ui/components/primitives/textarea.tsx +49 -0
  248. package/templates/ui/components/primitives/toggle-group.tsx +114 -0
  249. package/templates/ui/components/primitives/toggle.tsx +46 -0
  250. package/templates/ui/components/primitives/tooltip.tsx +100 -0
  251. package/templates/ui/hooks/use-mobile.ts +21 -0
  252. package/templates/ui/index.ts +31 -0
  253. package/templates/ui/lib/control-outline.ts +3 -0
  254. package/templates/ui/lib/input-control-style.ts +131 -0
  255. package/templates/ui/lib/style-guide-color-utils.ts +111 -0
  256. package/templates/ui/lib/utils.ts +6 -0
  257. package/templates/ui/styles.css +291 -0
@@ -0,0 +1,170 @@
1
+ import { expect, type Page } from "@playwright/test";
2
+
3
+ const defaultProductObservableSelector = [
4
+ "[data-toolcraft-product-output]",
5
+ "[data-toolcraft-product-text]",
6
+ "[data-toolcraft-canvas-slot] canvas",
7
+ "[data-toolcraft-canvas-slot] svg",
8
+ "[data-toolcraft-canvas-world] canvas",
9
+ "[data-toolcraft-canvas-world] svg",
10
+ ].join(", ");
11
+
12
+ export type ToolcraftProductObservableSnapshotOptions = {
13
+ selector?: string;
14
+ timeoutMs?: number;
15
+ };
16
+
17
+ export type ToolcraftProductObservableChangeOptions =
18
+ ToolcraftProductObservableSnapshotOptions & {
19
+ message?: string;
20
+ };
21
+
22
+ export async function getToolcraftProductObservableSnapshot(
23
+ page: Page,
24
+ options: ToolcraftProductObservableSnapshotOptions = {},
25
+ ): Promise<string> {
26
+ const selector = options.selector ?? defaultProductObservableSelector;
27
+ const observable = page.locator(selector).first();
28
+
29
+ await expect(
30
+ observable,
31
+ `Product observable "${selector}" must exist and be visible.`,
32
+ ).toBeVisible({
33
+ timeout: options.timeoutMs ?? 5000,
34
+ });
35
+
36
+ return observable.evaluate((element) => {
37
+ const normalizeText = (value: string) => value.replace(/\s+/g, " ").trim();
38
+ const hashString = (value: string) => {
39
+ let hash = 2166136261;
40
+
41
+ for (let index = 0; index < value.length; index += 1) {
42
+ hash ^= value.charCodeAt(index);
43
+ hash = Math.imul(hash, 16777619);
44
+ }
45
+
46
+ return (hash >>> 0).toString(16);
47
+ };
48
+ const hashBytes = (value: Uint8ClampedArray) => {
49
+ let hash = 2166136261;
50
+
51
+ for (let index = 0; index < value.length; index += 1) {
52
+ hash ^= value[index] ?? 0;
53
+ hash = Math.imul(hash, 16777619);
54
+ }
55
+
56
+ return (hash >>> 0).toString(16);
57
+ };
58
+ const snapshotCanvas = (canvas: HTMLCanvasElement) => {
59
+ const width = canvas.width;
60
+ const height = canvas.height;
61
+ const sampleWidth = Math.max(1, Math.min(96, width));
62
+ const sampleHeight = Math.max(1, Math.min(96, height));
63
+
64
+ try {
65
+ const sample = document.createElement("canvas");
66
+ sample.width = sampleWidth;
67
+ sample.height = sampleHeight;
68
+ const context = sample.getContext("2d", { willReadFrequently: true });
69
+
70
+ if (!context) {
71
+ return {
72
+ hash: "unavailable-context",
73
+ height,
74
+ sampleHeight,
75
+ sampleWidth,
76
+ width,
77
+ };
78
+ }
79
+
80
+ context.drawImage(canvas, 0, 0, sampleWidth, sampleHeight);
81
+ const pixels = context.getImageData(0, 0, sampleWidth, sampleHeight).data;
82
+
83
+ return {
84
+ hash: hashBytes(pixels),
85
+ height,
86
+ sampleHeight,
87
+ sampleWidth,
88
+ width,
89
+ };
90
+ } catch (error) {
91
+ return {
92
+ error: error instanceof Error ? error.message : "unreadable-canvas",
93
+ height,
94
+ sampleHeight,
95
+ sampleWidth,
96
+ width,
97
+ };
98
+ }
99
+ };
100
+ const snapshotElement = (node: Element) => {
101
+ const rect = node.getBoundingClientRect();
102
+ const computed = window.getComputedStyle(node);
103
+ const attributes = Array.from(node.attributes)
104
+ .filter((attribute) =>
105
+ /^(?:aria-|data-|role$|class$|style$|viewBox$|width$|height$)/.test(
106
+ attribute.name,
107
+ ),
108
+ )
109
+ .map((attribute) => [attribute.name, attribute.value] as const)
110
+ .sort(([left], [right]) => left.localeCompare(right));
111
+ const canvases =
112
+ node instanceof HTMLCanvasElement
113
+ ? [snapshotCanvas(node)]
114
+ : Array.from(node.querySelectorAll("canvas")).map(snapshotCanvas);
115
+ const svgs =
116
+ node instanceof SVGElement
117
+ ? [hashString(node.outerHTML)]
118
+ : Array.from(node.querySelectorAll("svg")).map((svg) => hashString(svg.outerHTML));
119
+
120
+ return {
121
+ attributes,
122
+ canvases,
123
+ childElementCount: node.childElementCount,
124
+ rect: {
125
+ height: Math.round(rect.height * 100) / 100,
126
+ width: Math.round(rect.width * 100) / 100,
127
+ x: Math.round(rect.x * 100) / 100,
128
+ y: Math.round(rect.y * 100) / 100,
129
+ },
130
+ style: {
131
+ backgroundColor: computed.backgroundColor,
132
+ color: computed.color,
133
+ fontFamily: computed.fontFamily,
134
+ fontSize: computed.fontSize,
135
+ letterSpacing: computed.letterSpacing,
136
+ lineHeight: computed.lineHeight,
137
+ opacity: computed.opacity,
138
+ transform: computed.transform,
139
+ },
140
+ svgs,
141
+ tagName: node.tagName.toLowerCase(),
142
+ text: normalizeText(node.textContent ?? ""),
143
+ };
144
+ };
145
+
146
+ return JSON.stringify(snapshotElement(element));
147
+ });
148
+ }
149
+
150
+ export async function expectToolcraftProductObservableToChange(
151
+ page: Page,
152
+ action: () => Promise<void>,
153
+ options: ToolcraftProductObservableChangeOptions = {},
154
+ ): Promise<void> {
155
+ const before = await getToolcraftProductObservableSnapshot(page, options);
156
+
157
+ await action();
158
+
159
+ await expect
160
+ .poll(
161
+ async () => getToolcraftProductObservableSnapshot(page, options),
162
+ {
163
+ message:
164
+ options.message ??
165
+ "Product output should change after the visible user interaction.",
166
+ timeout: options.timeoutMs ?? 5000,
167
+ },
168
+ )
169
+ .not.toBe(before);
170
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Toolcraft Starter</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "toolcraft-starter",
3
+ "private": true,
4
+ "license": "SEE LICENSE IN LICENSE.md",
5
+ "type": "module",
6
+ "scripts": {
7
+ "ai:check": "node scripts/check-ai-skills.mjs",
8
+ "dev": "node scripts/run-vite-on-free-port.mjs dev",
9
+ "build": "tsc -p tsconfig.json --noEmit && vite build",
10
+ "preview": "node scripts/run-vite-on-free-port.mjs preview",
11
+ "docs:check": "node scripts/check-toolcraft-docs.mjs",
12
+ "test": "node scripts/check-toolcraft-docs.mjs && node scripts/check-toolcraft-integrity.mjs && node --test scripts/*.test.mjs && vitest run src --passWithNoTests",
13
+ "test:browser": "playwright install chromium && playwright test",
14
+ "test:browser:perf": "playwright install chromium && playwright test e2e/app-performance.spec.ts --workers=1 && playwright test --grep \"browser perf:\" --workers=1 --pass-with-no-tests",
15
+ "typecheck": "tsc -p tsconfig.json --noEmit",
16
+ "verify:quick": "pnpm ai:check && pnpm test",
17
+ "verify:ui": "pnpm test:browser",
18
+ "verify:perf": "pnpm test:browser:perf",
19
+ "verify:final": "pnpm ai:check && pnpm test && pnpm build && pnpm test:browser && pnpm test:browser:perf"
20
+ },
21
+ "dependencies": {
22
+ "@base-ui/react": "^1.4.1",
23
+ "@fontsource-variable/inter": "^5.2.8",
24
+ "@phosphor-icons/react": "^2.1.10",
25
+ "@repo/toolcraft-runtime": "workspace:*",
26
+ "@repo/ui": "workspace:*",
27
+ "@tanstack/react-router": "1.170.6",
28
+ "class-variance-authority": "^0.7.1",
29
+ "clsx": "^2.1.1",
30
+ "cmdk": "^1.1.1",
31
+ "lucide-react": "^0.511.0",
32
+ "motion": "^11.16.3",
33
+ "react": "^19.2.0",
34
+ "react-dom": "^19.2.0",
35
+ "react-resizable-panels": "^4.10.0",
36
+ "sonner": "^2.0.7",
37
+ "tailwind-merge": "^3.5.0"
38
+ },
39
+ "devDependencies": {
40
+ "@playwright/test": "^1.51.1",
41
+ "@tailwindcss/vite": "^4.1.18",
42
+ "@types/node": "22.19.19",
43
+ "@types/react": "^19.2.14",
44
+ "@types/react-dom": "^19.2.3",
45
+ "@vitejs/plugin-react": "^6.0.1",
46
+ "tailwindcss": "^4.1.18",
47
+ "tw-animate-css": "^1.4.0",
48
+ "typescript": "^6.0.3",
49
+ "vite": "^8.0.0",
50
+ "vitest": "^3.0.5"
51
+ }
52
+ }
@@ -0,0 +1,43 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ import { findAvailablePort, readPreferredPort } from "./scripts/toolcraft-port.mjs";
4
+
5
+ const resolvedTestPortEnvName = "TOOLCRAFT_RESOLVED_TEST_PORT";
6
+ const resolvedTestPort = Number(process.env[resolvedTestPortEnvName]);
7
+ const preferredTestPort = readPreferredPort([
8
+ resolvedTestPortEnvName,
9
+ "TOOLCRAFT_TEST_PORT",
10
+ "TOOLCRAFT_PORT",
11
+ "PORT",
12
+ ]);
13
+ const testPort =
14
+ Number.isInteger(resolvedTestPort) && resolvedTestPort > 0 && resolvedTestPort <= 65_535
15
+ ? resolvedTestPort
16
+ : await findAvailablePort(preferredTestPort);
17
+ const testBaseUrl = `http://localhost:${testPort}`;
18
+ process.env[resolvedTestPortEnvName] = String(testPort);
19
+
20
+ if (!resolvedTestPort && testPort !== preferredTestPort) {
21
+ console.log(`[toolcraft] Browser test port ${preferredTestPort} is busy; using ${testPort} instead.`);
22
+ }
23
+
24
+ export default defineConfig({
25
+ testDir: "./e2e",
26
+ timeout: 30_000,
27
+ expect: {
28
+ timeout: 5_000,
29
+ },
30
+ fullyParallel: false,
31
+ reporter: [["list"]],
32
+ use: {
33
+ ...devices["Desktop Chrome"],
34
+ baseURL: testBaseUrl,
35
+ trace: "retain-on-failure",
36
+ },
37
+ webServer: {
38
+ command: `pnpm exec vite dev --port ${testPort}`,
39
+ reuseExistingServer: false,
40
+ timeout: 60_000,
41
+ url: testBaseUrl,
42
+ },
43
+ });
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+
7
+ const codexHome = process.env.CODEX_HOME
8
+ ? path.resolve(process.env.CODEX_HOME)
9
+ : path.join(os.homedir(), ".codex");
10
+
11
+ const requiredSkills = [
12
+ {
13
+ name: "brainstorming",
14
+ purpose: "shape the app behavior, panels, controls, canvas, media, export, and ambiguity before code",
15
+ paths: [path.join(codexHome, "skills/brainstorming/SKILL.md")],
16
+ },
17
+ {
18
+ name: "writing-plans",
19
+ purpose: "turn the approved app spec into a deterministic Toolcraft implementation plan",
20
+ paths: [path.join(codexHome, "skills/writing-plans/SKILL.md")],
21
+ },
22
+ {
23
+ name: "systematic-debugging",
24
+ purpose: "investigate root cause before fixing broken controls, tests, builds, visual regressions, or runtime bugs",
25
+ paths: [path.join(codexHome, "skills/systematic-debugging/SKILL.md")],
26
+ },
27
+ {
28
+ name: "browser",
29
+ purpose: "verify the generated UI in a running local browser after implementation",
30
+ paths: [
31
+ path.join(codexHome, "skills/browser/SKILL.md"),
32
+ path.join(codexHome, "skills/browser/browser/SKILL.md"),
33
+ path.join(codexHome, "plugins/cache/openai-bundled/browser/0.1.0-alpha2/skills/browser/SKILL.md"),
34
+ path.join(codexHome, "plugins/cache/openai-bundled/browser/*/skills/browser/SKILL.md"),
35
+ path.join(codexHome, "plugins/cache/openai-bundled/browser/*/skills/control-in-app-browser/SKILL.md"),
36
+ ],
37
+ },
38
+ ];
39
+
40
+ function pathPatternExists(candidatePath) {
41
+ if (!candidatePath.includes("*")) {
42
+ return fs.existsSync(candidatePath);
43
+ }
44
+
45
+ const { root } = path.parse(candidatePath);
46
+ const segments = candidatePath.slice(root.length).split(path.sep).filter(Boolean);
47
+
48
+ function walk(index, currentPath) {
49
+ if (index >= segments.length) {
50
+ return fs.existsSync(currentPath);
51
+ }
52
+
53
+ const segment = segments[index];
54
+
55
+ if (segment !== "*") {
56
+ return walk(index + 1, path.join(currentPath, segment));
57
+ }
58
+
59
+ if (!fs.existsSync(currentPath)) {
60
+ return false;
61
+ }
62
+
63
+ return fs
64
+ .readdirSync(currentPath, { withFileTypes: true })
65
+ .some((entry) => entry.isDirectory() && walk(index + 1, path.join(currentPath, entry.name)));
66
+ }
67
+
68
+ return walk(0, root);
69
+ }
70
+
71
+ function hasAnyPath(paths) {
72
+ return paths.some((candidatePath) => pathPatternExists(candidatePath));
73
+ }
74
+
75
+ const missingSkills = requiredSkills.filter((skill) => !hasAnyPath(skill.paths));
76
+
77
+ if (missingSkills.length === 0) {
78
+ console.log("AI workflow skills are installed:");
79
+ for (const skill of requiredSkills) {
80
+ console.log(`- ${skill.name}: ${skill.purpose}`);
81
+ }
82
+ process.exit(0);
83
+ }
84
+
85
+ console.error("Missing required AI workflow skills:");
86
+ for (const skill of missingSkills) {
87
+ console.error(`- ${skill.name}: ${skill.purpose}`);
88
+ }
89
+
90
+ console.error("");
91
+ console.error("If your AI environment supports Codex skills, install the missing skills before implementation.");
92
+ console.error("If installation is not available, stop and ask the user to install them.");
93
+ console.error(`Checked CODEX_HOME: ${codexHome}`);
94
+
95
+ process.exit(1);
@@ -0,0 +1,159 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
6
+ const projectRoot = path.resolve(scriptDir, "..");
7
+
8
+ const requiredDocs = [
9
+ "README.md",
10
+ "workflow.md",
11
+ "assembly-workflow.md",
12
+ "decision-contract.md",
13
+ "schema-reference.md",
14
+ "acceptance-testing.md",
15
+ "performance.md",
16
+ "renderer-technique.md",
17
+ "agent-worklog.md",
18
+ "custom-controls.md",
19
+ "component-rules.md",
20
+ ];
21
+
22
+ const requiredProjectFiles = ["LICENSE.md", "NOTICE.md"];
23
+
24
+ const requiredRuleIds = [
25
+ "runtime-shell-required",
26
+ "canvas-no-app-ui",
27
+ "canvas-surface-preserved",
28
+ "canvas-handle-placement",
29
+ "panel-host-behavior",
30
+ "layers-enable-only-when-needed",
31
+ "layers-enabled-behavior",
32
+ "timeline-mode-choice",
33
+ "timeline-enabled-behavior",
34
+ "controls-product-coverage",
35
+ "output-export-required",
36
+ "controls-layout-heuristics",
37
+ "renderer-technique-inventory",
38
+ "reference-clone-source-of-truth",
39
+ "acceptance-product-observable",
40
+ "performance-coverage-levels",
41
+ "persistence-policy-explicit",
42
+ "workflow-required",
43
+ ];
44
+
45
+ const requiredAgentsLinks = [
46
+ "workflow.md",
47
+ "assembly-workflow.md",
48
+ "decision-contract.md",
49
+ "schema-reference.md",
50
+ "acceptance-testing.md",
51
+ "performance.md",
52
+ "renderer-technique.md",
53
+ "agent-worklog.md",
54
+ "custom-controls.md",
55
+ "component-rules.md",
56
+ ];
57
+
58
+ const requiredWorkflowTerms = [
59
+ "Verification Tier Classifier",
60
+ "Verification tier: Tier N",
61
+ "pnpm verify:quick",
62
+ "pnpm verify:perf",
63
+ "pnpm verify:final",
64
+ ];
65
+
66
+ const repoScope = "@repo";
67
+ const workspaceProtocol = "workspace";
68
+ const forbiddenTextPatterns = [
69
+ new RegExp(`${repoScope}/ui`),
70
+ new RegExp(`${repoScope}/toolcraft-runtime`),
71
+ new RegExp(`${workspaceProtocol}:`),
72
+ /toolcraft-ai-assembly/,
73
+ /\bAI Assembly\b/,
74
+ /How AI/,
75
+ ];
76
+
77
+ async function readText(relativePath) {
78
+ return fs.readFile(path.join(projectRoot, relativePath), "utf8");
79
+ }
80
+
81
+ async function fileExists(relativePath) {
82
+ try {
83
+ const stat = await fs.stat(path.join(projectRoot, relativePath));
84
+ return stat.isFile();
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ const failures = [];
91
+
92
+ for (const fileName of requiredDocs) {
93
+ const relativePath = `docs/toolcraft/${fileName}`;
94
+
95
+ if (!(await fileExists(relativePath))) {
96
+ failures.push(`missing local Toolcraft doc: ${relativePath}`);
97
+ }
98
+ }
99
+
100
+ for (const relativePath of requiredProjectFiles) {
101
+ if (!(await fileExists(relativePath))) {
102
+ failures.push(`missing required project file: ${relativePath}`);
103
+ }
104
+ }
105
+
106
+ const agentsSource = await readText("AGENTS.md");
107
+ const decisionSource = await readText("docs/toolcraft/decision-contract.md");
108
+ const docsSources = [agentsSource, decisionSource];
109
+
110
+ for (const ruleId of requiredRuleIds) {
111
+ if (!agentsSource.includes(ruleId)) {
112
+ failures.push(`AGENTS.md must list decision rule "${ruleId}"`);
113
+ }
114
+
115
+ if (!decisionSource.includes(ruleId)) {
116
+ failures.push(`docs/toolcraft/decision-contract.md must list decision rule "${ruleId}"`);
117
+ }
118
+ }
119
+
120
+ for (const fileName of requiredDocs) {
121
+ const source = await readText(`docs/toolcraft/${fileName}`);
122
+ docsSources.push(source);
123
+ }
124
+
125
+ const combinedSource = docsSources.join("\n");
126
+
127
+ for (const pattern of forbiddenTextPatterns) {
128
+ if (pattern.test(combinedSource)) {
129
+ failures.push(`local Toolcraft docs contain forbidden text pattern ${pattern}`);
130
+ }
131
+ }
132
+
133
+ for (const localDoc of requiredAgentsLinks) {
134
+ if (!agentsSource.includes(`docs/toolcraft/${localDoc}`)) {
135
+ failures.push(`AGENTS.md must link docs/toolcraft/${localDoc}`);
136
+ }
137
+ }
138
+
139
+ for (const term of requiredWorkflowTerms) {
140
+ if (!agentsSource.includes(term)) {
141
+ failures.push(`AGENTS.md must mention "${term}"`);
142
+ }
143
+
144
+ if (!combinedSource.includes(term)) {
145
+ failures.push(`local Toolcraft docs must mention "${term}"`);
146
+ }
147
+ }
148
+
149
+ if (failures.length > 0) {
150
+ console.error("Toolcraft local docs check failed:");
151
+
152
+ for (const failure of failures) {
153
+ console.error(`- ${failure}`);
154
+ }
155
+
156
+ process.exit(1);
157
+ }
158
+
159
+ console.log("Toolcraft local docs check passed.");