@pixel-point/toolcraft 0.0.9 → 0.0.12

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 (77) hide show
  1. package/README.md +42 -9
  2. package/package.json +2 -1
  3. package/src/cli.mjs +4 -2
  4. package/src/cli.test.mjs +5 -2
  5. package/src/generate.mjs +14 -6
  6. package/src/generate.test.mjs +33 -2
  7. package/templates/runtime/contracts/component-contracts.test.ts +175 -36
  8. package/templates/runtime/contracts/component-contracts.ts +90 -45
  9. package/templates/runtime/contracts/decision-contracts.test.ts +33 -1
  10. package/templates/runtime/contracts/decision-contracts.ts +19 -6
  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 +478 -27
  17. package/templates/runtime/react/controls-panel.tsx +71 -26
  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 +225 -173
  27. package/templates/runtime/schema/define-toolcraft.ts +117 -247
  28. package/templates/runtime/schema/runtime-targets.ts +21 -0
  29. package/templates/runtime/schema/types.ts +41 -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 +657 -2
  41. package/templates/runtime/testing/performance.ts +789 -49
  42. package/templates/starter/AGENTS.md +22 -16
  43. package/templates/starter/docs/toolcraft/README.md +8 -4
  44. package/templates/starter/docs/toolcraft/acceptance-testing.md +41 -8
  45. package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
  46. package/templates/starter/docs/toolcraft/assembly-workflow.md +51 -20
  47. package/templates/starter/docs/toolcraft/component-rules.md +58 -37
  48. package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
  49. package/templates/starter/docs/toolcraft/performance.md +30 -10
  50. package/templates/starter/docs/toolcraft/schema-reference.md +134 -33
  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 +57 -4
  54. package/templates/starter/e2e/performance-helpers.ts +45 -0
  55. package/templates/starter/index.html +1 -0
  56. package/templates/starter/package.json +3 -2
  57. package/templates/starter/playwright.config.ts +1 -1
  58. package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
  59. package/templates/starter/scripts/run-vite-on-free-port.mjs +82 -16
  60. package/templates/starter/scripts/toolcraft-port.mjs +178 -0
  61. package/templates/starter/scripts/toolcraft-port.test.mjs +147 -0
  62. package/templates/starter/src/app/starter-acceptance.test.ts +2573 -313
  63. package/templates/starter/src/app/starter-acceptance.ts +978 -81
  64. package/templates/starter/src/app/starter-performance.test.ts +130 -8
  65. package/templates/starter/src/app/starter-performance.ts +5 -0
  66. package/templates/starter/src/app/starter-schema.test.ts +32 -7
  67. package/templates/starter/src/app/starter-schema.ts +6 -2
  68. package/templates/starter/vite.config.ts +58 -2
  69. package/templates/ui/components/controls/actions/actions-control.tsx +46 -3
  70. package/templates/ui/components/controls/color/palette-control.tsx +34 -4
  71. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
  72. package/templates/ui/components/controls/file-drop/index.ts +6 -1
  73. package/templates/ui/components/controls/index.ts +2 -0
  74. package/templates/ui/components/controls/range-slider/range-slider-value.ts +19 -5
  75. package/templates/ui/components/controls/select/select-control.tsx +4 -26
  76. package/templates/ui/components/controls/vector/vector-control.tsx +25 -4
  77. package/templates/ui/components/panel/panel-actions.tsx +1 -1
@@ -66,9 +66,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
66
66
  'Specs, plans, and app-schema tests must assert explicit discrete sliders render as variant: "discrete" with markers derived from min, max, and step.',
67
67
  'Browser verification can inspect [data-slot="slider"][data-variant="discrete"] plus slider markers to prove the Toolcraft component variant rendered.',
68
68
  "Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
69
- "Use schema disabled: true for unavailable sliders; do not recreate a disabled-looking slider with custom markup.",
70
- "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.",
71
- "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.",
72
72
  ],
73
73
  },
74
74
  rangeSlider: {
@@ -115,12 +115,12 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
115
115
  "Compact symbol/CSS units render tight, such as 20% – 80% or 12px – 48px; word units render with a space when truly needed.",
116
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.",
117
117
  "RangeSlider defaultValue must start with different lower and upper values so the two-thumb control does not collapse into a single-value slider.",
118
- "Manual range value editing accepts common separators such as slash, hyphen, spaces, and dashes; do not create custom parsers for RangeSlider labels.",
118
+ "Manual range value editing accepts common separators such as slash, hyphen, spaces, and dashes, including when values include unit suffixes such as 30%-150% or 30% - 90%; do not create custom parsers for RangeSlider labels.",
119
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.',
120
120
  "Visual discrete sliders must still drag smoothly; browser performance tests should use expectToolcraftDiscreteSliderDragSmoothness for real pointer drag.",
121
- "Use schema disabled: true for unavailable range sliders; do not recreate a disabled-looking range slider with custom markup.",
122
- "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.",
123
- "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.",
124
124
  "Acceptance must prove both rangeSlider.lower and rangeSlider.upper change the product output; testing one handle is not enough.",
125
125
  ],
126
126
  },
@@ -136,7 +136,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
136
136
  ],
137
137
  useWhen: [
138
138
  "Use Select for finite choices with long labels, many options, or values that would not fit in Segmented.",
139
- "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.",
140
140
  ],
141
141
  doNotReplaceWith: [
142
142
  "Do not use Select to recreate FontPicker, Gradient, ImagePicker, FileDrop, Curves, Vector, or Palette value models.",
@@ -145,8 +145,9 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
145
145
  "Use Segmented for two to four short closely related options that fit without clipping.",
146
146
  ],
147
147
  layoutConstraints: [
148
- "Prefer compact two-column inline layout for related short Select pairs that tune one workflow or entity.",
149
- "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.",
150
151
  "If a compact Select pair falls back to vertical layout, record the fit reason in the spec or worklog.",
151
152
  ],
152
153
  requiredAcceptance: [
@@ -502,6 +503,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
502
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.",
503
504
  "defineToolcraft hoists panelActions into the controls panel sticky footer automatically.",
504
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.',
505
507
  "Static or still-output apps include Export PNG as the primary footer action.",
506
508
  'Every app with Export PNG must expose a separate "Image Export" controls section.',
507
509
  'The Image Export section must include "export.image.format" as a Select control with PNG and JPG choices, defaulting to "png".',
@@ -509,6 +511,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
509
511
  "Image Export format and resolution render as one compact two-column inline Select pair, matching the Video Export settings structure.",
510
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.",
511
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.",
512
515
  'Animated apps with Export Video must expose a separate "Video Export" controls section.',
513
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.',
514
517
  'The Video Export section must include format and resolution controls such as targets "export.video.format" and "export.video.resolution".',
@@ -519,6 +522,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
519
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.",
520
523
  'Baseline browser video formats are "mp4" and "webm"; MOV or ProRes require an explicit custom encoder/transcoder and dedicated acceptance plus performance coverage.',
521
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.",
522
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.",
523
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.',
524
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.",
@@ -526,7 +531,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
526
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.',
527
532
  "Product-output apps must pass the includeBackground runtime value to createToolcraftPngExportCanvas for PNG alpha and call shouldIncludeToolcraftPreviewBackground(state) for live preview product background.",
528
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.",
529
- "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.",
530
535
  "Copy PNG can be a secondary action when clipboard output is useful, but copy does not replace export.",
531
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.",
532
537
  "Footer actions must be one compact horizontal group; do not split them into stacked full-width sections.",
@@ -566,6 +571,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
566
571
  ],
567
572
  requiredAcceptance: [
568
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.",
569
575
  ],
570
576
  }),
571
577
  aiUsageRules: [
@@ -575,6 +581,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
575
581
  "Do not use Palette when opacity belongs to the same color entity; use ColorOpacity instead.",
576
582
  "Do not use Palette for gradients or color transitions; use Gradient instead.",
577
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.",
578
585
  "Palette is a compound control; acceptance must prove palette.family and palette.shade both affect the product output.",
579
586
  "Do not accept a palette test that only changes a swatch preview without proving the renderer/export consumes the selected family and shade.",
580
587
  ],
@@ -585,21 +592,23 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
585
592
  strictness: "exact-owner",
586
593
  ownsValueModel: [
587
594
  "x/y vector",
588
- "position",
589
- "offset",
590
- "direction",
595
+ "user-authored stable position",
596
+ "user-authored stable offset",
597
+ "user-authored stable direction",
591
598
  "focus point",
592
599
  "light vector",
593
600
  "color balance pad",
594
601
  ],
595
602
  useWhen: [
596
- "Use Vector for paired X/Y values such as position, offset, direction, focus, anchor, light direction, or color-balance movement.",
603
+ "Use Vector for paired X/Y values only when the user is meant to manually author a stable two-axis product parameter such as position, offset, direction, focus, anchor, light direction, or color-balance movement.",
597
604
  ],
598
605
  doNotReplaceWith: [
599
606
  "Do not replace Vector with two unrelated sliders or text inputs when direct two-axis editing is the product interaction.",
607
+ "Do not replace animation, keyboard input, pointer input, physics, timeline phase, velocity, or simulated pose state with Vector just because the internal value has x/y coordinates.",
600
608
  ],
601
609
  acceptableAlternatives: [
602
610
  "Use two numeric text fields only when exact numeric entry is the primary product requirement.",
611
+ "Use timeline, keyboard/pointer handlers, motion sliders, path/step controls, or renderer simulation state when movement is generated by animation or user input rather than authored as a stable panel value.",
603
612
  ],
604
613
  layoutConstraints: [
605
614
  "One vector renders as a square pad; multiple vectors render compact pads.",
@@ -612,16 +621,20 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
612
621
  "If the controls panel contains exactly one vector control, the runtime renders the vector pad as a square.",
613
622
  "If the controls panel contains multiple vector controls, the runtime renders compact vector pads.",
614
623
  "Multiple vector controls should live in separate semantic sections unless they intentionally belong to the same entity with other related controls.",
624
+ "Use Vector only for user-authored stable two-axis product parameters. Do not expose Vector for current animation state, keyboard movement, pointer movement, physics state, timeline phase, velocity, target pose, current pose, or simulated position/direction.",
625
+ "Before adding a Vector control to an animated or interactive product, classify movement ownership as direct-authored, timeline-driven, keyboard/pointer-driven, or simulation-owned. Only direct-authored movement may become a visible Vector control; the other ownership modes stay in renderer/runtime interaction state and use controls such as Speed, Step, Spread, Path, Duration, or Timeline when the user needs tuning.",
615
626
  'Use variant: "whiteBalance" for temperature/tint pads: X maps cool blue to warm amber, Y maps green to magenta.',
616
627
  'Use variant: "colorBalance" for paired color-balance axes such as cyan/red and blue/yellow correction.',
617
628
  'Use variant: "chromaOffset" for RGB/chromatic offset vectors where the X/Y movement controls channel separation.',
618
629
  'Use variant: "toneBias" for split-tone, duotone, or color-grading vectors where both axes describe tone or hue bias.',
619
630
  'Use the default vector variant for spatial values such as position, offset, direction, focus, anchor, and light direction.',
620
631
  '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.',
621
- "Holding Shift while dragging a vector pad locks movement to the dominant axis; do not build a custom pad just to support axis-constrained movement.",
632
+ "Vector pad value labels render compact rounded coordinates. Do not expose raw floating-point strings such as -0.07070312499999998 in the controls panel.",
633
+ "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.",
634
+ "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.",
622
635
  'Use coordinateMode: "cartesian" only when the product intentionally exposes mathematical Y-up coordinates instead of canvas/screen movement.',
623
636
  "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.",
624
- "Vector is a compound control; acceptance must prove vector.x and vector.y both affect the product output.",
637
+ "Vector is a compound control; acceptance must prove vector.x and vector.y both affect the product output and that the vector represents a user-authored stable two-axis parameter rather than current animation, input, or simulation state.",
625
638
  ],
626
639
  },
627
640
  color: {
@@ -823,7 +836,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
823
836
  "Do not use AnchorGrid for freeform two-axis movement; use Vector.",
824
837
  ],
825
838
  acceptableAlternatives: [
826
- "Use Vector for continuous position or direction.",
839
+ "Use Vector only for stable direct-authored continuous position or direction parameters.",
827
840
  ],
828
841
  layoutConstraints: [
829
842
  "AnchorGrid is a standalone position selector.",
@@ -895,29 +908,38 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
895
908
  ],
896
909
  layoutConstraints: [
897
910
  "FileDrop lives in the controls panel; single-layer apps use its preview and clear behavior.",
911
+ "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.",
912
+ "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.",
898
913
  "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.",
914
+ "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.",
899
915
  "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.",
900
916
  ],
901
917
  requiredAcceptance: [
902
- "Prove file import changes media state and product output; prove clear, reorder, and section/global reset update source material.",
918
+ "Prove file import changes media state and product output; prove clear, reorder, rotate, flip, and section/global reset update source material.",
903
919
  ],
904
920
  }),
905
921
  aiUsageRules: [
906
922
  "Use fileDrop for source material uploads in the controls panel, not on the canvas.",
907
923
  'Use fileDrop with assetKind: "image" for image-only source media and assetKind: "file" for arbitrary uploaded files.',
924
+ "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.",
925
+ "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.",
908
926
  "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.",
909
927
  "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.",
910
928
  "In single-layer apps, the runtime shows the uploaded image as the fileDrop preview and provides the clear action.",
929
+ "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.",
930
+ "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.",
931
+ "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.",
911
932
  "In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
912
- "In single-layer apps, global Reset controls and section reset must remove uploaded fileDrop source media and return the fileDrop target to defaultValue.",
933
+ "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.",
913
934
  "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.",
914
935
  "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.",
915
936
  "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.",
916
937
  "Dragging thumbnails reorders runtime mediaAssets; preview, export, and renderer mapping must consume that media order instead of maintaining a separate product-only order.",
938
+ "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.",
917
939
  "Do not create custom upload buttons, file lists, or file sorting for generic source uploads when fileDrop can represent the source set.",
918
940
  "In multi-layer apps, deletion and visibility belong to the Layers panel; fileDrop remains an upload target.",
919
941
  ],
920
- commands: ["media.delete", "media.import", "media.reorder"],
942
+ commands: ["media.delete", "media.import", "media.reorder", "media.transform"],
921
943
  },
922
944
  imagePicker: {
923
945
  ...control("imagePicker", "ImagePicker", "standalone", "component-owned"),
@@ -1045,15 +1067,17 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1045
1067
  canvas: {
1046
1068
  aiUsageRules: [
1047
1069
  "Choose canvas.sizing.mode from product context instead of copying a universal 1024px artboard.",
1048
- "Use intrinsic-media for single-layer upload/generation apps so imported media natural size becomes canvas.size.",
1070
+ "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.",
1071
+ "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.",
1049
1072
  "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.",
1073
+ "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.",
1050
1074
  "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.",
1051
1075
  "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.",
1052
1076
  "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.",
1053
1077
  "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.",
1054
- "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.",
1078
+ "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.",
1055
1079
  "If canvas.size is provided without an explicit sizing mode, defineToolcraft treats it as editable-output and adds Canvas width and Canvas height controls.",
1056
- "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.",
1080
+ "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.",
1057
1081
  "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.",
1058
1082
  "Aspect ratio presets are the only interaction that may resize both canvas dimensions from a preset; manual size inputs are exact output dimensions.",
1059
1083
  "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.",
@@ -1072,6 +1096,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1072
1096
  "canvas.zoomReset",
1073
1097
  "media.delete",
1074
1098
  "media.import",
1099
+ "media.reorder",
1100
+ "media.transform",
1075
1101
  ],
1076
1102
  historyPolicy: "patch",
1077
1103
  id: "canvas",
@@ -1084,10 +1110,11 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1084
1110
  aiUsageRules: [
1085
1111
  "Do not write app state to localStorage directly.",
1086
1112
  "Use schema persistence policy for app state that should survive reload.",
1087
- "Persistence may include values, canvas, panels, timeline, and layers; history and media blobs are not persisted.",
1113
+ "Persistence may include values, canvas, panels, timeline, layers, and media; history is not persisted.",
1114
+ "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.",
1088
1115
  'Apps with visible runtime panels and localStorage persistence must include "panels" so dragged panel positions survive reload.',
1089
1116
  '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.',
1090
- "Settings import/export is a preset transfer feature for complex apps; it must not be used to hide or replace broken persistence reload behavior.",
1117
+ "Settings import/export is a mandatory runtime preset transfer feature; it must not be used to hide or replace broken persistence reload behavior.",
1091
1118
  "Do not store media blobs, files, or large generated images in localStorage.",
1092
1119
  ],
1093
1120
  capabilities: ["themePreference", "appStatePolicy"],
@@ -1101,12 +1128,13 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1101
1128
  },
1102
1129
  settingsTransfer: {
1103
1130
  aiUsageRules: [
1104
- 'Use schema settingsTransfer: "auto" for complex apps unless the prompt explicitly disables settings import/export.',
1105
- "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.",
1131
+ "Generated apps keep a controls panel so runtime Setup is visible from the first run; product controls are added after that mandatory runtime section.",
1132
+ "Do not gate Export Settings / Import Settings behind complexity thresholds, app size, or prompt wording.",
1106
1133
  "Do not hand-roll settings import/export through app routes, hidden file inputs, or panelActions.",
1107
- "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.",
1108
- "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.",
1109
- "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.",
1134
+ "Settings transfer appears in the first visible headerless Setup controls-panel block; it imports and exports control values, canvas size, and timeline state.",
1135
+ "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.",
1136
+ "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.",
1137
+ "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.",
1110
1138
  "Keep sticky footer panelActions for product delivery actions only, such as Export PNG, Export Video, Copy, Generate, Apply, or Download.",
1111
1139
  ],
1112
1140
  capabilities: ["settings-import-export"],
@@ -1126,14 +1154,16 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1126
1154
  "Acceptance tests must fail when an entity is disconnected from runtime state, renderer output, export output, or command side effects.",
1127
1155
  "Do not accept typecheck, component existence, registered commands, runtime state mutation, renderer input objects, shader uniform presence, or signature strings as final proof.",
1128
1156
  "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.",
1157
+ "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.",
1129
1158
  "A generic canvas hash difference is not enough for workload or semantic controls; assert the intended direction of the effect.",
1130
1159
  "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.",
1131
1160
  "Conditional entities require fixtures that make the condition observable.",
1132
- "Use visibleWhen for mode-, type-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
1133
- "When visibleWhen or disabledWhen 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.",
1161
+ "Use visibleWhen for mode-, type-, source-, include-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
1162
+ "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.",
1134
1163
  "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.",
1135
- "Use disabledWhen for controls that belong to the current entity but are temporarily unavailable for the selected mode; the disabled value must be preserved.",
1136
- "Do not leave inactive conditional controls visible and enabled while making the renderer ignore them.",
1164
+ "If a switch/select/segmented/imagePicker/checkbox chooses a branch for the same product entity, controls outside the current branch use visibleWhen, not disabledWhen.",
1165
+ "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.",
1166
+ "Do not leave inactive conditional controls visible while making the renderer ignore them.",
1137
1167
  "If an entity cannot be tested against a product-level observable, remove it from the app schema or ask whether it is required.",
1138
1168
  ],
1139
1169
  capabilities: ["acceptance-tests", "product-output-verification"],
@@ -1156,7 +1186,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1156
1186
  "Expensive renderers must cache decoded media, source pixels, glyph atlases, gradients, and other reusable inputs by media id, canvas size, and stable control keys.",
1157
1187
  "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.",
1158
1188
  "Performance matrices must declare rendererWorkload as none, simple-composition, text-output, vector-output, or pixel-output.",
1159
- "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.",
1189
+ "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.",
1160
1190
  "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.",
1161
1191
  "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.",
1162
1192
  "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.",
@@ -1208,11 +1238,14 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1208
1238
  'Use referenceTimeline.mode "toolcraft-keyframes" when controls need keyframe diamonds, expanded keyframe rows, easing, or editable keyframes.',
1209
1239
  'Use referenceTimeline.mode "none" only when the reference has no user-facing transport behavior at all.',
1210
1240
  "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.",
1241
+ '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.',
1242
+ "panels.timeline.defaultDurationSeconds must match referenceTimeline.loopDuration.seconds for referenceTimeline.mode toolcraft-playback/toolcraft-keyframes.",
1211
1243
  "Do not create right-panel controls named or targeted as Play, Pause, Paused, Animate, Restart animation, or equivalent app-wide transport toggles.",
1212
1244
  '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.',
1213
1245
  "Generated reference clone apps must declare starterTransferMode.referenceTimeline with mode none, toolcraft-playback, toolcraft-keyframes, or custom-reference-timeline.",
1214
1246
  "Custom reference timeline behavior needs referenceTimelineCoverage entries such as state-jump, trim-range, range-playback, all-range, jump-to-trim-start, and export-range.",
1215
1247
  "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.",
1248
+ "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.",
1216
1249
  "Browser tests must compare reference behavior or a reference-derived baseline, not only Toolcraft state mutation.",
1217
1250
  ],
1218
1251
  capabilities: [
@@ -1238,13 +1271,13 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1238
1271
  "Acceptance validators suggest semantic replacement labels for weak generic labels; fix the schema label instead of relying on runtime fallback rewriting.",
1239
1272
  "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.",
1240
1273
  "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.",
1241
- "Section splitting must preserve dependency cohesion: a selector stays with the visibleWhen/disabledWhen 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.",
1242
- "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.",
1243
- "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.",
1274
+ "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.",
1275
+ "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.",
1276
+ "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.",
1244
1277
  "Controls-panel section expand and collapse uses the standard runtime height/opacity animation; generated apps must not replace it with instant custom section visibility.",
1245
- "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.",
1278
+ "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.",
1246
1279
  "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.",
1247
- "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.",
1280
+ "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.",
1248
1281
  "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.",
1249
1282
  "Section titles in one controls panel must be unique.",
1250
1283
  "Use section titles, option labels, tests, or renderer/spec prose for details instead of long field labels.",
@@ -1303,12 +1336,21 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1303
1336
  timelinePanel: {
1304
1337
  ...panel("timelinePanel", "TimelinePanel", "top", ["top", "bottom"], "panel"),
1305
1338
  aiUsageRules: [
1306
- "Do not enable the timeline panel just because a renderer is animated.",
1339
+ "Any product output animation must enable the top Toolcraft timeline; do not ship animated product output with only local requestAnimationFrame playback.",
1307
1340
  "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.",
1308
- '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.',
1309
- 'Use panels.timeline: { mode: "playback" } when the product needs user-facing play, pause, scrubbing, duration, loop, restart, time progress, or export-at-time controls.',
1341
+ '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.',
1342
+ '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.',
1343
+ '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.',
1344
+ '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.',
1310
1345
  "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.",
1311
- "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.",
1346
+ "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.",
1347
+ '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.',
1348
+ "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.",
1349
+ "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.",
1350
+ "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.",
1351
+ "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.",
1352
+ "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.",
1353
+ "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.",
1312
1354
  "When non-looping playback reaches the end, pressing Play again restarts playback from time 0.",
1313
1355
  "Intrinsic-media upload timelines must stay paused at time 0 until source media exists; clearing the last media asset must pause and reset playback.",
1314
1356
  'Use panels.timeline: { mode: "keyframes" } or panels.timeline: true only when controls need keyframe diamonds, expanded timeline rows, easing, or keyframe editing.',
@@ -1324,6 +1366,8 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1324
1366
  "Do not put Pause or Resume in panelActions; playback belongs to TimelinePanel transport controls.",
1325
1367
  "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.",
1326
1368
  'Custom timeline UI is allowed only for explicit referenceTimeline.mode "custom-reference-timeline" transfers with browser-backed referenceTimelineCoverage.',
1369
+ 'When panels.timeline is enabled, runtime inserts a Setup switch labeled "Timeline" as the last runtime setup control after Resolution scale.',
1370
+ "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.",
1327
1371
  "Playback-only timelines stay collapsed and do not show keyframe diamonds or expanded keyframe rows.",
1328
1372
  "If timeline verification fails, wire the renderer to runtime timeline state or remove panels.timeline.",
1329
1373
  ],
@@ -1342,6 +1386,7 @@ export const TOOLCRAFT_COMPONENT_CONTRACTS = {
1342
1386
  "timeline.deleteKeyframe",
1343
1387
  "timeline.moveKeyframe",
1344
1388
  "timeline.selectKeyframe",
1389
+ "panels.setHidden",
1345
1390
  "timeline.setCurrentTime",
1346
1391
  "timeline.setDuration",
1347
1392
  "timeline.setPlaying",
@@ -24,7 +24,7 @@ describe("Toolcraft template decision contract", () => {
24
24
  for (const rule of TOOLCRAFT_DECISION_CONTRACT) {
25
25
  expect(rule.id).toMatch(/^[a-z0-9-]+$/);
26
26
  expect(rule.area).toMatch(
27
- /^(runtime-shell|canvas|panels|layers|timeline|controls|renderer|reference-clone|acceptance|performance|persistence|workflow)$/,
27
+ /^(runtime-shell|canvas|panels|layers|timeline|controls|renderer|reference-analysis|reference-clone|acceptance|performance|persistence|workflow)$/,
28
28
  );
29
29
  expect(rule.title.trim().length).toBeGreaterThan(0);
30
30
  expect(rule.currentConstraint.trim().length).toBeGreaterThan(0);
@@ -85,6 +85,36 @@ describe("Toolcraft template decision contract", () => {
85
85
  expect(rule?.enforcement).toContain("performance-validator");
86
86
  });
87
87
 
88
+ it("requires reference clone functionality to be inventoried and acceptance-backed", () => {
89
+ const rule = getToolcraftDecisionRule("reference-clone-source-of-truth");
90
+
91
+ expect(rule?.level).toBe("invariant");
92
+ expect(rule?.desiredBehavior).toMatch(/referenceStudy evidence/i);
93
+ expect(rule?.desiredBehavior).toMatch(/running the original or restoring it locally/i);
94
+ expect(rule?.desiredBehavior).toMatch(/reference feature inventory/i);
95
+ expect(rule?.desiredBehavior).toMatch(/feature-level behavior evidence/i);
96
+ expect(rule?.desiredBehavior).toMatch(/user-visible and output-affecting feature/i);
97
+ expect(rule?.desiredBehavior).toMatch(/Toolcraft implementation/i);
98
+ expect(rule?.desiredBehavior).toMatch(/acceptance coverage/i);
99
+ expect(rule?.desiredBehavior).toMatch(/explicit user approval/i);
100
+ expect(rule?.enforcement).toContain("acceptance-validator");
101
+ });
102
+
103
+ it("requires video references to be studied as behavior before implementation", () => {
104
+ const rule = getToolcraftDecisionRule("video-reference-analysis");
105
+
106
+ expect(rule?.level).toBe("invariant");
107
+ expect(rule?.area).toBe("reference-analysis");
108
+ expect(rule?.desiredBehavior).toMatch(/video, GIF, screen recording/i);
109
+ expect(rule?.desiredBehavior).toMatch(/storyboard/i);
110
+ expect(rule?.desiredBehavior).toMatch(/timecoded frame observations/i);
111
+ expect(rule?.desiredBehavior).toMatch(/frame-to-frame transitions/i);
112
+ expect(rule?.desiredBehavior).toMatch(/map those observed behaviors to acceptance rows/i);
113
+ expect(rule?.desiredBehavior).toMatch(/independent from loop duration or timeline choice/i);
114
+ expect(rule?.enforcement).toContain("acceptance-validator");
115
+ expect(rule?.enforcement).toContain("starter-agents");
116
+ });
117
+
88
118
  it("splits performance into workload and lightweight responsiveness coverage", () => {
89
119
  const rule = getToolcraftDecisionRule("performance-coverage-levels");
90
120
 
@@ -94,6 +124,8 @@ describe("Toolcraft template decision contract", () => {
94
124
  expect(rule?.desiredBehavior).toMatch(/canvas drag, pan, pinch, zoom, and radar\/center/i);
95
125
  expect(rule?.desiredBehavior).toMatch(/playback state/i);
96
126
  expect(rule?.desiredBehavior).toMatch(/first working version/i);
127
+ expect(rule?.desiredBehavior).toMatch(/agent's controlled browser/i);
128
+ expect(rule?.desiredBehavior).toMatch(/Playwright is only the fallback runner/i);
97
129
  expect(rule?.desiredBehavior).toMatch(/optimize performance/i);
98
130
  expect(rule?.desiredBehavior).toMatch(/otherwise complains about performance/i);
99
131
  expect(rule?.desiredBehavior).toMatch(/targeted functional\/browser checks/i);
@@ -6,6 +6,7 @@ export type ToolcraftDecisionArea =
6
6
  | "panels"
7
7
  | "performance"
8
8
  | "persistence"
9
+ | "reference-analysis"
9
10
  | "reference-clone"
10
11
  | "renderer"
11
12
  | "runtime-shell"
@@ -139,10 +140,10 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
139
140
  {
140
141
  area: "timeline",
141
142
  currentConstraint:
142
- "Timeline is optional, but animated products must first classify animation intent as product transport, editable keyframes, custom reference timeline, or autonomous decorative output.",
143
+ "Timeline is optional only for animated products that have no video export and are explicitly classified as autonomous decorative output.",
143
144
  desiredBehavior:
144
- "AI writes an Animation Intent Inventory before choosing no timeline, playback, keyframes, or custom reference timeline; user-requested product animation defaults to playback unless explicitly justified as autonomous output.",
145
- enforcement: ["starter-agents", "spec-checklist"],
145
+ "AI writes an Animation Intent Inventory before choosing no timeline, playback, keyframes, or custom reference timeline; user-requested product animation defaults to playback unless explicitly justified as autonomous output without video export. Export Video always requires a top Toolcraft timeline.",
146
+ enforcement: ["starter-agents", "spec-checklist", "acceptance-validator"],
146
147
  id: "timeline-mode-choice",
147
148
  level: "heuristic",
148
149
  title: "Timeline mode is chosen from behavior",
@@ -211,15 +212,27 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
211
212
  {
212
213
  area: "reference-clone",
213
214
  currentConstraint:
214
- "Reference-runtime-clone mode preserves the reference runtime as source of truth unless a redesign is explicit.",
215
+ "Reference-runtime-clone mode preserves the reference runtime as source of truth unless a redesign is explicit; functionality must be inventoried from inspected reference behavior before implementation.",
215
216
  desiredBehavior:
216
- "Ported apps keep reference loops, mutable state, transport semantics, media lifecycle, and export behavior before Toolcraft refinements.",
217
+ "Ported apps keep reference loops, mutable state, transport semantics, media lifecycle, and export behavior before Toolcraft refinements. The agent records referenceStudy evidence from source inspection plus running the original or restoring it locally when runnable/reconstructable, builds a reference feature inventory from inspected source/runtime/UI behavior, gives each inventory item feature-level behavior evidence from that study, maps every user-visible and output-affecting feature to Toolcraft implementation and acceptance coverage, and marks intentional behavior changes only with explicit user approval evidence.",
217
218
  enforcement: ["acceptance-validator", "browser-helper", "starter-agents"],
218
219
  id: "reference-clone-source-of-truth",
219
220
  level: "invariant",
220
221
  title: "Reference clone preserves behavior",
221
222
  verdict: "keep-hard",
222
223
  },
224
+ {
225
+ area: "reference-analysis",
226
+ currentConstraint:
227
+ "Video, GIF, and screen-recording references are often treated as static visual inspiration or summarized from a few frames.",
228
+ desiredBehavior:
229
+ "Whenever a supplied reference is a video, GIF, screen recording, contact sheet, or extracted frame sequence, the agent studies it as behavioral evidence before implementation: extract or inspect a storyboard, record timecoded frame observations, compare frame-to-frame transitions, decompose changing entities and state into product behavior, and map those observed behaviors to acceptance rows. This applies to new Toolcraft apps and reference-runtime-clone work; it is independent from loop duration or timeline choice.",
230
+ enforcement: ["acceptance-validator", "browser-helper", "docs", "starter-agents"],
231
+ id: "video-reference-analysis",
232
+ level: "invariant",
233
+ title: "Video references require storyboard behavior study",
234
+ verdict: "move-to-validator",
235
+ },
223
236
  {
224
237
  area: "acceptance",
225
238
  currentConstraint:
@@ -237,7 +250,7 @@ export const TOOLCRAFT_DECISION_CONTRACT = [
237
250
  currentConstraint:
238
251
  "Performance coverage currently asks every visible non-action control for a performance scenario.",
239
252
  desiredBehavior:
240
- "Heavy workload controls get min/default/max workload coverage; ordinary controls get lightweight responsiveness coverage so they cannot hang or break input. Animated previews suspend or coalesce non-essential animation work during canvas drag, pan, pinch, zoom, and radar/center interactions without changing user playback state. A full performance checkpoint is required only when the first working version of an app exists, or whenever the user explicitly asks to optimize performance, fix lag, remove jank, speed up animation, stabilize drag/zoom, or otherwise complains about performance. Renderer, canvas, animation, export, timeline, layers, canvas.renderScale, bug fixes, and performance-sensitive controls need targeted functional/browser checks first and targeted performance scenarios only for touched workload/viewport/export paths. Performance fixes must preserve the selected render scale and must not pass budgets by silently downsampling, stretching a lower-resolution backing canvas, blurring output, or clamping canvas.renderScale below the user's chosen value. Browser performance tests read budgets from typed performance config and run sequentially for stable measurements.",
253
+ "Heavy workload controls get min/default/max workload coverage; ordinary controls get lightweight responsiveness coverage so they cannot hang or break input. Animated previews suspend or coalesce non-essential animation work during canvas drag, pan, pinch, zoom, and radar/center interactions without changing user playback state. A full performance checkpoint is required only when the first working version of an app exists, or whenever the user explicitly asks to optimize performance, fix lag, remove jank, speed up animation, stabilize drag/zoom, or otherwise complains about performance. The checkpoint uses the current AI agent's controlled browser first; Playwright is only the fallback runner when no agent browser is available or in CI/non-agent automation. Renderer, canvas, animation, export, timeline, layers, canvas.renderScale, bug fixes, and performance-sensitive controls need targeted functional/browser checks first and targeted performance scenarios only for touched workload/viewport/export paths. Performance fixes must preserve the selected render scale and must not pass budgets by silently downsampling, stretching a lower-resolution backing canvas, blurring output, or clamping canvas.renderScale below the user's chosen value. Browser performance checks read budgets from typed performance config and run sequentially for stable measurements.",
241
254
  enforcement: ["performance-validator", "browser-helper", "starter-agents"],
242
255
  id: "performance-coverage-levels",
243
256
  level: "invariant",