@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
+ # Assembly Workflow
2
+
3
+ Build the app from the local Toolcraft runtime copy. Do not recreate controls, panels, toolbar, canvas behavior, timeline, layers, or app chrome by hand.
4
+
5
+ Use:
6
+
7
+ - `@/toolcraft/runtime` for schema, contracts, state, commands, history, canvas, panels, timeline, layers, toolbar, and tests.
8
+ - `@/toolcraft/runtime/react` for `ToolcraftApp` and hooks.
9
+ - `@/toolcraft/runtime/styles.css` for runtime styles.
10
+ - `@/toolcraft/ui` for visual components.
11
+
12
+ Declare the product with `defineToolcraft`. Render through `ToolcraftApp`.
13
+
14
+ ```tsx
15
+ import { defineToolcraft } from "@/toolcraft/runtime";
16
+ import { ToolcraftApp } from "@/toolcraft/runtime/react";
17
+
18
+ const appSchema = defineToolcraft({
19
+ canvas: { enabled: true, sizing: { mode: "intrinsic-media" }, upload: true },
20
+ panels: {},
21
+ toolbar: { history: true, radar: true, theme: true, zoom: true },
22
+ });
23
+
24
+ export function AppHome() {
25
+ return <ToolcraftApp schema={appSchema} />;
26
+ }
27
+ ```
28
+
29
+ Routes must render `ToolcraftApp` directly. Do not compose `ToolcraftRoot`, `CanvasShell`, `ControlsPanel`, `LayersPanel`, `TimelinePanel`, or `ToolbarPanel` by hand in product routes. If a runtime surface has a performance or behavior issue, fix the shared runtime contract instead of replacing the surface with app-level UI.
30
+
31
+ App-specific source may use only runtime extension points: schema controls, `canvasContent` for product output, `controlRenderers` for true custom controls, `onPanelAction` for sticky footer actions, and runtime commands/hooks. Do not render built-in control components such as `SliderControl`, `SelectControl`, `ColorControl`, `GradientControl`, `FontPickerControl`, `FileDropControl`, or `PanelActionsControl` directly in app code. Declare them in schema so layout, reset, history, disabled states, keyframes, labels, and tests stay runtime-owned.
32
+
33
+ Read `appSchema.assembly` before adding custom JSX. It lists enabled surfaces, capabilities, commands, and runtime assumptions.
34
+
35
+ Toolbar history owns undo/redo buttons and keyboard shortcuts. When `toolbar.history` is enabled, the runtime handles `Cmd/Ctrl+Z`, `Cmd/Ctrl+Shift+Z`, and `Ctrl+Y`, while ignoring shortcuts inside inputs, textareas, selects, and editable value labels. Do not add route-local undo/redo keyboard listeners.
36
+
37
+ The starter baseline is deliberately neutral. It must not include demo controls, prompt fields, timeline, or layers until the product behavior requires them. Use tests and docs fixtures to exercise component coverage; do not expose those fixtures in the starting product schema.
38
+
39
+ Once the folder is a real product, switch `src/app/app-acceptance.ts` from neutral readiness to:
40
+
41
+ ```ts
42
+ export const appProductReadiness = {
43
+ mode: "product",
44
+ productName: "Product name",
45
+ productSummary: "What the app creates or edits.",
46
+ requestedBehavior: "The user-facing behavior this app must implement.",
47
+ } as const;
48
+ ```
49
+
50
+ Do not leave `mode: "starter"` in a renamed product folder or after adding product controls, `canvasContent`, timeline, layers, or acceptance rows.
51
+
52
+ ## Control Sections
53
+
54
+ Before writing the schema, make a Control Section Inventory. Each section needs a product entity or workflow stage, included targets, and a reason for grouping. Do not group by control type.
55
+
56
+ Bad section titles: `Controls`, `Settings`, `Options`, `Sliders`, `Inputs`, `Buttons`, `Color`, `Colors`.
57
+
58
+ Good section titles name the thing being edited: `Background`, `Object`, `Square 1 (Right)`, `Token Pattern`, `Motion`, `Tone Mapping`, `Export`.
59
+
60
+ Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created setup/settings sections use the technical title `Setup` but render without a visible heading; sticky footer action sections use the technical title `Export` but render without a visible heading.
61
+
62
+ Every visible section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon. Do not hand-build section headers in generated apps.
63
+
64
+ Section expand/collapse uses the standard runtime height/opacity animation. Do not replace it with instant custom section visibility.
65
+
66
+ Ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Runtime technical `Setup` / settings sections use 12px top and bottom spacing to match side padding. Sticky footer action sections keep their dedicated spacing.
67
+
68
+ If a color, slider, input, or selector edits the same entity as nearby controls, keep it in that entity section. Split only when the product has a real workflow split and cover that decision in acceptance.
69
+
70
+ Before choosing the concrete control type for each target, check `component-rules.md` and `schema-reference.md`. Built-in compound controls must stay compound: for example, typography with font choice, weight, size, color/opacity, and text rhythm uses `fontPicker`, not a plain `select` plus separate inputs/sliders. The product renderer and acceptance rows must cover every semantic value part of the chosen component.
71
+
72
+ ## Figma Source
73
+
74
+ When the prompt provides a Figma URL, treat the Figma file as the design source of truth.
75
+
76
+ Required flow:
77
+
78
+ - Use Figma MCP/design context before implementation.
79
+ - Inspect the target node, layer tree, component instances, variants, text nodes, variables, styles, and assets.
80
+ - Recreate the design from the Figma structure and Toolcraft runtime/component contracts.
81
+ - Use screenshots only for final visual QA after reading the file structure.
82
+
83
+ Do not implement a Figma design by eye from an image, screenshot, exported PNG, or rough visual memory. If the Figma URL is not node-specific, inspect the file/page metadata and choose the relevant node only when it is unambiguous; otherwise ask for a node-specific link.
84
+
85
+ ## Product Output
86
+
87
+ Use `canvasContent` only for product output: WebGL, Canvas 2D, SVG, DOM product text, shader previews, generated previews, export previews, or product editing handles.
88
+
89
+ ```tsx
90
+ <ToolcraftApp
91
+ canvasContent={<ProductRenderer />}
92
+ renderDefaultCanvasMedia={false}
93
+ schema={appSchema}
94
+ />
95
+ ```
96
+
97
+ `canvasContent` must not contain app UI: buttons, forms, CTAs, upload prompts, helper text, settings, menus, labels, placeholder copy, or empty-state instructions.
98
+
99
+ Product text rendered as DOM must be marked with `data-toolcraft-product-output` or `data-toolcraft-product-text`. Product editing handles must be textless overlays, write to runtime state, and stay out of export/copy output.
100
+
101
+ Preserve the runtime canvas surface. Product renderers may draw their own output background, but must not hide, replace, or make the Toolcraft canvas backing transparent.
102
+
103
+ ## Reference Runtime Clone
104
+
105
+ When porting an existing app, use `transferMode: "reference-runtime-clone"` unless the user explicitly asks for redesign.
106
+
107
+ Preserve the reference runtime as source of truth:
108
+
109
+ - animation loop and time ownership;
110
+ - refs and mutable renderer state;
111
+ - particles, objects, connections, spawn cadence, and lifetime rules;
112
+ - pause/resume, restart, progress, export, and copy semantics;
113
+ - canvas sizing and media lifecycle;
114
+ - control-to-renderer mapping.
115
+
116
+ Toolcraft still owns the shell: schema, controls, canvas, panels, toolbar, file upload, sticky footer actions, and `canvasContent`.
117
+
118
+ Do not iframe the reference, replace the route with copied original UI, or rebuild the app as a different shell.
119
+
120
+ ## Animation Intent
121
+
122
+ Before adding animation controls, write an Animation Intent Inventory. Classify the animation as playback timeline, keyframes timeline, custom reference timeline, or autonomous decorative output.
123
+
124
+ If the user asks for product animation, use the top playback timeline by default. Use no timeline only when the animation is self-running output with no user-facing play/pause, scrub, duration, loop, restart, progress, or export-at-time behavior. In that case, declare `starterTransferMode.animationIntent.mode = "autonomous"` and list the absent transport behavior in `behaviorCoverage`.
125
+
126
+ Do not replace `TimelinePanel` with an app-level playback, transport, or timeline panel to work around performance. Playback/keyframe timeline UI is runtime-owned. Custom timeline UI is allowed only when a reference app has non-Toolcraft timeline behavior and `starterTransferMode.referenceTimeline.mode` is `"custom-reference-timeline"` with browser-backed `referenceTimelineCoverage`.
127
+
128
+ Animated preview renderers must prioritize viewport interactions. During canvas drag, pan, pinch, zoom, and radar/center, suspend or coalesce non-essential animation work, then resume from the correct timeline or autonomous time without changing the user's play/pause state.
129
+
130
+ ## Canvas Sizing And Background
131
+
132
+ A base/default size in the prompt is the initial output size. It does not remove user-facing size controls. Use `editable-output` unless the reference or product explicitly locks dimensions, and cover any `fixed-output` choice with `canvasSizingCoverage: "fixed-output-size"`.
133
+
134
+ Every product app exposes output background controls:
135
+
136
+ - `appearance.background` or `scene.background` as a schema `color` control;
137
+ - `export.includeBackground` as a `switch`, `checkbox`, `select`, or `segmented` control.
138
+
139
+ Preview, PNG export, and video export read the background color runtime value. PNG export passes the include-background runtime value to the export helper. Turning `export.includeBackground` off makes only PNG output transparent; live preview, workspace canvas backing, and video output keep the background.
140
+
141
+ Keep those controls together in one `Background` section. When the section title supplies the full context, prefer an inline row with hidden-label `export.includeBackground` on the left and the background color parameter on the right; do not repeat `Background` as both the section title and switch label.
142
+
143
+ Every product app needs output delivery in sticky footer `panelActions`. Still-output apps expose `Export PNG`. Animated apps expose `Export Video` and `Export PNG`. Clipboard copy is optional and never replaces export. If an odd number of footer actions leaves one action alone in the final row, that final action spans the full row.
144
+
145
+ Async product actions such as Export, Download, Copy, Generate, or Apply must return the real Promise from `ToolcraftApp onPanelAction`. The controls panel uses that Promise to show the sticky footer top accent indicator while the operation is pending. Use the `reportProgress(0..1)` callback from `onPanelAction` for determinate progress; video export reports frame-based render/encode progress, and PNG export reports phase progress for render, blob, and handoff when those phases are asynchronous. Do not fire-and-forget long export work from `onPanelAction`, and do not add custom loading strips or canvas UI for export progress.
146
+
147
+ For complex apps, use schema `settingsTransfer: "auto"` or `true` for settings import/export. Recalculate settings-transfer eligibility after adding, removing, or reorganizing controls, sections, timeline, or layers. The runtime threshold is 12 product controls, 5 product sections, or weighted score 18. Do not put Import Settings or Export Settings in sticky footer `panelActions`; runtime inserts the technical `Setup` settings-transfer section first without a visible section heading.
148
+
149
+ If the app also uses `editable-output` canvas sizing, that first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Canvas width`, and `Canvas height` in that order. Do not split the canvas size fields and settings-transfer actions into app-authored sections.
150
+
151
+ If a controls panel shows only `Export Settings` and `Import Settings` in the first runtime section, check the canvas sizing decision. Product-output apps usually need `editable-output`; intrinsic media and explicitly fixed output are the cases where visible canvas size inputs are absent.
152
+
153
+ For user-edited settings that should survive reload, use schema `persistence` with a stable app-specific key. When localStorage persistence is enabled, acceptance must prove a user setting restores after a real browser reload. Do not use settings import/export as a workaround for broken persistence.
154
+
155
+ Animated apps with `Export Video` must include a separate `Video Export` controls section with at least:
156
+
157
+ - `export.video.format` as `select`, defaulting to `mp4`, with `mp4` and `webm` baseline options;
158
+ - `export.video.resolution` as `select`, defaulting to `current`, with options such as `current` and `4k`.
159
+
160
+ Place `Video Export` as the final authored controls section directly above sticky footer export buttons. Treat `Format` and `Resolution` as a compact semantic pair and put them in one two-column inline row by default. Use vertical rows only when the compact row would clip labels or selected values, and record that fallback reason in the worklog.
161
+
162
+ Use standard export helpers. `createToolcraftPngExportCanvas` applies retina sizing and accepts `includeBackground` for runtime PNG transparency. Do not rely on static `export.png.background` alone when the UI exposes background controls. Video export keeps background and still uses `getToolcraftRetinaExportSize`.
163
+
164
+ Video export must choose the actual MIME/container with `MediaRecorder.isTypeSupported(...)` or an explicit encoder/transcoder capability check. `MOV` and `ProRes` are allowed only when the app provides a custom encoder/transcoder and proves it with acceptance plus performance coverage. Treat `4K` as an export resolution target, not a hardcoded canvas lock. Offline rendered-frame export must encode or mux frame timestamps from runtime timeline time; real-time `canvas.captureStream()` plus `MediaRecorder` records wall-clock export time and is not enough when renderer work can be slower than playback. Browser acceptance must load the exported blob as a video, wait for metadata, and compare `video.duration` with the edited timeline duration; `blobSize > 0`, `blobType`, parser fallback, or assigning the expected duration in `catch` is not enough.
165
+
166
+ ## Verification Tiers
167
+
168
+ Before every edit, classify the change by blast radius and write the planned checks in the implementation note or plan:
169
+
170
+ ```md
171
+ Verification tier: Tier N
172
+ Reason: <changed surface and expected blast radius>
173
+ Run: <commands and browser checks>
174
+ Skip: <checks not needed for this pass and why>
175
+ ```
176
+
177
+ Use these tiers:
178
+
179
+ | Tier | Use When | Required Checks |
180
+ | --- | --- | --- |
181
+ | Tier 0 — docs/copy | Documentation, comments, copy, labels, or titles change without schema targets, values, runtime behavior, renderer output, or layout mechanics. | Targeted docs/typecheck or targeted app test. Browser is not required unless visual text fitting is the risk. |
182
+ | Tier 1 — local control presentation | One control or panel visual state changes: spacing, hover, focus, disabled, marker visibility, label fit, or component variant display. Runtime state shape and product renderer are unchanged. | Targeted unit/component test plus one focused browser check for the affected control or panel. |
183
+ | Tier 2 — schema/product behavior | Controls, sections, defaults, persistence, panel actions, export actions, acceptance rows, or product behavior mapping changes. | `pnpm verify:quick` plus relevant browser acceptance. Run perf only when the changed control affects renderer workload or responsiveness. |
184
+ | Tier 3 — renderer/canvas/runtime feature | Custom renderer, animation loop, canvas sizing, upload/media, timeline, layers, toolbar, export bytes, WebGL/Canvas/SVG output, zoom, radar, history, heavy control behavior changes, or a post-generation iteration that touches renderer workload or viewport stability. | `pnpm verify:quick`, targeted browser acceptance, and relevant `pnpm verify:perf` scenarios for touched workload/viewport/export paths. |
185
+ | Tier 4 — final delivery/template architecture | Fresh generated app completion, folder export, commit-ready delivery, dependency changes, runtime/template/contract/CLI changes, broad refactors, or major post-generation iterations that rewrite renderer, canvas, animation, timeline/keyframes, layers, media, export, or control mapping. | Fresh folders run `pnpm install` once, then `pnpm verify:final`, then start `pnpm dev` to provide the local URL. |
186
+
187
+ Choose the tier by blast radius, not by line count. If uncertain, move one tier higher, not automatically to Tier 4.
188
+
189
+ Do not rerun `pnpm install` after every edit. Run it after fresh export, dependency changes, lockfile changes, or a missing package error.
190
+
191
+ Use `pnpm verify:ui` when a tier calls for the browser acceptance suite without the performance suite. Use a focused named Playwright test instead when only one entity changed and the relevant test is already known.
192
+
193
+ Run a full performance checkpoint with `pnpm verify:perf` when the first working version of the app exists, when renderer/canvas/animation/export/timeline/layers change, after fixing a bug that previously broke functionality, after any performance optimization, or when the user asks to optimize performance, fix lag, remove jank, speed up animation, or stabilize drag/zoom.
194
+
195
+ Fast feature loops may defer full performance only when none of the checkpoint triggers apply. Record the deferred check and reason in the worklog.
196
+
197
+ For final delivery, run:
198
+
199
+ ```bash
200
+ pnpm verify:final
201
+ pnpm dev
202
+ ```
203
+
204
+ Browser verification must use the real Toolcraft shell plus renderer output. `pnpm verify:final` runs the full static, build, browser, and browser performance gate. `pnpm dev` is intentionally separate because it keeps the local server running.
205
+
206
+ Do not stop existing local servers to free `3002`. `pnpm dev`, `pnpm preview`, and browser verification prefer `3002`, then automatically use the next free port when it is occupied.
@@ -0,0 +1,299 @@
1
+ # Component Rules
2
+
3
+ ## Control Decision Catalog
4
+
5
+ Choose controls by product value model before UI appearance.
6
+
7
+ - Exact owner: if the value model belongs to a built-in, use that built-in.
8
+ - Best fit: if multiple built-ins can work, choose one and record the reason.
9
+ - Custom escape hatch: use custom controls only after documenting checked built-ins and why the closest one is insufficient.
10
+
11
+ If a built-in owner is discovered after a custom workaround, replace the workaround with the built-in.
12
+
13
+ Common exact-owner choices:
14
+
15
+ - Use `gradient` for adjustable gradients, color transitions, gradient fills, stops, type, and angle. Do not replace it with two `color` controls. The built-in Gradient owns type/angle, the draggable stop track, and the Stops list; the full Gradient control uses content-width internal dividers only when it shares a section with sibling controls, with 18px between each divider and the control content. If Gradient is the first control in that section, only the bottom internal divider renders.
16
+ - Use `fontPicker` for typography that includes font family, weight, size, text case, text color/opacity, letter spacing, or line height.
17
+ - Use `colorOpacity` when one product entity owns both color and opacity.
18
+ - Use `rangeSlider` or `rangeInput` for lower/upper bounds or from/to ranges.
19
+ - Use `curves` for editable tone, response, easing, remapping, opacity, depth, mask, or channel curves.
20
+ - Use `vector` for position, offset, direction, focus, anchor, light direction, or color-balance pads.
21
+ - Use `fileDrop` for source material uploads.
22
+ - Use `imagePicker` for choosing one visual option from a set.
23
+ - Use `palette` only for constrained design-token color choices with both family and shade: brand palette, Tailwind-like token color, style-guide color scale, semantic palette family, or theme accent token.
24
+ - Use `actions` for local section commands that affect only the nearby entity, such as randomize palette, normalize weights, sort glyphs, clear selection, duplicate item, or reset current stop.
25
+ - Use `panelActions` for sticky final product actions such as export, copy, generate, apply, or download.
26
+
27
+ Small action buttons inside custom controls are for item-level actions such as remove, reorder, add stop, or delete stop. Use schema `actions` for section-level local commands. Keep final product actions in `panelActions`, keep timeline transport in the top timeline, and keep global reset in the controls panel header.
28
+
29
+ For local reset-like `actions`, use product-specific values such as `reset-current-layer`, `reset-palette`, or `reset-current-stop` and handle them through `ToolcraftApp onPanelAction`. Do not use a bare `reset` value unless the action intentionally runs global `controls.reset`.
30
+
31
+ ## Dividers
32
+
33
+ - Full-width dividers belong only to panel sections.
34
+ - Large built-in compound controls inside a section render content-width internal dividers only when their parent section contains more than one visible control item. Keep 18px between each rendered internal divider and the compound control content. If the compound control is the first item in that section, render only its bottom internal divider and remove the top internal padding. This applies to `gradient`, `fontPicker`, RGB `curves`, `channelMixer`, and `palette`. Single `curves` are one labeled control and do not render internal dividers.
35
+ - If a section contains exactly one control, whether simple or compound, render only the parent section dividers.
36
+ - Do not add full-width borders inside a compound control, and do not put dividers only around an internal subsection such as Gradient Stops.
37
+ - Small compound fields such as `colorOpacity` and `rangeInput` stay inline fields without section dividers.
38
+
39
+ ## Sliders
40
+
41
+ Slider `step` means numeric snapping only. It does not make a slider visually discrete by itself.
42
+
43
+ Classify every stepped slider as either `stepped continuous` or `visual discrete` in the spec and schema tests.
44
+
45
+ Use `variant: "discrete"` for semantic integer domains where markers help choose positions: counts, rows, columns, levels, bands, passes, points, tiles, segments, finite position choices, and finite animation-step controls such as flip depth, character count, glyph steps, or frame steps.
46
+
47
+ Keep large or precision stepped ranges visually continuous, even with `step`: speed, FPS, rate, duration, seconds, milliseconds, density, size, intensity, quality, and other ranges with many positions.
48
+
49
+ Visual discrete sliders must declare `step`; the runtime derives one marker per value position from `min`, `max`, and `step`. Visual discrete sliders with too many positions are invalid because they produce marker noise.
50
+
51
+ Schema sliders always render stacked at full width. Do not put `slider` or `rangeSlider` controls in two-column inline rows. The only built-in exception is `fontPicker`, whose letter-spacing and line-height footer sliders stay paired inside that component.
52
+
53
+ Range sliders are always full-width two-thumb controls. Do not put a `rangeSlider` in an inline row. Its `defaultValue` must start with different lower and upper values, such as `[20, 80]`, so the control does not collapse into a single-value slider.
54
+
55
+ Range slider value editing accepts common range separators such as `20/80`, `20-80`, `20 - 80`, `20 80`, and en-dash ranges. Use the built-in parser instead of adding custom label parsing.
56
+
57
+ Discrete sliders must still drag smoothly. Heavy preview work must be debounced, coalesced, or deferred.
58
+
59
+ When a slider or range slider is intentionally unavailable, use schema `disabled: true`. Do not draw custom disabled-looking slider rows or disable only the renderer response while leaving the control active.
60
+
61
+ When a slider is meaningful only in some mode values, keep it visible and use `disabledWhen`:
62
+
63
+ ```ts
64
+ fillAmount: {
65
+ type: "slider",
66
+ label: "Fill level",
67
+ target: "distribution.fillAmount",
68
+ disabledWhen: {
69
+ target: "distribution.fillMode",
70
+ equals: "full",
71
+ },
72
+ }
73
+ ```
74
+
75
+ The disabled value is preserved. When the user switches back to a mode where the control is meaningful, the slider becomes active with its previous value.
76
+
77
+ Use `visibleWhen` instead of `disabledWhen` when a control or section belongs only to another template, type, mode, variant, or count. Example: in a co-brand lockup, `Partner` belongs to text identity mode and `Partner logo` belongs to logo identity mode. For count-controlled banks, hide inactive siblings: if `Shades` is `2`, `Shade 3`, `Shade 4`, and `Shade 5` are not visible. Do not keep inactive controls visible and enabled while making the renderer ignore them.
78
+
79
+ ## Palette
80
+
81
+ Use `palette` only when the product needs a constrained token palette: family plus shade. It is for design-system color tokens, not for arbitrary color entry.
82
+
83
+ Use `color` for free hex colors, `colorOpacity` when opacity belongs to the same color entity, `gradient` for color transitions, and `fontPicker` when the color belongs to typography. Browser acceptance must change both `palette.family` and `palette.shade` and prove the rendered/exported output consumes both parts.
84
+
85
+ ## Segmented Controls
86
+
87
+ Use segmented controls only for compact mode choices that preserve every cell's internal padding.
88
+
89
+ Limits:
90
+
91
+ - at most four options;
92
+ - no option label longer than nine characters;
93
+ - no more than twenty-four total option-label characters.
94
+
95
+ If cells clip, collide, lose padding, or force labels into adjacent cells, shorten labels first. If compact labels still fail, use `select`.
96
+
97
+ ## Sections
98
+
99
+ Build controls-panel sections from product entities and workflow stages, not component types. Keep sections discrete: two to seven product controls is the normal size. When a section grows past seven controls or mixes several meanings, split it into specific sections such as `Flow Motion`, `Flow Geometry`, `Letter Burst`, `Shape Colors`, `Logo Glow`, `Logo Plate`, or `Text Block`. Do not reuse the same section title for multiple sections.
100
+
101
+ Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created setup/settings sections use the technical title `Setup` but render without a visible heading; sticky footer action sections use the technical title `Export` but render without a visible heading. Do not omit a title on app-authored body sections to avoid naming decisions; choose the nearest honest product context instead.
102
+
103
+ Every visible section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon. Do not hand-build section headers in generated apps.
104
+
105
+ Section expand/collapse uses the standard runtime height/opacity animation. Do not replace it with instant custom section visibility.
106
+
107
+ Ordinary section collapsed/expanded state persists as a per-app runtime UI preference. It is not undo/redo state, not settings import/export state, and `Reset controls` must not clear it. Runtime technical `Setup` / settings sections and sticky footer `Export` sections are not collapsible.
108
+
109
+ Ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Runtime technical `Setup` / settings sections use 12px top and bottom spacing to match side padding. Sticky footer action sections keep their dedicated spacing.
110
+
111
+ ## Colors
112
+
113
+ First identify the semantic entity the color belongs to: background, object, connector, glow, tone mapping, brand, export, or a named product object.
114
+
115
+ Keep color inside a section when it configures the same entity as nearby controls. Use a standalone color section only when color is the whole semantic section.
116
+
117
+ Standalone color section titles must describe product role. Never generate a section titled `Color` or `Colors`. If no meaningful role exists, use a neutral title such as `Appearance` instead of omitting the title.
118
+
119
+ Show visible field labels for `color` and `colorOpacity` controls when the section also contains any non-color controls. Omit visible color labels only when the whole section is made of color controls.
120
+
121
+ Multiple related plain colors stay in the same section and render at most two per row. If any color control has opacity, keep it stacked instead of placing it in a two-column row.
122
+
123
+ Use `colorOpacity` when one product entity owns both color and opacity, such as text color, shadow color, glow color, overlay color, or stroke color. Do not split that into a separate `color` plus opacity slider/input.
124
+
125
+ When one short numeric/text field and one plain `color` field configure the same entity, they can share a two-column inline row. Example: `Mask size` and `Color` belong in the same `Mask` row instead of two stacked rows. Do not put `colorOpacity` in inline rows.
126
+
127
+ Mixed inline rows require label parity: every field in that row has a visible label. The exception is a section-title-owned toggle + parameter row: a hidden-label `switch` or `checkbox` may sit beside one related parameter when the section title supplies the visible context. Color fields in other mixed rows must not be unlabeled.
128
+
129
+ Renderer-owned output background is a base product control. Use a schema `color` target such as `appearance.background` or `scene.background`, add an `export.includeBackground` control for PNG transparency, and make preview/export read those runtime values. Keep them in one `Background` section. Prefer one inline row with hidden-label `export.includeBackground` on the left and `appearance.background` on the right when no other fit rule is violated. `export.includeBackground` controls only PNG alpha; it must not make live preview, workspace canvas backing, or video output transparent. Do not hardcode a configurable background in CSS, Canvas `fillStyle`, or WebGL clear color.
130
+
131
+ ## File Upload
132
+
133
+ Use `fileDrop` for source material uploads in the controls panel. Do not place upload UI on the canvas.
134
+
135
+ In single-layer apps, the runtime shows uploaded image preview and clear button in the file control. Clearing removes source material from the renderer and canvas.
136
+
137
+ In multi-layer apps, deletion and visibility belong to the Layers panel; `fileDrop` stays an upload target.
138
+
139
+ ## Image Picker
140
+
141
+ Every visible `ImagePicker` item must be actionable in the current product context. Do not show choices that sanitize to fallback or no-op behavior.
142
+
143
+ Sizing:
144
+
145
+ - two options: large tiles;
146
+ - three or six options: medium tiles;
147
+ - larger sets: small tiles.
148
+
149
+ Filter or split choices by template, mode, or selected object when only some choices are valid.
150
+
151
+ ## Font Picker
152
+
153
+ Use `fontPicker` for typography choices that need font preview plus weight, size, text-case, text color/opacity, letter-spacing, and line-height controls. Do not recreate it with a plain `select`, custom font list, or separate typography inputs.
154
+
155
+ The value is one object: `{ fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, opacity }`. Typography renderers and exports must consume all eight parts.
156
+
157
+ If `fontPicker` controls product text, the preview renderer and export renderer must apply the selected `fontId`, `fontWeight`, `fontSize`, `letterSpacing`, `lineHeight`, `textCase`, `color`, and `opacity` to that actual text. Do not stop at updating runtime state, the select label, or the popup preview.
158
+
159
+ The component owns search, category filters, virtualized scrolling, font preview loading, selected-row behavior, the font-weight select, the font-size input, the text-case select, the color/opacity control, and the two footer sliders. Browser acceptance must choose a different font, change weight, change size, change text case, change color/opacity, move Letter spacing, and move Line height.
160
+
161
+ `fontPicker` is an atomic typography block. Do not place sibling schema controls for `Case`, `Weight`, `Size`, `Letter spacing`, `Line height`, `Color`, or `Opacity` when they affect the same product text entity. If a typography part is missing from the built-in value model, extend `fontPicker` in the kit instead of composing a neighboring control.
162
+
163
+ Do not add `description` to `fontPicker` just to list these owned fields. If the section title and visible field labels already make the text target clear, omit `description`; use it only for non-obvious product scope.
164
+
165
+ ## Vector
166
+
167
+ One vector control in the controls panel uses the square X/Y pad. Multiple vector controls use compact pads so the sidebar does not become too tall.
168
+
169
+ Use variants by product meaning:
170
+
171
+ - default: position, offset, direction, focus, anchor, light direction;
172
+ - `whiteBalance`: temperature and tint;
173
+ - `colorBalance`: paired color-balance axes;
174
+ - `chromaOffset`: RGB or chromatic offset;
175
+ - `toneBias`: split-tone, duotone, or color-grading bias.
176
+
177
+ Do not add custom vector sizing props. Choose the right number, variant, and section grouping, then let runtime sizing handle the pad.
178
+
179
+ ## Curves
180
+
181
+ Use `curves` for editable remapping curves. First decide the curve variant by product meaning; do not rely on the runtime default.
182
+
183
+ Use `variant: "single"` for one standalone curve without channel tabs, such as acceleration, bend, easing, opacity response, depth response, mask response, threshold response, tone response, or another single mapping curve. Do not create a custom curve UI just to remove RGB tabs.
184
+
185
+ RGB Curves is the color-correction or channel-specific case. Use RGB/R/G/B tabs only when the product edits RGB channels, color correction, color grading, or channel curves. Do not force RGB/R/G/B tabs onto products that need only one response, bend, depth, or easing curve.
186
+
187
+ Single Curves is one labeled control and does not use internal dividers. RGB Curves is the compound variant because it contains channel tabs plus curve points, so it follows the compound divider rules when mixed with sibling controls.
188
+
189
+ Choose interpolation by product meaning:
190
+
191
+ - `interpolation: "smooth"` for photo/editor-like visual tone, color, and RGB curves where the curve should feel like a creative spline;
192
+ - `interpolation: "monotone"` for depth, response, mask, opacity, threshold, and data-mapping curves where order must be preserved and overshoot is unsafe.
193
+
194
+ Single curves default to monotone. If a single curve is still a creative visual tone curve, set `interpolation: "smooth"` explicitly.
195
+
196
+ Acceptance for curves should include an off-center control point near an edge so smooth-vs-monotone interpolation mistakes are visible in the actual product output, not only in the curve UI.
197
+
198
+ ## Text And Code
199
+
200
+ Use `text` for short single-line strings: names, small values, compact prompts, titles, and tokens.
201
+
202
+ For `text`, separate content from settings. `commitMode` defaults to `"content"`: content strings such as prompts, names, titles, tokens, and short text update while the user types. Use `commitMode: "setting"` for text inputs that edit settings such as font size, numeric-like style values, dimensions, ids, or configuration fields; setting text commits on blur or Enter. Canvas width and Canvas height are runtime-owned editable-size fields and always commit on blur or Enter.
203
+
204
+ Use `code` / `CodeTextarea` as the base multiline content editor for any potentially long value: prompts, instructions, JSON, CSS, shader code, scripts, templates, or other structured text. It applies while typing, is capped at 12 visible lines, and long content scrolls inside the textarea instead of making the controls panel taller. Do not name a section `Code` unless the product value is actually code.
205
+
206
+ ## Labels
207
+
208
+ Visible control labels should be short UI names, usually one to three words. Do not put explanations, formulas, units, parenthetical hints, or usage instructions in field labels.
209
+
210
+ Short labels must still be semantically sufficient with nearby context. `Animation` / `Speed` is fine because the section names the entity; `Settings` / `Speed` should become `Animation speed`, and mixed visual buckets should use labels such as `Symbol color` or `Background opacity`.
211
+
212
+ Visible control labels can get a runtime-owned filled Phosphor question tooltip icon. Put a concise product-specific explanation in `description` only when it adds meaning beyond the label. Do not write recaps like `Adjusts Opacity`, and do not build custom help icons beside built-in labels.
213
+
214
+ For compound controls such as `fontPicker`, `description` must not enumerate owned fields like font, weight, size, case, color, opacity, letter spacing, or line height. The component already labels those fields.
215
+
216
+ If a source label is unavoidably long, keep the visible label concise and rely on native `title` for the full text.
217
+
218
+ Switch and checkbox labels name the setting context, not the action. Do not prefix them with `Enable` or `Disable`; use `CRT`, `Glow`, `Loop`, or `Guides` instead of `Enable CRT` or `Disable guides`. If the section title already names the setting context, do not repeat that title as the visible toggle label; use `label: false` and keep the meaning in `target` and `description`.
219
+
220
+ Two adjacent `switch` or `checkbox` controls for the same product entity must share one inline row when every visible label fits without truncation. Use short one- or two-word labels such as `Snap X` and `Snap Y`, or `Glow` and `Loop`. The runtime auto-pairs safe adjacent toggles by target entity; use explicit layout groups only when pairing a toggle with a non-toggle parameter. If either label would truncate in half-width, remove the inline group and let the toggles stack.
221
+
222
+ A single `switch` or `checkbox` may share an inline row with one related parameter control when the toggle label fits and the controls edit the same entity. Example: `Loop` plus `Duration`, or hidden-label `export.includeBackground` plus `appearance.background` inside `Background`. If the section title already names the toggle context, hide the toggle label instead of repeating the title.
223
+
224
+ ## Layers
225
+
226
+ Enable layers only when the app has multiple editable objects, media objects, groups, visibility, selection, reorder, or selected-layer controls.
227
+
228
+ Do not show Layers for a single-layer app. Do not use `selectedLayer.*` targets when Layers are disabled.
229
+
230
+ When Layers are enabled, browser tests must use the real LayersPanel UI: select, visibility, reorder, grouping, and media lifecycle when uploads/deletes create or remove layers.
231
+
232
+ ## Timeline
233
+
234
+ Before choosing timeline mode for an animated product, write an Animation Intent Inventory:
235
+
236
+ - `timeline-playback`: user-facing play, pause, scrub, duration, loop, restart, progress, or export-at-time.
237
+ - `timeline-keyframes`: editable diamonds, rows, easing, or keyframe evaluation.
238
+ - `autonomous`: decorative or self-running output with no user-facing transport.
239
+
240
+ User-requested product animation defaults to playback timeline. Use no timeline only for autonomous decorative/self-running animation, and declare `starterTransferMode.animationIntent.mode = "autonomous"` with coverage proving no play/pause, scrub, duration, loop, or export-at-time behavior.
241
+
242
+ Use playback timeline for play, pause, scrub, duration, loop, restart, or export-at-time.
243
+
244
+ Playback renderers must read `state.timeline.currentTimeSeconds`, `state.timeline.durationSeconds`, `state.timeline.isPlaying`, and loop state from the runtime. The full animation cycle must span `state.timeline.durationSeconds`; do not hard-code a separate local animation duration such as 3s or 8s inside the renderer.
245
+
246
+ Renderers may compute an initial duration default during app initialization or reset, but they must not watch `state.timeline.durationSeconds` and dispatch `timeline.setDuration` back to a computed local value. Once the user edits the timeline duration, that runtime value is the source of truth and renderer progress must map into it.
247
+
248
+ Use keyframes timeline for diamonds, editable rows, easing, or keyframe evaluation. In keyframes mode, Toolcraft infers capable controls; do not manually hide diamonds on controls that can be keyframed.
249
+
250
+ Keyframe state stores typed control values. `valueLabel` is display-only for the timeline UI; renderers and tests must never parse it as the source of truth. Custom renderers must read keyframed settings through `evaluateToolcraftTimelineValues`, `evaluateToolcraftTimelineValue`, `useToolcraftEvaluatedValues`, or `useToolcraftEvaluatedValue` instead of reading raw `state.values` for keyframed targets.
251
+
252
+ Playback-only timelines stay collapsed and must not show control diamonds or expanded keyframe rows.
253
+
254
+ When non-looping playback reaches the end, pressing Play again must restart from time 0. Do not require users to scrub back manually before replaying.
255
+
256
+ App-wide Play, Pause, Animate, and Restart controls do not belong in the right panel.
257
+
258
+ Right-panel animation controls may tune renderer parameters such as mode, intensity, speed, or stagger only after the animation intent is declared. They must not replace top timeline transport.
259
+
260
+ Do not replace `TimelinePanel` with an app-level playback, transport, or timeline panel to avoid runtime performance issues. Keep the runtime panel design and fix the Toolcraft runtime clock/state path. Use custom timeline UI only for explicit `custom-reference-timeline` transfers with browser-backed reference timeline coverage.
261
+
262
+ ## Panel Actions
263
+
264
+ Use `panelActions` only for sticky footer product actions such as Generate, Apply, Export, Copy, or Download.
265
+
266
+ Use schema `settingsTransfer` for settings import/export. Do not add Import Settings or Export Settings to sticky footer `panelActions`; when enabled, the runtime inserts a first technical `Setup` settings-transfer section without a visible heading and imports/exports control values, canvas size, and timeline state.
267
+
268
+ Recalculate settings-transfer eligibility after adding, removing, or reorganizing controls, sections, timeline, or layers. The runtime threshold is 12 product controls, 5 product sections, or weighted score 18. If the threshold is reached, use `settingsTransfer: "auto"` / `true` or document a product-specific opt-out through `runtime.settingsTransfer` acceptance evidence.
269
+
270
+ When settings transfer and editable-output canvas sizing are both enabled, the first technical `Setup` runtime section is mandatory and contains `Export Settings`, `Import Settings`, `Canvas width`, and `Canvas height` in that order. Do not split these into separate app-authored sections, rename the controls, or rebuild the block by hand.
271
+
272
+ If only `Export Settings` and `Import Settings` appear in that section, the schema is not using `editable-output` canvas sizing or already owns `canvas.size.width` / `canvas.size.height` controls. For product-output apps, prefer fixing the canvas sizing decision over adding hand-built size fields.
273
+
274
+ Reset belongs to the controls panel header reset button. Do not add a footer action with `label`, `value`, or `command` containing reset; acceptance treats that as a duplicate Reset.
275
+
276
+ Still-output product apps include one primary `Export PNG` action.
277
+
278
+ Animated product apps include `Export Video` as the primary action and `Export PNG` as the secondary action.
279
+
280
+ Animated product apps with `Export Video` include a separate `Video Export` section. That section must contain:
281
+
282
+ - `export.video.format` as a `select`, with default value `mp4` and baseline options `mp4` and `webm`;
283
+ - `export.video.resolution` as a `select`, with default value `current` and options such as `current` and `4k`.
284
+
285
+ Place `Video Export` as the final authored controls section directly above sticky footer export buttons. `Format` and `Resolution` are one compact workflow pair: render them in a two-column inline row by default. Use stacked rows only when a label or selected value would clip, truncate, or lose internal padding, and record that fallback reason in the spec or worklog. Do not use `segmented` for this pair unless the product has a deliberately tiny fixed output menu and browser tests prove every cell keeps padding.
286
+
287
+ Do not put video export format/resolution controls inside effect, renderer, animation, or output-background sections. `MOV` and `ProRes` are not baseline browser outputs; use them only with an explicit encoder/transcoder and dedicated acceptance plus performance coverage.
288
+
289
+ Add `Copy PNG` only when clipboard output is part of the product. Copy never replaces export. If two footer actions are needed, secondary/outline goes left and primary goes right. Footer actions must be one compact horizontal group, not stacked full-width rows. If an odd number of actions leaves one action alone in the final row, that final action spans the full row.
290
+
291
+ Async footer actions return the real Promise from `ToolcraftApp onPanelAction`. Export, download, copy, generate, and apply must not run as fire-and-forget work; the runtime uses the returned Promise to show the sticky footer top accent indicator only while the operation is pending. Use `reportProgress(0..1)` from `onPanelAction` for determinate progress. Video export reports frame-based render/encode progress, and PNG export reports phase progress when render/blob/handoff are asynchronous.
292
+
293
+ Do not place product action buttons on the canvas or in the renderer.
294
+
295
+ ## Canvas Handles
296
+
297
+ Use product editing handles only when direct manipulation is better than panel-only editing: gradient stops, focus points, light vectors, crop bounds, mask points, transforms, bezier anchors, or perspective corners.
298
+
299
+ Handles are visual overlays, not app UI. They must be textless, tokenized, bound to runtime state, and excluded from export/copy output.
@@ -0,0 +1,71 @@
1
+ # Custom Controls
2
+
3
+ Use a custom control only when no built-in Toolcraft control represents the product interaction.
4
+
5
+ Built-ins come first: `slider`, `rangeSlider`, `select`, `segmented`, `switch`, `checkbox`, `color`, `colorOpacity`, `vector`, `gradient`, `curves`, `fontPicker`, `imagePicker`, `fileDrop`, `text`, `code`, `rangeInput`, `palette`, `actions`, and `panelActions`.
6
+
7
+ Register custom renderers through `ToolcraftApp controlRenderers`.
8
+
9
+ Do not use `controlRenderers` to recreate a built-in control. If the product needs a slider, select, segmented mode picker, color input, gradient editor, font picker, upload, textarea, local action group, or footer action, declare the matching schema control instead of rendering the component manually.
10
+
11
+ Do not edit `ControlsPanel`, copied `src/toolcraft`, or Toolcraft internals inside a generated app.
12
+
13
+ Import custom renderer types from `@/toolcraft/runtime/react`.
14
+
15
+ ## Required Schema
16
+
17
+ Custom control schemas still need:
18
+
19
+ - `type`;
20
+ - `target`;
21
+ - `defaultValue`;
22
+ - `label`;
23
+ - `orderRole`;
24
+ - acceptance coverage;
25
+ - `customControlCoverage`;
26
+ - `builtInFitCheck`;
27
+ - browser coverage;
28
+ - performance coverage when they can trigger product work.
29
+
30
+ `builtInFitCheck` is required for every custom control acceptance row:
31
+
32
+ ```ts
33
+ builtInFitCheck: {
34
+ checkedBuiltIns: ["fileDrop", "imagePicker", "select"],
35
+ closestBuiltIn: "fileDrop",
36
+ whyInsufficient:
37
+ "FileDrop imports source files, but it does not provide ordering, preview, remove, and density mapping in one runtime value.",
38
+ productObservable:
39
+ "Ordering uploaded glyphs changes the rendered glyph ramp output.",
40
+ }
41
+ ```
42
+
43
+ `checkedBuiltIns` must name real Toolcraft built-in controls. `closestBuiltIn` must be one of those checked controls or `"none"` when no built-in is meaningfully close. `whyInsufficient` explains the missing interaction. `productObservable` names the output or side effect that proves the custom control is necessary.
44
+
45
+ ## State Rules
46
+
47
+ Custom renderers must write through the provided `setValue(nextValue, meta)` callback or existing runtime commands.
48
+
49
+ Local-only custom control state is invalid unless it is transient draft, hover, focus, or drag state. Final product state belongs to the Toolcraft runtime.
50
+
51
+ ## Keyframes
52
+
53
+ If a custom value is keyframe-capable, the renderer must work with runtime keyframes instead of local animation state. Store typed values in keyframes through runtime commands and consume `useToolcraftEvaluatedValues`, `useToolcraftEvaluatedValue`, `evaluateToolcraftTimelineValues`, or `evaluateToolcraftTimelineValue`.
54
+
55
+ ## Visual Rules
56
+
57
+ Custom controls should use Toolcraft tokens, spacing, focus states, disabled opacity, and interaction patterns. A custom control should look like it belongs in the controls panel.
58
+
59
+ Custom controls must render the minimum UI needed to understand the value, context, and available actions. Do not add decorative metadata or text that repeats the section title, control label, or obvious item state.
60
+
61
+ Every visible custom-control element must justify its space by enabling selection, ordering, preview, removal, upload, editing, or a product-affecting status. If text is only nice-to-have, remove it.
62
+
63
+ Use Toolcraft primitives for all custom-control chrome. Do not hand-style basic buttons, inputs, selects, sliders, scroll areas, or focus states.
64
+
65
+ Custom controls may use primitives for app-specific chrome, but they must not duplicate toolbar, timeline, layers, canvas, panel, or built-in control mechanics.
66
+
67
+ Choose element sizes from interaction need, not from how much content you want to fit. Glyphs, swatches, chips, and thumbnails can be compact, but destructive, reorder, upload, and primary actions must keep comfortable kit button or icon-button sizes.
68
+
69
+ When a custom list item needs context, prefer concise semantic labels such as `Darkest`, `Mid tone`, or `Lightest`. Omit file names, long captions, and duplicate helper text unless they are required to distinguish items.
70
+
71
+ If a custom control is really direct manipulation of the product output, prefer a product editing handle in `canvasContent` plus a schema-backed target.