@pixel-point/toolcraft 0.0.8 → 0.0.11

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 (88) hide show
  1. package/README.md +42 -9
  2. package/package.json +1 -1
  3. package/src/generate.mjs +42 -5
  4. package/src/generate.test.mjs +40 -0
  5. package/src/package-json.mjs +15 -0
  6. package/src/package-json.test.mjs +14 -1
  7. package/templates/runtime/contracts/component-contracts.test.ts +251 -47
  8. package/templates/runtime/contracts/component-contracts.ts +130 -57
  9. package/templates/runtime/contracts/decision-contracts.test.ts +38 -1
  10. package/templates/runtime/contracts/decision-contracts.ts +22 -9
  11. package/templates/runtime/export/export.test.ts +63 -0
  12. package/templates/runtime/export/export.ts +55 -0
  13. package/templates/runtime/index.ts +1 -0
  14. package/templates/runtime/react/canvas-shell.test.tsx +58 -4
  15. package/templates/runtime/react/canvas-shell.tsx +31 -9
  16. package/templates/runtime/react/controls-panel.test.tsx +916 -108
  17. package/templates/runtime/react/controls-panel.tsx +136 -30
  18. package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
  19. package/templates/runtime/react/settings-transfer.test.ts +4 -0
  20. package/templates/runtime/react/settings-transfer.ts +6 -1
  21. package/templates/runtime/react/timeline-panel.test.tsx +14 -0
  22. package/templates/runtime/react/timeline-panel.tsx +44 -7
  23. package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
  24. package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
  25. package/templates/runtime/react/toolcraft-app.tsx +56 -37
  26. package/templates/runtime/schema/define-toolcraft.test.ts +266 -170
  27. package/templates/runtime/schema/define-toolcraft.ts +140 -246
  28. package/templates/runtime/schema/runtime-targets.ts +21 -0
  29. package/templates/runtime/schema/types.ts +44 -0
  30. package/templates/runtime/state/create-template-state.test.ts +156 -0
  31. package/templates/runtime/state/create-template-state.ts +38 -8
  32. package/templates/runtime/state/media-defaults.ts +105 -0
  33. package/templates/runtime/state/persistence.test.ts +58 -0
  34. package/templates/runtime/state/persistence.ts +105 -1
  35. package/templates/runtime/state/reducer.test.ts +280 -4
  36. package/templates/runtime/state/reducer.ts +195 -9
  37. package/templates/runtime/state/timeline-loop.test.ts +71 -0
  38. package/templates/runtime/state/timeline-loop.ts +35 -0
  39. package/templates/runtime/state/types.ts +27 -0
  40. package/templates/runtime/testing/performance.test.ts +810 -21
  41. package/templates/runtime/testing/performance.ts +823 -53
  42. package/templates/starter/AGENTS.md +24 -18
  43. package/templates/starter/docs/toolcraft/README.md +8 -4
  44. package/templates/starter/docs/toolcraft/acceptance-testing.md +43 -10
  45. package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
  46. package/templates/starter/docs/toolcraft/assembly-workflow.md +55 -20
  47. package/templates/starter/docs/toolcraft/component-rules.md +75 -42
  48. package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
  49. package/templates/starter/docs/toolcraft/performance.md +37 -11
  50. package/templates/starter/docs/toolcraft/schema-reference.md +171 -41
  51. package/templates/starter/docs/toolcraft/workflow.md +5 -2
  52. package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
  53. package/templates/starter/e2e/app-performance.spec.ts +55 -2
  54. package/templates/starter/e2e/performance-helpers.ts +45 -0
  55. package/templates/starter/gitignore +1 -0
  56. package/templates/starter/index.html +1 -0
  57. package/templates/starter/package.json +3 -0
  58. package/templates/starter/playwright.config.ts +1 -1
  59. package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
  60. package/templates/starter/scripts/run-vite-on-free-port.mjs +114 -13
  61. package/templates/starter/scripts/toolcraft-port.mjs +280 -0
  62. package/templates/starter/scripts/toolcraft-port.test.mjs +207 -1
  63. package/templates/starter/src/app/starter-acceptance.test.ts +3412 -479
  64. package/templates/starter/src/app/starter-acceptance.ts +1453 -97
  65. package/templates/starter/src/app/starter-performance.test.ts +111 -7
  66. package/templates/starter/src/app/starter-performance.ts +5 -0
  67. package/templates/starter/src/app/starter-schema.test.ts +32 -7
  68. package/templates/starter/src/app/starter-schema.ts +6 -2
  69. package/templates/starter/vite.config.ts +58 -2
  70. package/templates/ui/components/control-layout/index.tsx +8 -3
  71. package/templates/ui/components/controls/actions/actions-control.tsx +56 -5
  72. package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
  73. package/templates/ui/components/controls/color/index.ts +4 -1
  74. package/templates/ui/components/controls/color/palette-control.tsx +34 -4
  75. package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
  76. package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
  77. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
  78. package/templates/ui/components/controls/file-drop/index.ts +6 -1
  79. package/templates/ui/components/controls/index.ts +4 -0
  80. package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
  81. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
  82. package/templates/ui/components/controls/select/select-control.tsx +8 -25
  83. package/templates/ui/components/controls/slider/slider-value.ts +0 -1
  84. package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
  85. package/templates/ui/components/controls/vector/index.ts +1 -0
  86. package/templates/ui/components/controls/vector/vector-control.tsx +109 -12
  87. package/templates/ui/components/panel/panel-actions.tsx +1 -1
  88. package/templates/ui/components/panel/panel-section.tsx +29 -5
@@ -39,20 +39,26 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
39
39
  "Only FontPicker may pair its internal letter-spacing and line-height sliders.",
40
40
  ],
41
41
  requiredAcceptance: [
42
- "Prove changing the slider changes product output or the intended runtime side effect.",
42
+ "Prove dragging the slider changes product output or the intended runtime side effect while the drag is in progress, not only after pointer release, blur, Apply, or a final commit.",
43
43
  "For discrete sliders, prove the discrete variant renders markers and dragging remains smooth.",
44
44
  ],
45
45
  }),
46
46
  aiUsageRules: [
47
+ "Sliders are live canvas controls: dragging must update runtime state and product output in real time by default.",
48
+ "Do not implement slider values as deferred local drafts, Apply-only updates, pointer-up-only commits, or renderer changes that appear only after the user asks again.",
49
+ "Slider performance coverage must use a real control-drag scenario; control-change coverage is not enough to prove live canvas feedback or drag smoothness.",
50
+ "If a live slider causes jank, optimize the renderer path first: update uniforms or stable buffers, cache expensive inputs, coalesce preview work to requestAnimationFrame, cancel stale async renders, move heavy work off React, or switch renderer strategy.",
51
+ "Only in an extreme documented performance ceiling may a slider use a degraded live preview or delayed heavy refinement; the user must still see immediate canvas feedback while dragging and the worklog must record the measured reason.",
47
52
  "Slider step means numeric snapping only; it does not make the slider visually discrete by itself.",
48
53
  "Classify every stepped slider as stepped continuous or visual discrete before writing the schema.",
49
54
  'Small semantic integer domains such as rows, cols, gaps, jitter, counts, levels, bands, passes, points, tiles, and segments must use variant: "discrete".',
50
55
  'Finite animation step domains such as flip depth, character count, glyph steps, and frame steps must use variant: "discrete" when the marker count stays within the Toolcraft visual budget.',
51
56
  "Large or precision stepped ranges such as speed, FPS, rate, duration, density, size, and intensity stay visually continuous even when they declare step.",
52
- "Use slider unit only for measurement or scale suffixes such as %, px, °, x, s, ms, fps, rows/cols, or similar domain units.",
57
+ "Use slider unit only for real measurement suffixes such as %, px, °, s, ms, fps, rows/cols, or similar domain units.",
53
58
  "Do not use unit for repeated entity nouns already named by the section or label, such as Letters + letters, Shape Density / Count + shapes, Words + words, Symbols + symbols, Items + items, Particles + particles, or Layers + layers.",
59
+ 'Do not use unit: "x"; scale, multiplier, intensity, opacity, strength, depth, and shader amount sliders display plain numbers unless a real measurement unit applies.',
54
60
  "When the value needs an entity noun to make sense, improve the label or section title instead of appending that noun as the value unit.",
55
- "Compact symbol/CSS units render tight, such as 70%, 24px, 1.2x, and 8s; word units render with a space, such as 5 cols, when they are truly needed.",
61
+ "Compact symbol/CSS units render tight, such as 70%, 24px, and 8s; word units render with a space, such as 5 cols, when they are truly needed.",
56
62
  "Slider valueLabel is editable only when it contains a numeric value; textual state labels such as Normal are display-only and must not expose hover or click editing affordances.",
57
63
  "Schema sliders render stacked at full width; do not put sliders in two-column inline layout groups.",
58
64
  "The fontPicker component is the only built-in exception with two internal footer sliders for letter spacing and line height.",
@@ -60,9 +66,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
60
66
  'Specs, plans, and app-schema tests must assert explicit discrete sliders render as variant: "discrete" with markers derived from min, max, and step.',
61
67
  'Browser verification can inspect [data-slot="slider"][data-variant="discrete"] plus slider markers to prove the Toolcraft component variant rendered.',
62
68
  "Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
63
- "Use schema disabled: true for unavailable sliders; do not recreate a disabled-looking slider with custom markup.",
64
- "Use disabledWhen for sliders that are only meaningful in some mode values, such as Fill level and Islands being disabled when Fill mode is Full.",
65
- "Do not leave a mode-dependent slider active while making the renderer ignore it; the UI must expose the unavailable state.",
69
+ "Use visibleWhen for sliders that are meaningful only in some mode/type/source/include/count states; inactive branches should disappear so the panel shows only controls usable in the current state.",
70
+ "Do not use schema disabled: true or disabledWhen for product sliders; product panels should show only controls usable in the current state. Use visibleWhen for unavailable product states instead of rendering disabled controls.",
71
+ "Do not leave an inactive conditional slider visible while making the renderer ignore it; hide it with visibleWhen.",
66
72
  ],
67
73
  },
68
74
  rangeSlider: {
@@ -90,16 +96,21 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
90
96
  "Default lower and upper values must be different.",
91
97
  ],
92
98
  requiredAcceptance: [
93
- "Prove rangeSlider.lower and rangeSlider.upper both affect product output.",
99
+ "Prove dragging rangeSlider.lower and rangeSlider.upper both affect product output while the drag is in progress, not only after pointer release, blur, Apply, or a final commit.",
94
100
  ],
95
101
  }),
96
102
  aiUsageRules: [
103
+ "Range sliders are live canvas controls: dragging either thumb must update runtime state and product output in real time by default.",
104
+ "Do not implement range slider values as deferred local drafts, Apply-only updates, pointer-up-only commits, or renderer changes that appear only after the user asks again.",
105
+ "Range slider performance coverage must use a real control-drag scenario; control-change coverage is not enough to prove live canvas feedback or drag smoothness.",
106
+ "If a live range slider causes jank, optimize the renderer path first: update uniforms or stable buffers, cache expensive inputs, coalesce preview work to requestAnimationFrame, cancel stale async renders, move heavy work off React, or switch renderer strategy.",
107
+ "Only in an extreme documented performance ceiling may a range slider use a degraded live preview or delayed heavy refinement; the user must still see immediate canvas feedback while dragging and the worklog must record the measured reason.",
97
108
  "Range slider step means numeric snapping only; it does not make the range slider visually discrete by itself.",
98
109
  "Classify every stepped range slider as stepped continuous or visual discrete before writing the schema.",
99
110
  'Small semantic integer domains such as rows, cols, gaps, jitter, counts, levels, bands, passes, points, tiles, and segments must use variant: "discrete".',
100
111
  'Finite animation step domains such as flip depth, character count, glyph steps, and frame steps must use variant: "discrete" when the marker count stays within the Toolcraft visual budget.',
101
112
  "Large or precision stepped ranges such as speed, FPS, rate, duration, density, size, and intensity stay visually continuous even when they declare step.",
102
- "Use rangeSlider unit only for measurement or scale suffixes; do not use it for repeated entity nouns already named by the section or label.",
113
+ "Use rangeSlider unit only for real measurement suffixes; do not use it for repeated entity nouns already named by the section or label, and do not use x as a unit.",
103
114
  "When a range label needs an entity noun to make sense, improve the label or section title instead of appending that noun as the value unit.",
104
115
  "Compact symbol/CSS units render tight, such as 20% – 80% or 12px – 48px; word units render with a space when truly needed.",
105
116
  "RangeSlider is always a full-width two-thumb control; never place it in an inline two-column layout group with another slider or range slider.",
@@ -107,9 +118,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
107
118
  "Manual range value editing accepts common separators such as slash, hyphen, spaces, and dashes; do not create custom parsers for RangeSlider labels.",
108
119
  'Specs, plans, and app-schema tests must assert explicit discrete range sliders render as variant: "discrete" with markers derived from min, max, and step.',
109
120
  "Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
110
- "Use schema disabled: true for unavailable range sliders; do not recreate a disabled-looking range slider with custom markup.",
111
- "Use disabledWhen for range sliders that are only meaningful in some mode values; keep the value so it returns when the mode becomes relevant again.",
112
- "Do not leave a mode-dependent range slider active while making the renderer ignore it; the UI must expose the unavailable state.",
121
+ "Use visibleWhen for range sliders that are meaningful only in some mode/type/source/include/count states; inactive branches should disappear so the panel shows only controls usable in the current state.",
122
+ "Do not use schema disabled: true or disabledWhen for product range sliders; product panels should show only controls usable in the current state. Use visibleWhen for unavailable product states instead of rendering disabled controls.",
123
+ "Do not leave an inactive conditional range slider visible while making the renderer ignore it; hide it with visibleWhen.",
113
124
  "Acceptance must prove both rangeSlider.lower and rangeSlider.upper change the product output; testing one handle is not enough.",
114
125
  ],
115
126
  },
@@ -125,7 +136,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
125
136
  ],
126
137
  useWhen: [
127
138
  "Use Select for finite choices with long labels, many options, or values that would not fit in Segmented.",
128
- "Use Select when a compact dropdown is more readable than a row of cells.",
139
+ "Use Select when a dropdown choice is more readable than a row of segmented cells.",
129
140
  ],
130
141
  doNotReplaceWith: [
131
142
  "Do not use Select to recreate FontPicker, Gradient, ImagePicker, FileDrop, Curves, Vector, or Palette value models.",
@@ -134,8 +145,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
134
145
  "Use Segmented for two to four short closely related options that fit without clipping.",
135
146
  ],
136
147
  layoutConstraints: [
137
- "Prefer compact two-column inline layout for related short Select pairs that tune one workflow or entity.",
138
- "Use vertical one-select-per-row layout only as a fallback when a label, selected value, or option text would clip, truncate, or lose internal padding in the compact row.",
148
+ "Standalone Select controls render stacked full-width with the label above the dropdown; do not use the compact side-label row with label left and dropdown right.",
149
+ "Prefer compact two-column inline layout only for related short Select pairs that tune one workflow or entity.",
150
+ "Use vertical one-select-per-row layout for single Select controls and as the fallback when a Select pair label, selected value, or option text would clip, truncate, or lose internal padding in the compact row.",
139
151
  "If a compact Select pair falls back to vertical layout, record the fit reason in the spec or worklog.",
140
152
  ],
141
153
  requiredAcceptance: [
@@ -165,6 +177,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
165
177
  ],
166
178
  layoutConstraints: [
167
179
  "Keep text segmented controls to at most four options and compact labels.",
180
+ "Segmented controls are full-width controls and must not be placed in two-column inline or half-width layout groups.",
168
181
  "Fallback to Select when cells collide, clip, or lose padding.",
169
182
  ],
170
183
  requiredAcceptance: [
@@ -174,6 +187,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
174
187
  }),
175
188
  aiUsageRules: [
176
189
  "Use Segmented only for compact mode choices where every cell keeps its internal padding.",
190
+ "Do not place Segmented beside Switch, Color, Select, or another control in an inline row; use Select when a finite choice must occupy a half-width column.",
177
191
  "If a segmented control is too wide, first shorten option labels; if the compact labels still exceed the width budget, use Select because it has the same selection mechanics without broken cells.",
178
192
  "Generated schemas should keep text segmented controls to at most four options, no option label longer than nine characters, and no more than twenty-four total option-label characters.",
179
193
  "Browser verification must treat collided, clipped, or paddingless segmented cells as a broken component and switch to shorter labels or Select.",
@@ -212,7 +226,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
212
226
  'Use labels such as "CRT", "Background", "Glow", or "Loop" instead of "Enable CRT" or "Disable background".',
213
227
  "Two adjacent Switch controls for the same product entity must share one inline row when every visible label fits without truncation. Keep paired labels to short one- or two-word names; the runtime auto-pairs safe adjacent switches by target entity, and generated schemas should stack switches only when any label would truncate.",
214
228
  "When the nearest section title already names the switch context, do not duplicate that title as the visible switch label. Use label false for a visual-only toggle and keep the meaning in target/description.",
215
- 'A Switch may share an inline row with one related parameter control when the visible switch label is short enough to fit. That row uses equal-width columns; never shrink the switch column to intrinsic width. In section-owned rows, use a short visible label such as "Include" instead of repeating the section title, such as "Include background" inside Background.',
229
+ 'A Switch may share an inline row with one related parameter control when the visible switch label is short enough to fit. That row uses equal-width columns and the same horizontal column gap as paired Select controls; never shrink the switch column to intrinsic width. The non-switch parameter uses label false in that row; if its label is needed, stack the controls instead. In section-owned rows, use a short visible switch label such as "Include" instead of repeating the section title, such as "Include background" inside Background.',
216
230
  ],
217
231
  },
218
232
  checkbox: {
@@ -247,7 +261,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
247
261
  'Use labels such as "Transparent background", "Guides", or "Loop" instead of "Enable transparent background".',
248
262
  "When the nearest section title already names the checkbox context, do not duplicate that title as the visible checkbox label. Use label false for a visual-only checkbox and keep the meaning in target/description.",
249
263
  "Two adjacent Checkbox controls for the same product entity must share one inline row when every visible label fits without truncation. Keep paired labels to short one- or two-word names; the runtime auto-pairs safe adjacent checkboxes by target entity, and generated schemas should stack checkboxes only when any label would truncate.",
250
- "A Checkbox may share an inline row with one related parameter control when the visible checkbox label is short enough to fit. That row uses equal-width columns; never shrink the checkbox column to intrinsic width. Hide the checkbox label when the section title provides the visible context.",
264
+ "A Checkbox may share an inline row with one related parameter control when the visible checkbox label is short enough to fit. That row uses equal-width columns and the same horizontal column gap as paired Select controls; never shrink the checkbox column to intrinsic width. The non-checkbox parameter uses label false in that row; if its label is needed, stack the controls instead. Hide the checkbox label when the section title provides the visible context.",
251
265
  ],
252
266
  },
253
267
  colorOpacity: {
@@ -295,7 +309,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
295
309
  "single-line setting value",
296
310
  ],
297
311
  useWhen: [
298
- "Use TextInput for short names, titles, tokens, compact prompts, and small setting strings.",
312
+ "Use TextInput for short names, button labels, titles, captions, tokens, compact prompts, and small setting strings.",
299
313
  "Use commitMode content for real product content and commitMode setting for configuration-like values.",
300
314
  ],
301
315
  doNotReplaceWith: [
@@ -313,6 +327,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
313
327
  ],
314
328
  }),
315
329
  aiUsageRules: [
330
+ "TextInput owns short single-line product text: button labels, labels on the canvas, names, titles, captions, badges, short tokens, and compact prompts.",
316
331
  'TextInput commitMode defaults to "content": text content, prompts, names, tokens, titles, and instructions apply while typing.',
317
332
  '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.',
318
333
  "Canvas width and Canvas height are runtime editable-size fields and always commit on blur or Enter like editor size fields.",
@@ -375,7 +390,10 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
375
390
  layoutConstraints: [
376
391
  "Keep local actions close to the entity they affect.",
377
392
  "Keep action labels short and scoped by the section title; prefer Randomize, Clear, Sort, Normalize, Duplicate, or Reset when the section already names the target.",
378
- "When an Actions button group wraps below its label, the wrapped button row starts at the left edge of the control content instead of centering or staying right-aligned.",
393
+ "Do not set an Actions control label to the exact same visible text as its only button; use a short one- or two-word context label such as Ink wash, Palette action, or Current layer while the button keeps the command verb.",
394
+ "Actions never use a side-label layout. If a visible label exists, it sits above the buttons.",
395
+ "Actions buttons render as a two-column grid. One visible button occupies the left half of the section; two buttons occupy one half each; more than two buttons wrap into additional 50% cells.",
396
+ "Do not center or right-align a partial final Actions row; an odd trailing button stays in the left 50% cell.",
379
397
  ],
380
398
  requiredAcceptance: [
381
399
  "Prove each action dispatches the intended command or product side effect for the nearby entity only.",
@@ -389,7 +407,10 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
389
407
  "Do not use Actions for final product delivery actions; use sticky panelActions for Export, Copy, Download, Generate, or Apply.",
390
408
  "Do not use Actions for global reset; the controls panel header owns global reset.",
391
409
  "Do not use Actions for animation transport; Play, Pause, Resume, Restart, and Scrub belong to the top timeline when timeline behavior exists.",
392
- "When an Actions button group no longer fits beside its label and wraps to a second row, align the wrapped button row to the left.",
410
+ "For a single visible Actions button, the control label and button label must not be identical; make the control label a concise context and the button label the command.",
411
+ "Render the Actions label above the buttons; do not put the label on the left with buttons on the right.",
412
+ "Render Actions buttons in 50% cells: one button uses the left half, two buttons fill one row, and larger groups continue in two columns.",
413
+ "Do not stretch an odd trailing Actions button full-width.",
393
414
  'For local reset-like actions, use product-specific values such as "reset-current-layer" or "reset-palette" and handle them through ToolcraftApp onPanelAction; do not use a bare "reset" value unless the action intentionally runs controls.reset.',
394
415
  "Acceptance and browser tests must click each Actions button and prove the product output or runtime state for the nearby entity changed.",
395
416
  ],
@@ -482,6 +503,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
482
503
  "The sticky footer top accent indicator is determinate when reportProgress receives 0..1 values and falls back to pending state only when progress is unavailable.",
483
504
  "defineToolcraft hoists panelActions into the controls panel sticky footer automatically.",
484
505
  "Product-output apps must always include export in panelActions.",
506
+ 'Export-labeled panelActions use icon "upload-simple", matching the Setup "Export Settings" action; do not use "download", "download-simple", or "export" icons for Export PNG or Export Video.',
485
507
  "Static or still-output apps include Export PNG as the primary footer action.",
486
508
  'Every app with Export PNG must expose a separate "Image Export" controls section.',
487
509
  'The Image Export section must include "export.image.format" as a Select control with PNG and JPG choices, defaulting to "png".',
@@ -489,6 +511,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
489
511
  "Image Export format and resolution render as one compact two-column inline Select pair, matching the Video Export settings structure.",
490
512
  "Image Export resolution controls the actual exported image long edge: 2K = 2048px, 4K = 4096px, 8K = 8192px. Pass the selected runtime value to createToolcraftPngExportCanvas resolution and prove decoded image width/height in browser acceptance.",
491
513
  "Animated apps include Export Video as the primary footer action and Export PNG as a secondary footer action.",
514
+ "Any app with Export Video must enable the top Toolcraft timeline; video duration, loop, and rendered timestamps come from runtime timeline state.",
492
515
  'Animated apps with Export Video must expose a separate "Video Export" controls section.',
493
516
  'Animated apps with both Export PNG and Export Video must expose both "Image Export" and "Video Export"; Image Export sits immediately before Video Export.',
494
517
  'The Video Export section must include format and resolution controls such as targets "export.video.format" and "export.video.resolution".',
@@ -499,6 +522,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
499
522
  "Video Export format and resolution are a compact semantic pair and should use a two-column inline layout by default; use stacked rows only when labels or selected values do not fit without clipping.",
500
523
  'Baseline browser video formats are "mp4" and "webm"; MOV or ProRes require an explicit custom encoder/transcoder and dedicated acceptance plus performance coverage.',
501
524
  "Video export code must choose the actual MIME/container through MediaRecorder.isTypeSupported or an equivalent encoder capability check, then fall back safely.",
525
+ "Video export must use getToolcraftVideoExportSize for current and 4K dimensions. Current video export uses the current canvas/output size with even encoder-safe rounding; 4K video fits inside an encoder-safe 3840x2160 box, preserves canvas aspect ratio, and uses even pixel dimensions. Do not hand-roll 4096px long-edge video sizing.",
526
+ "Video export must set recording canvas dimensions before captureStream, MediaRecorder, VideoEncoder, or equivalent encoder setup, and must reject recorder/encoder errors instead of returning corrupt blobs.",
502
527
  "Offline video export duration must be encoded from runtime timeline timestamps. Do not rely on canvas.captureStream plus MediaRecorder wall-clock recording time as the only duration mechanism for rendered-frame export.",
503
528
  'Video resolution must control exported dimensions. Use "current" output size by default; "4K" is an export resolution target, not a hardcoded 3840x2160 canvas lock.',
504
529
  "Video export browser coverage must load the exported blob metadata and prove video.duration matches the edited runtime timeline duration; blobSize/blobType checks alone are not enough.",
@@ -506,7 +531,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
506
531
  'Product-output apps must expose a dedicated "Background" section directly before the first export settings section. With PNG export that first section is Image Export; with video-only export it is Video Export.',
507
532
  "Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas for PNG alpha and call shouldIncludeToolcraftPreviewBackground(state) for live preview product background.",
508
533
  "PNG export must use createToolcraftPngExportCanvas so background transparency and selected image dimensions or retina fallback are applied consistently; turning Include off makes preview product background and PNG alpha transparent without hiding the Toolcraft canvas backing or video background.",
509
- "Video export must keep product background and use getToolcraftRetinaExportSize for retina dimensions.",
534
+ "Video export must keep product background and use getToolcraftVideoExportSize for current/4K output dimensions.",
510
535
  "Copy PNG can be a secondary action when clipboard output is useful, but copy does not replace export.",
511
536
  "Add Copy PNG as a secondary action only when the prompt/reference includes clipboard output or the product clearly benefits from paste/share workflows.",
512
537
  "Footer actions must be one compact horizontal group; do not split them into stacked full-width sections.",
@@ -542,10 +567,11 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
542
567
  ],
543
568
  layoutConstraints: [
544
569
  "Palette is a standalone compound control.",
545
- "Palette renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
570
+ "Palette renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
546
571
  ],
547
572
  requiredAcceptance: [
548
573
  "Prove palette.family and palette.shade both affect product output.",
574
+ "Prove Palette family and shade selections update runtime state immediately, before delayed persistence/commit settles.",
549
575
  ],
550
576
  }),
551
577
  aiUsageRules: [
@@ -555,6 +581,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
555
581
  "Do not use Palette when opacity belongs to the same color entity; use ColorOpacity instead.",
556
582
  "Do not use Palette for gradients or color transitions; use Gradient instead.",
557
583
  "Do not split typography color out to Palette when the text styling belongs to FontPicker.",
584
+ "Palette is a live control like Color and Slider: family and shade changes must update runtime state immediately so the next canvas interaction uses the selected token without waiting for delayed commit or persistence timers.",
558
585
  "Palette is a compound control; acceptance must prove palette.family and palette.shade both affect the product output.",
559
586
  "Do not accept a palette test that only changes a swatch preview without proving the renderer/export consumes the selected family and shade.",
560
587
  ],
@@ -597,6 +624,11 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
597
624
  'Use variant: "chromaOffset" for RGB/chromatic offset vectors where the X/Y movement controls channel separation.',
598
625
  'Use variant: "toneBias" for split-tone, duotone, or color-grading vectors where both axes describe tone or hue bias.',
599
626
  'Use the default vector variant for spatial values such as position, offset, direction, focus, anchor, and light direction.',
627
+ 'Default spatial vector pads use coordinateMode: "screen": dragging left/up makes vector.x and vector.y smaller so canvas objects move left/up without renderer-side Y inversion.',
628
+ "Vector pad value labels render compact rounded coordinates. Do not expose raw floating-point strings such as -0.07070312499999998 in the controls panel.",
629
+ "Double-clicking a vector pad resets both axes to the control default through the normal runtime value update, matching section header reset semantics; if no default is defined, the fallback is 0,0. Do not add a separate custom reset UI for this basic pad reset behavior.",
630
+ "Holding Shift while dragging a vector pad locks movement to the dominant axis and must not select text or page content; do not build a custom pad just to support axis-constrained movement.",
631
+ 'Use coordinateMode: "cartesian" only when the product intentionally exposes mathematical Y-up coordinates instead of canvas/screen movement.',
600
632
  "Do not add custom vector sizing props in generated schemas; choose the number, variant, and section grouping from product need and let the runtime size the pads.",
601
633
  "Vector is a compound control; acceptance must prove vector.x and vector.y both affect the product output.",
602
634
  ],
@@ -645,7 +677,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
645
677
  "Never use generic Color or Colors as a generated section title. If no meaningful color role exists and the colors are just basic colors, use a neutral section title such as Appearance instead of omitting the title.",
646
678
  "Do not split a grouped object section into a separate generated Color section; if the color role is unclear, ask the user before implementation.",
647
679
  "When one short numeric/text field and one Color field configure the same entity, keep them in one two-column inline layout group.",
648
- 'Mixed inline rows require visible labels on both controls. The required Background row is the section-title-owned exception: use the Switch label "Include" and set the background Color control label to false. Palette variation color banks are the other exception when the group/section label already names the color bank.',
680
+ 'Mixed inline rows usually require visible labels on both controls. Toggle-plus-parameter rows are the section-owned exception: keep the Switch/Checkbox label visible and set the non-toggle parameter label to false; if the parameter label is needed, stack the controls instead. All 50/50 inline rows use the same horizontal column gap as paired Select controls. The required Background row uses the Switch label "Include" and sets the background Color control label to false. Palette variation color banks are the other exception when the group/section label already names the color bank.',
649
681
  "Plain Color popovers must not show opacity controls. If opacity is editable, use ColorOpacity instead.",
650
682
  "Product-output apps always expose renderer-owned output background color as a schema color target such as appearance.background or scene.background.",
651
683
  'Pair renderer-owned output background color with export.includeBackground in one Background section directly before export settings. Use an equal-width inline row with the export.includeBackground Switch labeled "Include" on the left and the background Color parameter with label false on the right; each control occupies one half of the row.',
@@ -681,7 +713,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
681
713
  ],
682
714
  layoutConstraints: [
683
715
  "Gradient is a full standalone compound control.",
684
- "Gradient renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
716
+ "Gradient renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
685
717
  ],
686
718
  requiredAcceptance: [
687
719
  "Prove gradientType, angle, stop position, stop color, and stop opacity affect product output or export output.",
@@ -722,7 +754,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
722
754
  ],
723
755
  layoutConstraints: [
724
756
  "FontPicker owns its popup and internal footer controls.",
725
- "FontPicker renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
757
+ "FontPicker renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
726
758
  ],
727
759
  requiredAcceptance: [
728
760
  "Prove fontId, fontWeight, fontSize, letterSpacing, lineHeight, textCase, color, and opacity affect actual product text output.",
@@ -766,7 +798,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
766
798
  layoutConstraints: [
767
799
  "Use variant single for one standalone curve and RGB variant only for color-correction or channel-specific curves.",
768
800
  "Single Curves is one labeled control and does not render internal dividers, even inside mixed sections.",
769
- "RGB Curves is a compound channel control and renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each rendered divider and the control content; if it is the first control in that section, only the bottom internal divider renders and the top internal padding is removed, and if it is the only control in the section, only the parent section dividers render.",
801
+ "RGB Curves is a compound channel control and renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each rendered divider and the control content; if it is the first control in that section, only the bottom internal divider renders and the top internal padding is removed; if it is the last control in that section, only the top internal divider renders and the bottom internal padding is removed; and if it is the only control in the section, only the parent section dividers render.",
770
802
  ],
771
803
  requiredAcceptance: [
772
804
  "Prove curves.points affect product output; RGB curves also prove activeChannel affects output.",
@@ -836,7 +868,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
836
868
  ],
837
869
  layoutConstraints: [
838
870
  "ChannelMixer is a standalone compound control.",
839
- "ChannelMixer renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders, and if it is the only control in the section, only the parent section dividers render.",
871
+ "ChannelMixer renders content-width internal dividers only when it shares a panel section with sibling controls, with 18px vertical spacing between each divider and the control content; if it is the first control in that section, only the bottom internal divider renders; if it is the last control in that section, only the top internal divider renders; and if it is the only control in the section, only the parent section dividers render.",
840
872
  ],
841
873
  requiredAcceptance: [
842
874
  "Prove channelMixer.activeChannel and channelMixer.values both affect product output.",
@@ -864,6 +896,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
864
896
  ],
865
897
  doNotReplaceWith: [
866
898
  "Do not place upload UI on the canvas.",
899
+ "Do not draw a custom canvas empty-state design, CTA, fake sample output, decorative placeholder, or agent-invented preset source while waiting for uploaded content.",
867
900
  "Do not build custom file buttons for source media import.",
868
901
  ],
869
902
  acceptableAlternatives: [
@@ -871,27 +904,38 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
871
904
  ],
872
905
  layoutConstraints: [
873
906
  "FileDrop lives in the controls panel; single-layer apps use its preview and clear behavior.",
907
+ "When fileDrop has assetKind: image and one image is present, the runtime renders image transform actions through the built-in actions-control directly below the uploader: 90° Right, Flip horizontal, and Flip vertical.",
908
+ "FileDrop image previews keep a stable preview frame when rotated or flipped. Rotated preview bitmaps use contain semantics inside that frame so the controls-panel preview never crops the image; canvas/product renderers may still use cover/crop when the uploaded image is source material.",
874
909
  "When fileDrop has multiple: true and more than one image is present, the runtime renders a sortable four-column thumbnail grid with the add-more tile last.",
910
+ "When fileDrop has multiple: true and more than one image is present, image transform actions render only after the user selects a thumbnail, and they target only that selected image.",
875
911
  "When fileDrop has assetKind: file, the runtime renders a sortable file list with a paperclip icon, filename, remove button, and row separators using --border/5.",
876
912
  ],
877
913
  requiredAcceptance: [
878
- "Prove file import changes media state and product output; prove clear, reorder, and section/global reset update source material.",
914
+ "Prove file import changes media state and product output; prove clear, reorder, rotate, flip, and section/global reset update source material.",
879
915
  ],
880
916
  }),
881
917
  aiUsageRules: [
882
918
  "Use fileDrop for source material uploads in the controls panel, not on the canvas.",
883
919
  'Use fileDrop with assetKind: "image" for image-only source media and assetKind: "file" for arbitrary uploaded files.',
920
+ "If an app ships with predefined source files or background images, declare them as schema media.defaultAssets with sourceTarget matching the fileDrop control. They must render as ordinary attached files in fileDrop, not as hidden renderer constants or canvas placeholder artwork.",
921
+ "Predefined media files are default runtime state: users can remove them to get an empty source/canvas state, persistence may keep that removal with include: [\"media\"], and global or section Reset restores the default attached files.",
922
+ "When uploaded/imported content is part of the source-material flow, the canvas must not show agent-invented artwork, CTA text, fake sample output, decorative placeholders, or preset source designs before real content exists; keep the canvas neutral/runtime-backed and put upload affordance in fileDrop.",
923
+ "Do not add procedural Source Preset modes only to avoid an empty canvas. A default procedural or reference source is allowed only when the prompt/reference explicitly defines it and the worklog records that evidence.",
884
924
  "In single-layer apps, the runtime shows the uploaded image as the fileDrop preview and provides the clear action.",
925
+ "In image mode, the runtime owns image transform actions: 90° Right, Flip horizontal, and Flip vertical. These actions render through the built-in actions-control in one three-column row with compact visible labels: 90°, Flip H, Flip V; keep a 6px vertical gap between the uploader and action row. Do not create a custom image action button grid. They update runtime mediaAssets transform metadata, and product preview/export must consume that metadata instead of keeping a separate transform state.",
926
+ "FileDrop panel previews are not product canvas rendering. Keep the preview frame height stable across image rotation/flip, and contain the rotated bitmap inside that frame so horizontal or vertical uploads are not cropped by the control.",
927
+ "When exactly one uploaded image is present, image transform actions are visible immediately. When multiple images are present, users select a thumbnail first; no transform actions render until a thumbnail is selected, and the action applies only to that selected image.",
885
928
  "In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
886
- "In single-layer apps, global Reset controls and section reset must remove uploaded fileDrop source media and return the fileDrop target to defaultValue.",
929
+ "In single-layer apps, global Reset controls and section reset must restore fileDrop source media to schema media.defaultAssets for that target; when no default asset exists, Reset removes uploaded media and returns the fileDrop target to defaultValue.",
887
930
  "Use fileDrop with multiple: true when the app needs several uploaded images as one source set; do not build a custom thumbnail uploader for this.",
888
931
  "When multiple uploaded images are present, the runtime appends media, shows a sortable four-column preview grid, puts the add-more tile last, and exposes per-image removal.",
889
932
  "Canvas drops route to the first visible matching fileDrop target by asset kind: image files prefer image uploaders, non-image files prefer file uploaders, and file uploaders accept images only when no image uploader matches.",
890
933
  "Dragging thumbnails reorders runtime mediaAssets; preview, export, and renderer mapping must consume that media order instead of maintaining a separate product-only order.",
934
+ "When uploaded images are used as canvas/background source material, draw them with cover/crop behavior: scale proportionally until the current canvas bounds are fully covered, keep the canvas size/settings unchanged, and crop overflow at the canvas bounds.",
891
935
  "Do not create custom upload buttons, file lists, or file sorting for generic source uploads when fileDrop can represent the source set.",
892
936
  "In multi-layer apps, deletion and visibility belong to the Layers panel; fileDrop remains an upload target.",
893
937
  ],
894
- commands: ["media.delete", "media.import", "media.reorder"],
938
+ commands: ["media.delete", "media.import", "media.reorder", "media.transform"],
895
939
  },
896
940
  imagePicker: {
897
941
  ...control("imagePicker", "ImagePicker", "standalone", "component-owned"),
@@ -961,8 +1005,10 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
961
1005
  }),
962
1006
  aiUsageRules: [
963
1007
  "CodeTextarea is the multiline text input for any potentially long value, not only source code.",
964
- "Use text for short single-line strings such as names, small numeric values, compact prompts, titles, and short tokens.",
965
- "Use code when the user may enter long prompts, multiline text, JSON, CSS, shader code, scripts, templates, or other long structured data.",
1008
+ "Do not use CodeTextarea for short single-line canvas text, button labels, names, titles, captions, badges, or short tokens; use TextInput.",
1009
+ "Use text for short single-line strings such as names, button labels, small numeric values, compact prompts, titles, captions, and short tokens.",
1010
+ "Use code only when the user may enter long prompts, multiline text, instructions, JSON, CSS, shader code, scripts, templates, or other long structured data.",
1011
+ "If CodeTextarea has a short single-line default value, the schema description must make the long, multiline, or structured-content reason explicit.",
966
1012
  "CodeTextarea is a content editor and applies values while typing; do not wait for blur, Enter, or Cmd/Ctrl+Enter to update runtime state.",
967
1013
  "CodeTextarea height is capped at 12 visible text lines; long content scrolls inside the textarea instead of making the controls panel taller.",
968
1014
  "Do not name a section Code unless the product value is actually code; use the product role such as Prompt, Instructions, Template, JSON, Shader, or CSS.",
@@ -1017,17 +1063,20 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1017
1063
  canvas: {
1018
1064
  aiUsageRules: [
1019
1065
  "Choose canvas.sizing.mode from product context instead of copying a universal 1024px artboard.",
1020
- "Use intrinsic-media for single-layer upload/generation apps so imported media natural size becomes canvas.size.",
1021
- "Use editable-output by default for generated, exportable, shader, poster, badge, wall, banner, thumbnail, and product-output apps where users should see or edit width and height.",
1066
+ "Do not let canvas.upload choose intrinsic-media by default. Upload without explicit sizing resolves to editable-output so source/background images do not own product output size.",
1067
+ "Use intrinsic-media only for true media-viewer or source-native apps where the natural uploaded/generated media size is the product output; record the reason and prove it with intrinsic-media-size acceptance.",
1068
+ "Use editable-output for generated, exportable, shader, poster, badge, wall, banner, thumbnail, procedural, reference-clone, and product-output apps so users always see Aspect ratio, Canvas width, and Canvas height.",
1069
+ "When an uploaded image is a background/source inside the product canvas, keep the current canvas.size, keep Setup/canvas controls visible, and render the image as cover/crop inside the current canvas bounds without letterbox or aspect distortion.",
1022
1070
  "When no explicit product size is provided, the runtime default canvas is 16:9 at 1920x1080; do not reset a new product-output app to 1:1 unless the product meaning requires it.",
1023
- "A user-provided base/default size is not a reason to remove size controls; model it as canvas.size plus editable-output unless the prompt or reference explicitly locks output dimensions.",
1024
- "Use fixed-output only when the product output size must not be user-editable, and prove that lock with canvasSizingCoverage fixed-output-size acceptance.",
1025
- "Resolved canvas.size exists for every canvas app, but visible Canvas width and Canvas height controls are mandatory only for editable-output sizing and do not depend on settingsTransfer.",
1071
+ "A user-provided, reference, fixed-format, or base/default size is not a reason to remove size controls; model it as canvas.size plus editable-output so the size is an initial value, not a hidden lock.",
1072
+ "Do not use fixed-output for generated product/output apps with export actions. Reserve fixed-output for non-product internal fixtures where width and height truly must never be user-editable, and prove that lock with canvasSizingCoverage fixed-output-size acceptance.",
1073
+ "A reference or previous app lacking a size editor, or defining a fixed-size baseline, is not a fixed-output reason for a generated product app; product-output clones still use editable-output.",
1074
+ "Resolved canvas.size exists for every canvas app, but visible Canvas width and Canvas height controls are mandatory only for editable-output sizing and live in the mandatory runtime Setup section.",
1026
1075
  "If canvas.size is provided without an explicit sizing mode, defineToolcraft treats it as editable-output and adds Canvas width and Canvas height controls.",
1027
- "The runtime Canvas width and Canvas height block uses the technical Setup section and renders without a visible section heading; do not add a separate Canvas section label above these fields.",
1076
+ "The runtime Canvas width and Canvas height block uses the mandatory headerless Setup controls block; do not add a separate Canvas section label above these fields.",
1028
1077
  "When the user manually edits Canvas width or Canvas height, the runtime keeps the typed dimension, keeps the other dimension unchanged, switches Aspect ratio to Custom, and shows the reduced current ratio in the custom ratio inputs.",
1029
1078
  "Aspect ratio presets are the only interaction that may resize both canvas dimensions from a preset; manual size inputs are exact output dimensions.",
1030
- "For non-vector raster, Canvas 2D, WebGL, and WebGPU previews, set canvas.renderScale: true so the runtime adds Resolution scale after canvas sizing. The scale changes backing pixels from 1x to 2x without changing visible canvas size, and adding/enabling it requires targeted browser evidence that the canvas stays responsive at the selected scale.",
1079
+ "For non-vector raster, Canvas 2D, WebGL, and WebGPU previews, set canvas.renderScale: true so the runtime adds Resolution scale after canvas sizing. The scale changes backing pixels from 1 to 2 without changing visible canvas size, and adding/enabling it requires targeted browser evidence that the canvas stays responsive at the selected scale.",
1031
1080
  "After enabling canvas.renderScale, verify that canvas preview stays responsive while dragging sliders and other high-frequency controls at the selected scale.",
1032
1081
  "Performance fixes for canvas.renderScale must preserve the selected visual quality; do not silently downsample, stretch a lower-resolution backing canvas, blur output, or clamp canvas.renderScale below the user's chosen value to pass budgets.",
1033
1082
  "Do not enable canvas.renderScale for DOM/SVG/vector-native previews; preserve vector fidelity through native vector rendering instead of raster supersampling.",
@@ -1043,6 +1092,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1043
1092
  "canvas.zoomReset",
1044
1093
  "media.delete",
1045
1094
  "media.import",
1095
+ "media.reorder",
1096
+ "media.transform",
1046
1097
  ],
1047
1098
  historyPolicy: "patch",
1048
1099
  id: "canvas",
@@ -1055,10 +1106,11 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1055
1106
  aiUsageRules: [
1056
1107
  "Do not write app state to localStorage directly.",
1057
1108
  "Use schema persistence policy for app state that should survive reload.",
1058
- "Persistence may include values, canvas, panels, timeline, and layers; history and media blobs are not persisted.",
1109
+ "Persistence may include values, canvas, panels, timeline, layers, and media; history is not persisted.",
1110
+ "Use persistence include: [\"media\"] only when runtime media state must survive reload, such as predefined attached files that users can delete, reorder, or transform. Do not use ad hoc storage for media state.",
1059
1111
  'Apps with visible runtime panels and localStorage persistence must include "panels" so dragged panel positions survive reload.',
1060
1112
  'Apps with localStorage persistence must include acceptance coverage for changing a user setting, reloading the browser page, and seeing the restored value or product output.',
1061
- "Settings import/export is a preset transfer feature for complex apps; it must not be used to hide or replace broken persistence reload behavior.",
1113
+ "Settings import/export is a mandatory runtime preset transfer feature; it must not be used to hide or replace broken persistence reload behavior.",
1062
1114
  "Do not store media blobs, files, or large generated images in localStorage.",
1063
1115
  ],
1064
1116
  capabilities: ["themePreference", "appStatePolicy"],
@@ -1072,12 +1124,13 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1072
1124
  },
1073
1125
  settingsTransfer: {
1074
1126
  aiUsageRules: [
1075
- 'Use schema settingsTransfer: "auto" for complex apps unless the prompt explicitly disables settings import/export.',
1076
- "After adding, removing, or reorganizing controls, sections, timeline, or layers, recalculate settings-transfer eligibility. The runtime threshold is 12 product controls, 5 product sections, or weighted score 18.",
1127
+ "Generated apps keep a controls panel so runtime Setup is visible from the first run; product controls are added after that mandatory runtime section.",
1128
+ "Do not gate Export Settings / Import Settings behind complexity thresholds, app size, or prompt wording.",
1077
1129
  "Do not hand-roll settings import/export through app routes, hidden file inputs, or panelActions.",
1078
- "Settings transfer appears as the first technical Setup controls-panel section when enabled and renders without a visible section heading; it imports and exports control values, canvas size, and timeline state.",
1079
- "A settings-transfer section with only Export Settings and Import Settings means canvas sizing is not editable-output or canvas size controls already exist elsewhere.",
1080
- "When settings transfer and editable-output canvas sizing are both enabled, the first technical Setup runtime section contains Export Settings, Import Settings, Aspect ratio, Canvas width, Canvas height, and optional Resolution scale in that order and renders without a visible section heading.",
1130
+ "Settings transfer appears in the first visible headerless Setup controls-panel block; it imports and exports control values, canvas size, and timeline state.",
1131
+ "App-authored sections must not declare runtime Setup targets such as runtime.settingsTransfer, canvas.aspectRatio, canvas.size.width, canvas.size.height, canvas.renderScale, or panels.timeline.extended; those controls never suppress the mandatory runtime Setup controls.",
1132
+ "When editable-output canvas sizing is enabled, the first visible Setup runtime section contains Export Settings, Import Settings, Aspect ratio, Canvas width, Canvas height, and optional Resolution scale in that order.",
1133
+ "When panels.timeline is enabled, Setup appends the Timeline switch as the last control; when panels.timeline is omitted, the Timeline switch must not appear.",
1081
1134
  "Keep sticky footer panelActions for product delivery actions only, such as Export PNG, Export Video, Copy, Generate, Apply, or Download.",
1082
1135
  ],
1083
1136
  capabilities: ["settings-import-export"],
@@ -1097,13 +1150,16 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1097
1150
  "Acceptance tests must fail when an entity is disconnected from runtime state, renderer output, export output, or command side effects.",
1098
1151
  "Do not accept typecheck, component existence, registered commands, runtime state mutation, renderer input objects, shader uniform presence, or signature strings as final proof.",
1099
1152
  "Use product-level observables such as rendered pixels, exported image bytes, canvas hash, clipboard payload, cleared media preview, selected layer result, changed viewport, or timeline-rendered frame.",
1153
+ "Any supplied video, GIF, screen-recording, contact-sheet, or extracted-frame reference requires a Video Reference Study before implementation: storyboard frames, frame-to-frame transition analysis, behavior decomposition, and acceptance mapping. Do not implement video references from a single screenshot or high-level summary.",
1100
1154
  "A generic canvas hash difference is not enough for workload or semantic controls; assert the intended direction of the effect.",
1101
1155
  "Component variants are accepted entities too; tests should fail if a non-default Toolcraft control variant falls back to the default variant or custom markup.",
1102
1156
  "Conditional entities require fixtures that make the condition observable.",
1103
- "Use visibleWhen for mode-, type-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
1157
+ "Use visibleWhen for mode-, type-, source-, include-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
1158
+ "When visibleWhen references a selector for the same product entity or selected branch, keep the selector and its dependent controls in one semantic section. A section that exists only because one selector option is active is not a separate product section just because the branch uses a standalone control.",
1104
1159
  "When a count/quantity control determines how many sibling controls are available, hide unavailable siblings with visibleWhen; do not render all possible controls while the renderer reads only the first N.",
1105
- "Use disabledWhen for controls that belong to the current entity but are temporarily unavailable for the selected mode; the disabled value must be preserved.",
1106
- "Do not leave inactive conditional controls visible and enabled while making the renderer ignore them.",
1160
+ "If a switch/select/segmented/imagePicker/checkbox chooses a branch for the same product entity, controls outside the current branch use visibleWhen, not disabledWhen.",
1161
+ "Do not use schema disabled: true or disabledWhen for generated product controls; the panel should show only controls usable in the current state. Runtime primitives may still have disabled styling internally, but app schemas should model product availability with visibleWhen.",
1162
+ "Do not leave inactive conditional controls visible while making the renderer ignore them.",
1107
1163
  "If an entity cannot be tested against a product-level observable, remove it from the app schema or ask whether it is required.",
1108
1164
  ],
1109
1165
  capabilities: ["acceptance-tests", "product-output-verification"],
@@ -1126,7 +1182,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1126
1182
  "Expensive renderers must cache decoded media, source pixels, glyph atlases, gradients, and other reusable inputs by media id, canvas size, and stable control keys.",
1127
1183
  "Slider drags and high-frequency controls must debounce or coalesce preview work, cancel stale async renders, and avoid re-decoding media on every control change.",
1128
1184
  "Performance matrices must declare rendererWorkload as none, simple-composition, text-output, vector-output, or pixel-output.",
1129
- "A full performance checkpoint must run with pnpm verify:perf only when the first working app version exists or the user requests performance, lag, jank, animation speed, drag/zoom stabilization work, or otherwise complains about performance.",
1185
+ "A full performance checkpoint must run only when the first working app version exists or the user requests performance, lag, jank, animation speed, drag/zoom stabilization work, or otherwise complains about performance; use the agent-controlled browser first and pnpm verify:perf only as the Playwright fallback.",
1130
1186
  "Renderer, canvas, animation, export, timeline, layers, canvas.renderScale, bug fixes, and performance-sensitive control changes use targeted functional/browser checks first and targeted performance scenarios only for touched workload, viewport, or export paths.",
1131
1187
  "Performance fixes must preserve selected output and preview quality; do not reduce image quality, selected renderScale, export resolution, source media fidelity, or canvas backing pixels as the hidden way to pass budgets.",
1132
1188
  "When canvas or slider interactions lag, diagnose where the slowdown comes from before changing output quality: renderer technique, React update frequency, decoded media, shader/program setup, buffer uploads, layout work, async render cancellation, or animation scheduling.",
@@ -1178,11 +1234,14 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1178
1234
  'Use referenceTimeline.mode "toolcraft-keyframes" when controls need keyframe diamonds, expanded keyframe rows, easing, or editable keyframes.',
1179
1235
  'Use referenceTimeline.mode "none" only when the reference has no user-facing transport behavior at all.',
1180
1236
  "Reference clone specs must list every detected transport behavior explicitly, including pause-resume, restart, time-progress, export-at-time, playback, scrub, duration, loop, and keyframes when present.",
1237
+ 'Toolcraft reference timelines must declare referenceTimeline.loopDuration with source "reference", "user-request", or "product-derived", plus seconds and evidence; runtime/template fallback 8s is not a valid source.',
1238
+ "panels.timeline.defaultDurationSeconds must match referenceTimeline.loopDuration.seconds for referenceTimeline.mode toolcraft-playback/toolcraft-keyframes.",
1181
1239
  "Do not create right-panel controls named or targeted as Play, Pause, Paused, Animate, Restart animation, or equivalent app-wide transport toggles.",
1182
1240
  'Do not downgrade custom reference timelines to panels.timeline mode "playback". State buttons, trim handles, selected-range playback, or range export require referenceTimeline.mode "custom-reference-timeline" and dedicated acceptance.',
1183
1241
  "Generated reference clone apps must declare starterTransferMode.referenceTimeline with mode none, toolcraft-playback, toolcraft-keyframes, or custom-reference-timeline.",
1184
1242
  "Custom reference timeline behavior needs referenceTimelineCoverage entries such as state-jump, trim-range, range-playback, all-range, jump-to-trim-start, and export-range.",
1185
1243
  "Reference clone acceptance must include referenceCoverage rows for canvas sizing, control mapping, renderer state, and any renderer loop, spawn/update cadence, pause/resume, export/copy, or media lifecycle behavior in the reference.",
1244
+ "Video reference assets used in a reference clone also require starterTransferMode.videoReferenceStudy with storyboard frames, frame-to-frame transition analysis, behavior decomposition, and acceptance mapping.",
1186
1245
  "Browser tests must compare reference behavior or a reference-derived baseline, not only Toolcraft state mutation.",
1187
1246
  ],
1188
1247
  capabilities: [
@@ -1207,12 +1266,14 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1207
1266
  "When the section is generic, mixed, missing, or otherwise weak context, include the affected entity or role in the label: Pattern color, Background opacity, Wave speed, Stroke width.",
1208
1267
  "Acceptance validators suggest semantic replacement labels for weak generic labels; fix the schema label instead of relying on runtime fallback rewriting.",
1209
1268
  "Controls-panel sections should stay discrete: two to seven product controls is the normal size, and larger sections must split by product sub-entity or workflow stage.",
1210
- "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.",
1211
- "Every visible controls-panel section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon; generated apps must not hand-build section headers.",
1269
+ "Generated product apps must export starterControlSectionInventory beside acceptance. Every product controls section declares title, exact targets, groupingReason, and entity or workflowStage; intentional splits of one target entity require workflowStage and splitReason on every split section.",
1270
+ "Section splitting must preserve dependency cohesion: a selector stays with the visibleWhen controls it gates when they share the same target entity or selected branch. Use internal spacing/dividers or a more specific section title before splitting dependent branch controls away.",
1271
+ "Every app-authored controls-panel body section must have a short meaningful visible title. Runtime-created Setup renders as the first visible headerless controls block with no title, reset action, collapse button, or collapsed state; sticky footer action sections use the technical title Export but render without a visible heading.",
1272
+ "Every visible app-authored controls-panel section title renders through the standard 36px collapsible header row with vertically centered text and the runtime collapse icon; generated apps must not hand-build section headers.",
1212
1273
  "Controls-panel section expand and collapse uses the standard runtime height/opacity animation; generated apps must not replace it with instant custom section visibility.",
1213
- "Ordinary controls-panel section collapsed/expanded state persists as a runtime UI preference per app. 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.",
1274
+ "Controls-panel section collapsed/expanded state persists as a runtime UI preference per app. It is not undo/redo state, not settings import/export state, and Reset controls must not clear it. Runtime Setup is not collapsible; sticky footer Export sections are not collapsible.",
1214
1275
  "Ordinary controls-panel section headers expose the runtime section reset action before the collapse button; it dispatches controls.resetTargets and restores only that section's control targets to their schema defaultValue.",
1215
- "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.",
1276
+ "Runtime Setup and ordinary controls-panel body sections use 8px top spacing and 24px bottom spacing for their control content. Sticky footer action sections keep their dedicated spacing.",
1216
1277
  "Broad section titles such as Flow, Icon, Shapes, Scene, Text, Typography, or Motion are only valid for small cohesive groups; use specific titles such as Flow Motion, Flow Geometry, Letter Burst, Shape Colors, Logo Glow, Logo Plate, or Text Block for larger groups.",
1217
1278
  "Section titles in one controls panel must be unique.",
1218
1279
  "Use section titles, option labels, tests, or renderer/spec prose for details instead of long field labels.",
@@ -1271,12 +1332,21 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1271
1332
  timelinePanel: {
1272
1333
  ...panel("timelinePanel", "TimelinePanel", "top", ["top", "bottom"], "panel"),
1273
1334
  aiUsageRules: [
1274
- "Do not enable the timeline panel just because a renderer is animated.",
1335
+ "Any product output animation must enable the top Toolcraft timeline; do not ship animated product output with only local requestAnimationFrame playback.",
1275
1336
  "Before choosing no timeline for any animated product, write an Animation Intent Inventory: product transport, editable keyframes, or autonomous decorative output, plus the user-facing time behaviors present or intentionally absent.",
1276
- 'User-requested product animation defaults to panels.timeline mode "playback" unless the spec explicitly declares autonomous decorative/self-running output with no play, pause, scrub, duration, loop, or export-at-time behavior.',
1277
- 'Use panels.timeline: { mode: "playback" } when the product needs user-facing play, pause, scrubbing, duration, loop, restart, time progress, or export-at-time controls.',
1337
+ 'User-requested product animation defaults to panels.timeline mode "playback" unless the spec explicitly declares autonomous decorative/self-running output with no play, pause, scrub, duration, loop, export-at-time behavior, or video export.',
1338
+ 'Any product app with Export Video must enable the top Toolcraft timeline: use panels.timeline mode "playback" for product animation transport, or mode "keyframes" when exported animation is driven by keyframes.',
1339
+ 'Use panels.timeline: { mode: "playback" } when the product needs user-facing play, pause, scrubbing, duration, loop, restart, time progress, export-at-time controls, or video export.',
1340
+ 'When panels.timeline is enabled for a new Toolcraft app, appTransferMode.animationIntent must match it: mode "timeline-playback" for playback, or mode "timeline-keyframes" for keyframes.',
1278
1341
  "Playback renderers must consume runtime timeline state; pause freezes output, scrubbing renders a deterministic frame, and the full animation cycle maps to state.timeline.durationSeconds instead of a local fixed duration.",
1279
- "Playback renderers may compute an initial default duration, but must not watch state.timeline.durationSeconds and dispatch timeline.setDuration back to a computed local duration. User-edited timeline duration is the source of truth after initialization or reset.",
1342
+ "When the product has a known loop duration, declare it as panels.timeline.defaultDurationSeconds; the runtime timeline duration starts from that loop duration instead of an unrelated 8s default.",
1343
+ 'Timeline animation intent must declare loopDuration with source "reference", "user-request", or "product-derived", plus seconds and evidence; runtime/template fallback 8s is not a valid source.',
1344
+ "panels.timeline.defaultDurationSeconds must match animationIntent.loopDuration.seconds for playback/keyframe animation, so the initial timeline UI shows the declared product loop instead of a generic default.",
1345
+ "Video export renderers must render deterministic frames from runtime timeline timestamps and duration; do not treat autonomous wall-clock time or captureStream recording time as the source of product duration.",
1346
+ "Playback renderers may compute an initial loop duration default during app initialization or reset, but must not watch state.timeline.durationSeconds and dispatch timeline.setDuration back to a computed local duration. User-edited timeline duration becomes the loop duration source of truth after initialization or reset.",
1347
+ "Playback renderers should use getToolcraftTimelineLoopTime or getToolcraftTimelineLoopProgress to derive product loop phase from state.timeline.currentTimeSeconds and state.timeline.durationSeconds; do not hand-roll wall-clock, fixed-duration, mirror, yoyo, ping-pong, or reverse phase math.",
1348
+ "Product animation loop means a seamless forward-only cycle by default: motion advances in one direction, first and last frames stitch without a visible jump, and mirror/yoyo/ping-pong/reverse loops require explicit user request.",
1349
+ "Changing timeline duration must preserve seamless forward-loop semantics: one full product animation cycle maps from 0 to state.timeline.durationSeconds, first and last frames still stitch, direction does not reverse, and changing duration must not switch the renderer to wall-clock or fixed local speed.",
1280
1350
  "When non-looping playback reaches the end, pressing Play again restarts playback from time 0.",
1281
1351
  "Intrinsic-media upload timelines must stay paused at time 0 until source media exists; clearing the last media asset must pause and reset playback.",
1282
1352
  'Use panels.timeline: { mode: "keyframes" } or panels.timeline: true only when controls need keyframe diamonds, expanded timeline rows, easing, or keyframe editing.',
@@ -1292,6 +1362,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1292
1362
  "Do not put Pause or Resume in panelActions; playback belongs to TimelinePanel transport controls.",
1293
1363
  "Do not replace TimelinePanel with an app-level playback, transport, or timeline panel to avoid runtime performance issues; fix the Toolcraft runtime clock/state path instead.",
1294
1364
  'Custom timeline UI is allowed only for explicit referenceTimeline.mode "custom-reference-timeline" transfers with browser-backed referenceTimelineCoverage.',
1365
+ 'When panels.timeline is enabled, runtime inserts a Setup switch labeled "Timeline" as the last runtime setup control after Resolution scale.',
1366
+ "The Timeline Setup switch controls only compact versus extended runtime presentation: off shows the compact Play-only transport, on shows the extended timeline with scrubber, duration, loop, and keyframe UI. It must not stop playback, change keyframes, affect export, enter product values, or be reset by Reset controls/settings transfer.",
1295
1367
  "Playback-only timelines stay collapsed and do not show keyframe diamonds or expanded keyframe rows.",
1296
1368
  "If timeline verification fails, wire the renderer to runtime timeline state or remove panels.timeline.",
1297
1369
  ],
@@ -1310,6 +1382,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1310
1382
  "timeline.deleteKeyframe",
1311
1383
  "timeline.moveKeyframe",
1312
1384
  "timeline.selectKeyframe",
1385
+ "panels.setHidden",
1313
1386
  "timeline.setCurrentTime",
1314
1387
  "timeline.setDuration",
1315
1388
  "timeline.setPlaying",