@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,206 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ import {
8
+ getToolcraftDecisionRule,
9
+ getToolcraftDecisionRulesByArea,
10
+ TOOLCRAFT_DECISION_CONTRACT,
11
+ } from "./decision-contracts";
12
+
13
+ const contractsDir = dirname(fileURLToPath(import.meta.url));
14
+ const repoRoot = resolve(contractsDir, "../../../..");
15
+
16
+ function readRepoFile(pathFromRoot: string): string {
17
+ return readFileSync(resolve(repoRoot, pathFromRoot), "utf8");
18
+ }
19
+
20
+ describe("Toolcraft template decision contract", () => {
21
+ it("classifies every rule by level, verdict, and enforcement", () => {
22
+ expect(TOOLCRAFT_DECISION_CONTRACT.length).toBeGreaterThanOrEqual(12);
23
+
24
+ for (const rule of TOOLCRAFT_DECISION_CONTRACT) {
25
+ expect(rule.id).toMatch(/^[a-z0-9-]+$/);
26
+ expect(rule.area).toMatch(
27
+ /^(runtime-shell|canvas|panels|layers|timeline|controls|renderer|reference-clone|acceptance|performance|persistence|workflow)$/,
28
+ );
29
+ expect(rule.title.trim().length).toBeGreaterThan(0);
30
+ expect(rule.currentConstraint.trim().length).toBeGreaterThan(0);
31
+ expect(rule.desiredBehavior.trim().length).toBeGreaterThan(0);
32
+ expect(rule.enforcement.length).toBeGreaterThan(0);
33
+ }
34
+ });
35
+
36
+ it("keeps canvas app UI out of canvasContent as an invariant", () => {
37
+ const rule = getToolcraftDecisionRule("canvas-no-app-ui");
38
+
39
+ expect(rule?.level).toBe("invariant");
40
+ expect(rule?.verdict).toBe("move-to-validator");
41
+ expect(rule?.enforcement).toContain("browser-helper");
42
+ expect(rule?.desiredBehavior).toMatch(/product result/i);
43
+ });
44
+
45
+ it("keeps runtime shell bypass prevention in validators, not only docs", () => {
46
+ const rule = getToolcraftDecisionRule("runtime-shell-required");
47
+
48
+ expect(rule?.level).toBe("invariant");
49
+ expect(rule?.desiredBehavior).toMatch(/schema controls/i);
50
+ expect(rule?.desiredBehavior).toMatch(/canvasContent/i);
51
+ expect(rule?.desiredBehavior).toMatch(/controlRenderers/i);
52
+ expect(rule?.desiredBehavior).toMatch(/onPanelAction/i);
53
+ expect(rule?.desiredBehavior).toMatch(/low-level runtime surfaces/i);
54
+ expect(rule?.desiredBehavior).toMatch(/built-in control components directly/i);
55
+ expect(rule?.enforcement).toContain("cli-integrity-check");
56
+ expect(rule?.enforcement).toContain("acceptance-validator");
57
+ });
58
+
59
+ it("keeps layers and timeline as heuristics until enabled", () => {
60
+ expect(getToolcraftDecisionRule("layers-enable-only-when-needed")?.level).toBe(
61
+ "heuristic",
62
+ );
63
+ expect(getToolcraftDecisionRule("layers-enabled-behavior")?.level).toBe(
64
+ "invariant",
65
+ );
66
+ expect(getToolcraftDecisionRule("timeline-mode-choice")?.level).toBe("heuristic");
67
+ expect(getToolcraftDecisionRule("timeline-mode-choice")?.desiredBehavior).toMatch(
68
+ /Animation Intent Inventory/,
69
+ );
70
+ expect(getToolcraftDecisionRule("timeline-enabled-behavior")?.level).toBe(
71
+ "invariant",
72
+ );
73
+ });
74
+
75
+ it("makes renderer technology a default with escape hatches", () => {
76
+ const rule = getToolcraftDecisionRule("renderer-technique-inventory");
77
+
78
+ expect(rule?.level).toBe("default");
79
+ expect(rule?.desiredBehavior).toMatch(/product output semantics/i);
80
+ expect(rule?.enforcement).toContain("performance-validator");
81
+ });
82
+
83
+ it("splits performance into workload and lightweight responsiveness coverage", () => {
84
+ const rule = getToolcraftDecisionRule("performance-coverage-levels");
85
+
86
+ expect(rule?.level).toBe("invariant");
87
+ expect(rule?.desiredBehavior).toMatch(/workload/i);
88
+ expect(rule?.desiredBehavior).toMatch(/responsiveness/i);
89
+ expect(rule?.desiredBehavior).toMatch(/canvas drag, pan, pinch, zoom, and radar\/center/i);
90
+ expect(rule?.desiredBehavior).toMatch(/playback state/i);
91
+ expect(rule?.desiredBehavior).toMatch(/first working version/i);
92
+ expect(rule?.desiredBehavior).toMatch(/renderer\/canvas\/animation\/export\/timeline\/layers/i);
93
+ expect(rule?.desiredBehavior).toMatch(/bug that previously broke functionality/i);
94
+ expect(rule?.desiredBehavior).toMatch(/optimize performance/i);
95
+ expect(rule?.desiredBehavior).toMatch(/full performance checkpoint/i);
96
+ });
97
+
98
+ it("requires persistence reload coverage when localStorage is enabled", () => {
99
+ const rule = getToolcraftDecisionRule("persistence-policy-explicit");
100
+
101
+ expect(rule?.desiredBehavior).toMatch(/localStorage persistence/i);
102
+ expect(rule?.desiredBehavior).toMatch(/reloading the page/i);
103
+ expect(rule?.desiredBehavior).toMatch(/settings import\/export is not a substitute/i);
104
+ expect(rule?.enforcement).toContain("acceptance-validator");
105
+ expect(rule?.enforcement).toContain("browser-helper");
106
+ });
107
+
108
+ it("groups rules by area", () => {
109
+ expect(getToolcraftDecisionRulesByArea("canvas").map((rule) => rule.id)).toContain(
110
+ "canvas-no-app-ui",
111
+ );
112
+ });
113
+
114
+ it("uses neutral workflow rule ids in public contract data", () => {
115
+ const legacyWorkflowRuleId = ["ai", "workflow", "required"].join("-");
116
+
117
+ expect(getToolcraftDecisionRule("workflow-required")?.area).toBe("workflow");
118
+ expect(getToolcraftDecisionRule(legacyWorkflowRuleId)).toBeUndefined();
119
+ });
120
+
121
+ it("keeps public docs, local docs, and app assembly instructions synced with rule ids", () => {
122
+ const docsSource = readRepoFile(
123
+ "apps/website/src/content/docs/toolcraft-decision-contract.md",
124
+ );
125
+ const rootAgentsSource = readRepoFile("AGENTS.md");
126
+ const starterAgentsSource = readRepoFile("starter/AGENTS.md");
127
+ const starterDecisionSource = readRepoFile("starter/docs/toolcraft/decision-contract.md");
128
+ const starterDocsCheckSource = readRepoFile("starter/scripts/check-toolcraft-docs.mjs");
129
+
130
+ for (const rule of TOOLCRAFT_DECISION_CONTRACT) {
131
+ expect(docsSource, `Docs page must list decision rule "${rule.id}".`).toContain(rule.id);
132
+ expect(rootAgentsSource, `Root AGENTS.md must list decision rule "${rule.id}".`).toContain(
133
+ rule.id,
134
+ );
135
+ expect(
136
+ starterAgentsSource,
137
+ `Starter AGENTS.md must list decision rule "${rule.id}" for generated apps.`,
138
+ ).toContain(rule.id);
139
+ expect(
140
+ starterDecisionSource,
141
+ `Starter local decision docs must list decision rule "${rule.id}".`,
142
+ ).toContain(rule.id);
143
+ expect(
144
+ starterDocsCheckSource,
145
+ `Starter docs check must enforce decision rule "${rule.id}".`,
146
+ ).toContain(rule.id);
147
+ }
148
+ });
149
+
150
+ it("keeps root AGENTS.md as a concise entry map instead of duplicated rule prose", () => {
151
+ const rootAgentsSource = readRepoFile("AGENTS.md");
152
+ const rootAgentsLines = rootAgentsSource.trim().split(/\r?\n/);
153
+ const forbiddenDuplicatedSectionHeadings = [
154
+ "Reference Runtime Clone Mode",
155
+ "Always-On Canvas Capabilities",
156
+ "Optional Surfaces",
157
+ "Panel Rules",
158
+ "Control Rules",
159
+ "App Completion Bar",
160
+ ];
161
+ const requiredEntryMapRefs = [
162
+ "starter/docs/toolcraft/assembly-workflow.md",
163
+ "starter/docs/toolcraft/decision-contract.md",
164
+ "starter/docs/toolcraft/schema-reference.md",
165
+ "starter/docs/toolcraft/component-rules.md",
166
+ "starter/docs/toolcraft/acceptance-testing.md",
167
+ "starter/docs/toolcraft/performance.md",
168
+ "starter/docs/toolcraft/renderer-technique.md",
169
+ "starter/docs/toolcraft/custom-controls.md",
170
+ "packages/toolcraft-runtime/src/contracts/decision-contracts.ts",
171
+ "packages/toolcraft-runtime/src/contracts/component-contracts.ts",
172
+ ];
173
+
174
+ expect(rootAgentsLines.length).toBeLessThanOrEqual(180);
175
+ expect(rootAgentsSource).toContain("## Quick Entry Contract");
176
+ expect(rootAgentsSource).toContain("## Detailed References");
177
+
178
+ for (const heading of forbiddenDuplicatedSectionHeadings) {
179
+ expect(rootAgentsSource, `Root AGENTS.md should link out instead of duplicating "${heading}".`).not.toContain(
180
+ `## ${heading}`,
181
+ );
182
+ }
183
+
184
+ for (const ref of requiredEntryMapRefs) {
185
+ expect(rootAgentsSource, `Root AGENTS.md should link to "${ref}".`).toContain(ref);
186
+ }
187
+ });
188
+
189
+ it("keeps required local Toolcraft docs available for generated apps", () => {
190
+ const requiredLocalDocs = [
191
+ "README.md",
192
+ "assembly-workflow.md",
193
+ "decision-contract.md",
194
+ "schema-reference.md",
195
+ "acceptance-testing.md",
196
+ "performance.md",
197
+ "renderer-technique.md",
198
+ "custom-controls.md",
199
+ "component-rules.md",
200
+ ];
201
+
202
+ for (const docName of requiredLocalDocs) {
203
+ expect(() => readRepoFile(`starter/docs/toolcraft/${docName}`)).not.toThrow();
204
+ }
205
+ });
206
+ });
@@ -0,0 +1,283 @@
1
+ export type ToolcraftDecisionArea =
2
+ | "acceptance"
3
+ | "canvas"
4
+ | "controls"
5
+ | "layers"
6
+ | "panels"
7
+ | "performance"
8
+ | "persistence"
9
+ | "reference-clone"
10
+ | "renderer"
11
+ | "runtime-shell"
12
+ | "timeline"
13
+ | "workflow";
14
+
15
+ export type ToolcraftDecisionRuleLevel =
16
+ | "default"
17
+ | "escape-hatch"
18
+ | "heuristic"
19
+ | "invariant"
20
+ | "recommendation";
21
+
22
+ export type ToolcraftDecisionVerdict =
23
+ | "keep-hard"
24
+ | "keep-but-clarify"
25
+ | "move-to-validator"
26
+ | "relax-to-heuristic"
27
+ | "remove-duplicate";
28
+
29
+ export type ToolcraftDecisionEnforcement =
30
+ | "acceptance-validator"
31
+ | "browser-helper"
32
+ | "cli-integrity-check"
33
+ | "docs"
34
+ | "performance-validator"
35
+ | "runtime"
36
+ | "schema-normalization"
37
+ | "spec-checklist"
38
+ | "starter-agents";
39
+
40
+ export type ToolcraftDecisionRule = {
41
+ area: ToolcraftDecisionArea;
42
+ currentConstraint: string;
43
+ desiredBehavior: string;
44
+ enforcement: readonly ToolcraftDecisionEnforcement[];
45
+ id: string;
46
+ level: ToolcraftDecisionRuleLevel;
47
+ title: string;
48
+ verdict: ToolcraftDecisionVerdict;
49
+ };
50
+
51
+ export const TOOLCRAFT_DECISION_CONTRACT = [
52
+ {
53
+ area: "runtime-shell",
54
+ currentConstraint: "Apps must assemble through defineToolcraft and ToolcraftApp.",
55
+ desiredBehavior:
56
+ "Generated apps use the Toolcraft runtime shell and keep product-specific rendering inside supported extension points: schema controls, canvasContent product output, controlRenderers for true custom controls, ToolcraftApp onPanelAction, and runtime commands. App-specific code must not import or render low-level runtime surfaces or built-in control components directly.",
57
+ enforcement: [
58
+ "cli-integrity-check",
59
+ "acceptance-validator",
60
+ "starter-agents",
61
+ "spec-checklist",
62
+ ],
63
+ id: "runtime-shell-required",
64
+ level: "invariant",
65
+ title: "Toolcraft runtime shell is required",
66
+ verdict: "keep-hard",
67
+ },
68
+ {
69
+ area: "canvas",
70
+ currentConstraint:
71
+ "canvasContent must not contain buttons, forms, CTAs, helper text, upload prompts, menus, or settings UI.",
72
+ desiredBehavior:
73
+ "Canvas renders only product result, source material, renderer output, and valid product editing handles.",
74
+ enforcement: ["browser-helper", "starter-agents", "spec-checklist"],
75
+ id: "canvas-no-app-ui",
76
+ level: "invariant",
77
+ title: "Canvas contains product output, not app UI",
78
+ verdict: "move-to-validator",
79
+ },
80
+ {
81
+ area: "canvas",
82
+ currentConstraint:
83
+ "The Toolcraft canvas shell owns the visible workspace backing behind product output.",
84
+ desiredBehavior:
85
+ "Generated apps preserve the runtime canvas surface and do not replace, hide, or make the shell backing transparent when product renderers customize their own background.",
86
+ enforcement: ["browser-helper", "starter-agents", "spec-checklist"],
87
+ id: "canvas-surface-preserved",
88
+ level: "invariant",
89
+ title: "Canvas backing stays runtime-owned",
90
+ verdict: "move-to-validator",
91
+ },
92
+ {
93
+ area: "canvas",
94
+ currentConstraint:
95
+ "Product editing handles may live on the canvas when they manipulate visible product geometry.",
96
+ desiredBehavior:
97
+ "AI chooses canvas handles only for direct geometry or parameter manipulation and keeps handles tokenized, textless, export-excluded, and runtime-bound.",
98
+ enforcement: ["browser-helper", "acceptance-validator", "spec-checklist"],
99
+ id: "canvas-handle-placement",
100
+ level: "heuristic",
101
+ title: "Canvas handle placement is product-dependent",
102
+ verdict: "keep-but-clarify",
103
+ },
104
+ {
105
+ area: "panels",
106
+ currentConstraint: "PanelHost owns drag, snap, and double-click reset behavior.",
107
+ desiredBehavior:
108
+ "Any rendered application panel preserves runtime panel mechanics and does not recreate panel dragging locally.",
109
+ enforcement: ["runtime", "browser-helper", "starter-agents"],
110
+ id: "panel-host-behavior",
111
+ level: "invariant",
112
+ title: "Panel mechanics stay runtime-owned",
113
+ verdict: "keep-hard",
114
+ },
115
+ {
116
+ area: "layers",
117
+ currentConstraint:
118
+ "Layers are optional and should appear only for multiple editable entities, grouping, visibility, selection, or reorder workflows.",
119
+ desiredBehavior:
120
+ "AI decides whether the product needs layers; single-layer apps do not render a layers panel.",
121
+ enforcement: ["starter-agents", "spec-checklist"],
122
+ id: "layers-enable-only-when-needed",
123
+ level: "heuristic",
124
+ title: "Layer enablement is product-dependent",
125
+ verdict: "keep-but-clarify",
126
+ },
127
+ {
128
+ area: "layers",
129
+ currentConstraint:
130
+ "Once layers are enabled, selection, visibility, reorder, grouping, media lifecycle, and selected-layer controls must work through the real LayersPanel UI.",
131
+ desiredBehavior:
132
+ "Layer-enabled apps prove real layer interactions and product output changes instead of dispatching layer commands directly in browser tests.",
133
+ enforcement: ["acceptance-validator", "browser-helper", "performance-validator"],
134
+ id: "layers-enabled-behavior",
135
+ level: "invariant",
136
+ title: "Enabled layers must fully work",
137
+ verdict: "keep-hard",
138
+ },
139
+ {
140
+ area: "timeline",
141
+ currentConstraint:
142
+ "Timeline is optional, but animated products must first classify animation intent as product transport, editable keyframes, custom reference timeline, or autonomous decorative output.",
143
+ desiredBehavior:
144
+ "AI writes an Animation Intent Inventory before choosing no timeline, playback, keyframes, or custom reference timeline; user-requested product animation defaults to playback unless explicitly justified as autonomous output.",
145
+ enforcement: ["starter-agents", "spec-checklist"],
146
+ id: "timeline-mode-choice",
147
+ level: "heuristic",
148
+ title: "Timeline mode is chosen from behavior",
149
+ verdict: "keep-but-clarify",
150
+ },
151
+ {
152
+ area: "timeline",
153
+ currentConstraint:
154
+ "Enabled timeline modes must control renderer time, pause, scrub, duration, loop, and keyframe evaluation where relevant.",
155
+ desiredBehavior:
156
+ "Playback and keyframe apps prove runtime timeline state controls visible and exported output, and renderer cycle duration follows state.timeline.durationSeconds.",
157
+ enforcement: ["acceptance-validator", "browser-helper", "performance-validator"],
158
+ id: "timeline-enabled-behavior",
159
+ level: "invariant",
160
+ title: "Enabled timelines must drive output",
161
+ verdict: "keep-hard",
162
+ },
163
+ {
164
+ area: "controls",
165
+ currentConstraint:
166
+ "Every visible schema control must bind to runtime state, reset from schema defaults, and have acceptance and browser coverage.",
167
+ desiredBehavior:
168
+ "Controls are not decorative; each visible control proves its product responsibility through runtime and output observables.",
169
+ enforcement: ["acceptance-validator", "browser-helper"],
170
+ id: "controls-product-coverage",
171
+ level: "invariant",
172
+ title: "Visible controls must affect the product",
173
+ verdict: "keep-hard",
174
+ },
175
+ {
176
+ area: "controls",
177
+ currentConstraint:
178
+ "Product-output apps expose final output delivery through sticky footer panelActions.",
179
+ desiredBehavior:
180
+ "Static products include Export PNG; animated products include Export Video and Export PNG. Copy can be secondary, but it does not replace export. Product apps expose Background and Include background controls; standard export helpers own runtime PNG transparency and retina dimensions, while live preview, workspace canvas backing, and video keep the background.",
181
+ enforcement: ["acceptance-validator", "performance-validator", "browser-helper", "starter-agents"],
182
+ id: "output-export-required",
183
+ level: "invariant",
184
+ title: "Product output always has export",
185
+ verdict: "move-to-validator",
186
+ },
187
+ {
188
+ area: "controls",
189
+ currentConstraint:
190
+ "Labels, color placement, section grouping, selector order, and inline density need product-aware decisions.",
191
+ desiredBehavior:
192
+ "Apps define a Control Section Inventory before schema authoring: every controls section has a product entity or workflow stage, related targets stay together, and sections are not named after UI component types.",
193
+ enforcement: ["acceptance-validator", "schema-normalization", "docs", "starter-agents"],
194
+ id: "controls-layout-heuristics",
195
+ level: "heuristic",
196
+ title: "Control layout remains product-aware",
197
+ verdict: "keep-but-clarify",
198
+ },
199
+ {
200
+ area: "renderer",
201
+ currentConstraint:
202
+ "Custom renderers must declare rendererTechnique and rendererTechnique.layers when product output uses semantic layers or mixed rendering.",
203
+ desiredBehavior:
204
+ "AI chooses rendering technology from product output semantics, fidelity, reference behavior, and performance, then proves that choice in typed config and browser tests.",
205
+ enforcement: ["performance-validator", "browser-helper", "spec-checklist"],
206
+ id: "renderer-technique-inventory",
207
+ level: "default",
208
+ title: "Renderer technique is a typed decision",
209
+ verdict: "keep-but-clarify",
210
+ },
211
+ {
212
+ area: "reference-clone",
213
+ currentConstraint:
214
+ "Reference-runtime-clone mode preserves the reference runtime as source of truth unless a redesign is explicit.",
215
+ desiredBehavior:
216
+ "Ported apps keep reference loops, mutable state, transport semantics, media lifecycle, and export behavior before Toolcraft refinements.",
217
+ enforcement: ["acceptance-validator", "browser-helper", "starter-agents"],
218
+ id: "reference-clone-source-of-truth",
219
+ level: "invariant",
220
+ title: "Reference clone preserves behavior",
221
+ verdict: "keep-hard",
222
+ },
223
+ {
224
+ area: "acceptance",
225
+ currentConstraint:
226
+ "Acceptance coverage must prove product responsibility, not only typecheck, component existence, runtime mutation, or shader uniform presence.",
227
+ desiredBehavior:
228
+ "Generated apps fail when a visible entity is disconnected from runtime state, product output, export output, or command side effects.",
229
+ enforcement: ["acceptance-validator", "browser-helper"],
230
+ id: "acceptance-product-observable",
231
+ level: "invariant",
232
+ title: "Acceptance needs product observables",
233
+ verdict: "keep-hard",
234
+ },
235
+ {
236
+ area: "performance",
237
+ currentConstraint:
238
+ "Performance coverage currently asks every visible non-action control for a performance scenario.",
239
+ desiredBehavior:
240
+ "Heavy workload controls get min/default/max workload coverage; ordinary controls get lightweight responsiveness coverage so they cannot hang or break input. Animated previews suspend or coalesce non-essential animation work during canvas drag, pan, pinch, zoom, and radar/center interactions without changing user playback state. A full performance checkpoint is required when the first working version of an app exists, when renderer/canvas/animation/export/timeline/layers change, after fixing a bug that previously broke functionality, after any performance optimization, and whenever the user asks to optimize performance, fix lag, remove jank, speed up animation, or stabilize drag/zoom. Browser performance tests read budgets from typed performance config and run sequentially for stable measurements.",
241
+ enforcement: ["performance-validator", "browser-helper", "starter-agents"],
242
+ id: "performance-coverage-levels",
243
+ level: "invariant",
244
+ title: "Performance coverage has workload and responsiveness levels",
245
+ verdict: "keep-but-clarify",
246
+ },
247
+ {
248
+ area: "persistence",
249
+ currentConstraint:
250
+ "Persistence policy must be deliberate; generated apps should not write runtime state to localStorage directly.",
251
+ desiredBehavior:
252
+ "AI states whether persistence exists, what is persisted, and how reset, import, clear, and new media affect stored state. Apps with localStorage persistence must include browser acceptance for changing a user setting, reloading the page, and observing the restored value or product output; settings import/export is not a substitute for persistence.",
253
+ enforcement: ["starter-agents", "spec-checklist", "acceptance-validator", "browser-helper"],
254
+ id: "persistence-policy-explicit",
255
+ level: "default",
256
+ title: "Persistence is explicit app policy",
257
+ verdict: "keep-but-clarify",
258
+ },
259
+ {
260
+ area: "workflow",
261
+ currentConstraint:
262
+ "Template app work must use brainstorming, writing-plans, systematic-debugging, and browser verification when the environment supports those skills.",
263
+ desiredBehavior:
264
+ "Workflow skills guide the generation process, while product implementation plans stay focused on app files, tests, build, and browser verification.",
265
+ enforcement: ["starter-agents", "docs"],
266
+ id: "workflow-required",
267
+ level: "invariant",
268
+ title: "Required workflow stays part of the contract",
269
+ verdict: "remove-duplicate",
270
+ },
271
+ ] as const satisfies readonly ToolcraftDecisionRule[];
272
+
273
+ export function getToolcraftDecisionRule(
274
+ id: string,
275
+ ): ToolcraftDecisionRule | undefined {
276
+ return TOOLCRAFT_DECISION_CONTRACT.find((rule) => rule.id === id);
277
+ }
278
+
279
+ export function getToolcraftDecisionRulesByArea(
280
+ area: ToolcraftDecisionArea,
281
+ ): ToolcraftDecisionRule[] {
282
+ return TOOLCRAFT_DECISION_CONTRACT.filter((rule) => rule.area === area);
283
+ }
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ getToolcraftComponentContract,
5
+ getToolcraftDecisionRule,
6
+ } from "./index";
7
+
8
+ describe("Toolcraft template contracts barrel", () => {
9
+ it("exports component and decision contracts together", () => {
10
+ expect(getToolcraftComponentContract("slider")?.visualComponent).toBe("Slider");
11
+ expect(getToolcraftDecisionRule("canvas-no-app-ui")?.level).toBe("invariant");
12
+ expect(getToolcraftDecisionRule("workflow-required")?.area).toBe("workflow");
13
+ });
14
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./component-contracts";
2
+ export * from "./decision-contracts";
3
+ export * from "./types";
@@ -0,0 +1,56 @@
1
+ export type ToolcraftContractKind =
2
+ | "canvas"
3
+ | "command"
4
+ | "composition"
5
+ | "control"
6
+ | "panel"
7
+ | "persistence"
8
+ | "settings"
9
+ | "toolbar";
10
+
11
+ export type ToolcraftStateMode = "command-only" | "controlled" | "runtime-owned";
12
+
13
+ export type ToolcraftSectionLayout = "grouped" | "standalone";
14
+
15
+ export type ToolcraftControlLayoutGroupColumns = 2;
16
+
17
+ export type ToolcraftControlLayoutGroupLayout = "inline";
18
+
19
+ export type ToolcraftLabelPolicy = "component-owned" | "hidden" | "optional" | "required";
20
+
21
+ export type ToolcraftPanelPlacement = "bottom" | "left" | "right" | "top";
22
+
23
+ export type ToolcraftPanelSnapEdge = "bottom" | "left" | "right" | "top";
24
+
25
+ export type ToolcraftControlDecisionStrictness =
26
+ | "best-fit"
27
+ | "custom-escape-hatch"
28
+ | "exact-owner";
29
+
30
+ export type ToolcraftControlDecisionCatalog = {
31
+ acceptableAlternatives?: readonly string[];
32
+ doNotReplaceWith?: readonly string[];
33
+ layoutConstraints?: readonly string[];
34
+ ownsValueModel: readonly string[];
35
+ requiredAcceptance: readonly string[];
36
+ strictness: ToolcraftControlDecisionStrictness;
37
+ useWhen: readonly string[];
38
+ };
39
+
40
+ export type ToolcraftComponentContract = {
41
+ aiUsageRules?: readonly string[];
42
+ capabilities?: readonly string[];
43
+ commands?: readonly string[];
44
+ decisionCatalog?: ToolcraftControlDecisionCatalog;
45
+ defaultPlacement?: ToolcraftPanelPlacement;
46
+ defaultSectionLayout?: ToolcraftSectionLayout;
47
+ historyPolicy?: "never" | "optional" | "patch";
48
+ id: string;
49
+ kind: ToolcraftContractKind;
50
+ labelPolicy?: ToolcraftLabelPolicy;
51
+ requiredWrapper?: "PanelHost";
52
+ schemaType?: string;
53
+ snapEdges?: readonly ToolcraftPanelSnapEdge[];
54
+ stateMode: ToolcraftStateMode;
55
+ visualComponent: string;
56
+ };