@pixel-point/toolcraft 0.0.17 → 0.0.18

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 (196) hide show
  1. package/package.json +1 -1
  2. package/src/generate-test-doc-assertions.mjs +58 -68
  3. package/src/generate-test-doc-assertions.test.mjs +43 -0
  4. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  5. package/src/generate-test-file-assertions.mjs +26 -75
  6. package/src/generate-test-product-iteration.test.mjs +17 -0
  7. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  8. package/src/generate-test-standalone-assertions.mjs +10 -0
  9. package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
  10. package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
  11. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  12. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  13. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  14. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  15. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
  16. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  17. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
  18. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
  19. package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
  20. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  21. package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
  22. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  23. package/templates/runtime/export/export-background.ts +0 -19
  24. package/templates/runtime/export/export.test.ts +45 -42
  25. package/templates/runtime/export/index.ts +44 -16
  26. package/templates/runtime/index.ts +2 -0
  27. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  28. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
  29. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
  30. package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
  31. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  32. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  33. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  34. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  35. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  36. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  37. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  38. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  39. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  40. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
  41. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  42. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  43. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
  44. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
  45. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  46. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  47. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  48. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  49. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  50. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  51. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  52. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  53. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  54. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  55. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  56. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  57. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  58. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  59. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  60. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  61. package/templates/runtime/react/index.ts +4 -0
  62. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  63. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  64. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  65. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  66. package/templates/runtime/scene/scene-bounds.ts +31 -17
  67. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  68. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  69. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  70. package/templates/runtime/schema/control-applicability.ts +433 -0
  71. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  72. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  73. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  74. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
  75. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  76. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  77. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  78. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  79. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  80. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  81. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  82. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  83. package/templates/runtime/schema/types.ts +68 -13
  84. package/templates/starter/AGENTS.md +12 -10
  85. package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
  86. package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
  87. package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
  88. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  89. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  90. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  91. package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
  92. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
  93. package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
  94. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  95. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  96. package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
  97. package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
  98. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  99. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
  100. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  101. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
  102. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
  103. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  104. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  105. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
  106. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  107. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  108. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  109. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
  110. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
  111. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  112. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  113. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
  114. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  115. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
  116. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  117. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  118. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  119. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
  120. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  121. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  122. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  123. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
  124. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  125. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  126. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  127. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  128. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  129. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  130. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  131. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  132. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  133. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  134. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  135. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  136. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  137. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  138. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  139. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  140. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  141. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  142. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  143. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  144. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  145. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  146. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  147. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  148. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  149. package/templates/starter/src/app/acceptance/output-export.ts +22 -10
  150. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  151. package/templates/starter/src/app/acceptance/types.ts +23 -8
  152. package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
  153. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  154. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  155. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  156. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  157. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  158. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  159. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  160. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  161. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  162. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  163. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  164. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  165. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  166. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  167. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  168. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  169. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  170. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
  171. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  172. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  173. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  174. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  175. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  176. package/templates/starter/src/app/starter-acceptance.ts +11 -2
  177. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  178. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  179. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
  180. package/templates/starter/src/routes/index.tsx +1 -0
  181. package/templates/ui/components/composites/tabs.tsx +2 -2
  182. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  183. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  184. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  185. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  186. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  187. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  188. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  189. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  190. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  191. package/templates/ui/components/controls/index.ts +3 -0
  192. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  193. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  194. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  195. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  196. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
@@ -152,6 +152,38 @@ describe("Toolcraft template component contracts: choices and actions", () => {
152
152
  expect(contract.aiUsageRules).toContain(
153
153
  "Use FontPicker as the collection item control when each repeated item is a typography/text-style entity; do not split its font, color, opacity, size, case, letter-spacing, or line-height into sibling collection fields.",
154
154
  );
155
+ expect(contract.aiUsageRules).toContain(
156
+ "Use itemControl when each repeated entity is one built-in value. Use itemControls only when two or more built-in fields describe one logical repeated product entity and are added or removed atomically.",
157
+ );
158
+ expect(contract.decisionCatalog?.layoutConstraints).toContain(
159
+ "Compound itemControls records render one line only between adjacent logical items, with no generated Item N headings; standalone color itemControl collections keep the compact two-column grid without item dividers.",
160
+ );
161
+ expect(contract.decisionCatalog?.requiredAcceptance).toContain(
162
+ "For itemControls, prove plus creates every declared default field in one runtime record, editing preserves sibling fields, and minus removes the whole final record from preview and export.",
163
+ );
164
+ });
165
+
166
+ it("documents SourceCollection as source-cardinality built-in item controls", () => {
167
+ const contract = getToolcraftComponentContract("sourceCollection");
168
+
169
+ expect(contract.stateMode).toBe("controlled");
170
+ expect(contract.visualComponent).toBe("ControlsPanelCollectionItems");
171
+ expect(contract.decisionCatalog?.strictness).toBe("exact-owner");
172
+ expect(contract.decisionCatalog?.ownsValueModel).toContain(
173
+ "source-owned collection cardinality",
174
+ );
175
+ expect(contract.decisionCatalog?.useWhen.join(" ")).toMatch(
176
+ /loaded|derived|detected/i,
177
+ );
178
+ expect(contract.decisionCatalog?.doNotReplaceWith?.join(" ")).toMatch(
179
+ /custom control|copied|recreated/i,
180
+ );
181
+ expect(contract.decisionCatalog?.requiredAcceptance.join(" ")).toMatch(
182
+ /source item count.*canvas preview.*export/i,
183
+ );
184
+ expect(contract.aiUsageRules.join(" ")).toMatch(
185
+ /built-in itemControl.*must not render add or remove/i,
186
+ );
155
187
  });
156
188
 
157
189
  it("documents segmented controls as compact selector-only choices", () => {
@@ -228,13 +260,13 @@ describe("Toolcraft template component contracts: choices and actions", () => {
228
260
  "defineToolcraft hoists panelActions into the controls panel sticky footer automatically.",
229
261
  );
230
262
  expect(contract.aiUsageRules).toContain(
231
- "Product-output apps must always include export in panelActions.",
263
+ "Every product must declare productReadiness.exportIntent before authoring export panelActions or settings sections.",
232
264
  );
233
265
  expect(contract.aiUsageRules).toContain(
234
266
  '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.',
235
267
  );
236
268
  expect(contract.aiUsageRules).toContain(
237
- "Static or still-output apps include Export PNG as the primary footer action.",
269
+ 'Image export is the Toolcraft product default. Keep Export PNG unless productReadiness.exportIntent.image is "user-removed" with non-empty explicit user-removal evidence.',
238
270
  );
239
271
  expect(contract.aiUsageRules).toContain(
240
272
  'Every app with Export PNG must expose a separate "Image Export" controls section.',
@@ -252,7 +284,10 @@ describe("Toolcraft template component contracts: choices and actions", () => {
252
284
  "Image Export resolution controls the actual exported image long edge: 2K = 2048px, 4K = 4096px, 8K = 8192px. Runtime resolves the selected value and protected browser acceptance proves decoded image width and height.",
253
285
  );
254
286
  expect(contract.aiUsageRules).toContain(
255
- "Apps that deliver animation include Export Video as the primary footer action and Export PNG as a secondary footer action; timeline playback alone does not invent video delivery.",
287
+ 'Add Export Video only when productReadiness.exportIntent.video is "user-requested" with non-empty explicit user-request evidence.',
288
+ );
289
+ expect(contract.aiUsageRules).toContain(
290
+ 'Animation, playback, keyframes, and timeline presence never add Export Video when productReadiness.exportIntent.video is "not-requested".',
256
291
  );
257
292
  expect(contract.aiUsageRules).toContain(
258
293
  "Any app with Export Video must enable the top Toolcraft timeline; video duration, loop, and rendered timestamps come from runtime timeline state.",
@@ -263,6 +298,15 @@ describe("Toolcraft template component contracts: choices and actions", () => {
263
298
  expect(contract.aiUsageRules).toContain(
264
299
  'Apps with both Export PNG and Export Video must expose both "Image Export" and "Video Export"; Image Export sits immediately before Video Export.',
265
300
  );
301
+ expect(contract.aiUsageRules).toContain(
302
+ 'Image-only apps place "Image Export" directly above sticky footer panelActions.',
303
+ );
304
+ expect(contract.aiUsageRules).toContain(
305
+ 'Video-only apps require explicit image user-removal evidence and place "Video Export" directly above sticky footer panelActions.',
306
+ );
307
+ expect(contract.aiUsageRules).toContain(
308
+ 'Explicit no-export apps require image user-removal evidence with video "not-requested" and omit export actions and settings sections.',
309
+ );
266
310
  expect(contract.aiUsageRules).toContain(
267
311
  'The Video Export section must include format and resolution controls such as targets "export.video.format" and "export.video.resolution".',
268
312
  );
@@ -264,6 +264,7 @@ export const TOOLCRAFT_CHOICE_COMPONENT_CONTRACTS = {
264
264
  "CollectionActions sits at the start of its section, renders the collection label on the left, and keeps remove/add icon buttons together on the right.",
265
265
  "Homogeneous repeated item controls do not render visible per-item labels when the collection label already names the group.",
266
266
  "Collection item controls follow normal density rules: plain color items use equal 50% columns when they fit, while color+opacity items stay stacked.",
267
+ "Compound itemControls records render one line only between adjacent logical items, with no generated Item N headings; standalone color itemControl collections keep the compact two-column grid without item dividers.",
267
268
  "CollectionActions is a compound control and follows content-width compound divider rules when sharing a section with sibling controls.",
268
269
  "recommendedMaxItems is an agent/layout/performance hint, not a hard add limit; hardMaxItems is allowed only for real algorithm, format, API, export, or proven performance limits.",
269
270
  ],
@@ -271,6 +272,7 @@ export const TOOLCRAFT_CHOICE_COMPONENT_CONTRACTS = {
271
272
  "Prove plus adds a runtime item and that the new item appears in or affects canvas preview and export.",
272
273
  "Prove minus removes a runtime item and that the removed item disappears from or stops affecting canvas preview and export.",
273
274
  "Prove minItems prevents deleting below the minimum and recommendedMaxItems does not silently block adding more items.",
275
+ "For itemControls, prove plus creates every declared default field in one runtime record, editing preserves sibling fields, and minus removes the whole final record from preview and export.",
274
276
  ],
275
277
  }),
276
278
  stateMode: "controlled",
@@ -280,12 +282,51 @@ export const TOOLCRAFT_CHOICE_COMPONENT_CONTRACTS = {
280
282
  "Do not model add/remove item behavior with a Slider count when users need to edit the actual items.",
281
283
  "recommendedMaxItems is advisory only and must not disable the plus button. Use hardMaxItems only when a real product, algorithm, API, export, or measured performance limit requires it.",
282
284
  "CollectionActions item controls use built-in controls whenever possible, such as Color, ColorOpacity, TextInput, Select, Segmented, Slider, Switch, Checkbox, RangeInput, or FontPicker.",
285
+ "Use itemControl when each repeated entity is one built-in value. Use itemControls only when two or more built-in fields describe one logical repeated product entity and are added or removed atomically.",
286
+ "Every itemControls field must belong to the same repeated product entity, share one parent array record, and affect that entity's product outcome; never group unrelated settings because they happen to use add/remove UI.",
283
287
  "Use FontPicker as the collection item control when each repeated item is a typography/text-style entity; do not split its font, color, opacity, size, case, letter-spacing, or line-height into sibling collection fields.",
284
288
  "Do not add visible labels like Color 1, Color 2, Item 1, or Item 2 for homogeneous collection items when the collection label already explains the group.",
285
289
  "Use compact half-width item layout whenever the child control is allowed to fit in a half row; color items without opacity are the default two-column case.",
286
290
  "Acceptance must add and remove items through the browser UI and prove canvas/export output follows the changed collection.",
287
291
  ],
288
292
  },
293
+ sourceCollection: {
294
+ ...control(
295
+ "sourceCollection",
296
+ "ControlsPanelCollectionItems",
297
+ "standalone",
298
+ "component-owned",
299
+ ),
300
+ decisionCatalog: decisionCatalog({
301
+ strictness: "exact-owner",
302
+ ownsValueModel: [
303
+ "source-owned collection cardinality",
304
+ "source-defined repeatable product entity collection",
305
+ "source-defined dynamic list of visible controls",
306
+ ],
307
+ useWhen: [
308
+ "Use SourceCollection when a loaded, derived, or detected source owns the item array length and the user edits only existing item values.",
309
+ ],
310
+ doNotReplaceWith: [
311
+ "Do not use CollectionActions when the user must not add or remove source-defined items.",
312
+ "Do not rebuild, copy, or recreate the repeated built-in item control in a custom control renderer.",
313
+ ],
314
+ acceptableAlternatives: [
315
+ "Use CollectionActions when the user owns add/remove cardinality.",
316
+ ],
317
+ layoutConstraints: [
318
+ "SourceCollection uses the same built-in item layout as CollectionActions and renders no add/remove header.",
319
+ ],
320
+ requiredAcceptance: [
321
+ "Prove the source item count, item editing, canvas preview, export output, and absence of add/remove commands.",
322
+ ],
323
+ }),
324
+ aiUsageRules: [
325
+ "Use SourceCollection for externally sized arrays of built-in item controls.",
326
+ "Declare a built-in itemControl; SourceCollection must not render add or remove commands.",
327
+ "The source workflow writes the complete target array; the panel edits item values only.",
328
+ ],
329
+ },
289
330
  panelActions: {
290
331
  ...control("panelActions", "PanelActions", "standalone", "component-owned"),
291
332
  decisionCatalog: decisionCatalog({
@@ -323,18 +364,22 @@ export const TOOLCRAFT_CHOICE_COMPONENT_CONTRACTS = {
323
364
  "Async non-export product actions such as Download, Copy, Generate, or Apply must return the real Promise from onPanelAction and report progress through the onPanelAction reportProgress callback.",
324
365
  "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.",
325
366
  "defineToolcraft hoists panelActions into the controls panel sticky footer automatically.",
326
- "Product-output apps must always include export in panelActions.",
367
+ "Every product must declare productReadiness.exportIntent before authoring export panelActions or settings sections.",
327
368
  '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.',
328
- "Static or still-output apps include Export PNG as the primary footer action.",
369
+ 'Image export is the Toolcraft product default. Keep Export PNG unless productReadiness.exportIntent.image is "user-removed" with non-empty explicit user-removal evidence.',
329
370
  'Every app with Export PNG must expose a separate "Image Export" controls section.',
330
371
  'The Image Export section must include "export.image.format" as a Select control with PNG and JPG choices, defaulting to "png".',
331
372
  'The Image Export section must include "export.image.resolution" as a Select control with 2K, 4K, and 8K choices, defaulting to "4k".',
332
373
  "Image Export format and resolution render as one compact two-column inline Select pair, matching the Video Export settings structure.",
333
374
  "Image Export resolution controls the actual exported image long edge: 2K = 2048px, 4K = 4096px, 8K = 8192px. Runtime resolves the selected value and protected browser acceptance proves decoded image width and height.",
334
- "Apps that deliver animation include Export Video as the primary footer action and Export PNG as a secondary footer action; timeline playback alone does not invent video delivery.",
375
+ 'Add Export Video only when productReadiness.exportIntent.video is "user-requested" with non-empty explicit user-request evidence.',
376
+ 'Animation, playback, keyframes, and timeline presence never add Export Video when productReadiness.exportIntent.video is "not-requested".',
335
377
  "Any app with Export Video must enable the top Toolcraft timeline; video duration, loop, and rendered timestamps come from runtime timeline state.",
336
378
  'Apps with Export Video must expose a separate "Video Export" controls section.',
337
379
  'Apps with both Export PNG and Export Video must expose both "Image Export" and "Video Export"; Image Export sits immediately before Video Export.',
380
+ 'Image-only apps place "Image Export" directly above sticky footer panelActions.',
381
+ 'Video-only apps require explicit image user-removal evidence and place "Video Export" directly above sticky footer panelActions.',
382
+ 'Explicit no-export apps require image user-removal evidence with video "not-requested" and omit export actions and settings sections.',
338
383
  'The Video Export section must include format and resolution controls such as targets "export.video.format" and "export.video.resolution".',
339
384
  "Use Select controls for Video Export format and resolution; do not use Segmented unless the product has a deliberately tiny fixed output menu and browser tests prove every cell keeps padding.",
340
385
  'Place the Video Export section as the final controls section directly above sticky footer panelActions.',
@@ -77,13 +77,13 @@ describe("Toolcraft template component contracts: inputs", () => {
77
77
  "Visual discrete sliders must still drag smoothly; their canonical performance path adapter should use dragToolcraftSliderByTarget for real pointer drag and let the central path profile own the budget.",
78
78
  );
79
79
  expect(slider.aiUsageRules).toContain(
80
- "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.",
80
+ 'Every product slider declares applicability as mode: "always" or mode: "conditional"; omitted applicability and legacy visibleWhen are runtime compatibility only.',
81
81
  );
82
82
  expect(slider.aiUsageRules).toContain(
83
- "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.",
83
+ "Use conditional applicability for sliders that are meaningful only in some mode/type/source/include/count states; every predicate must match and inactive branches disappear.",
84
84
  );
85
85
  expect(slider.aiUsageRules).toContain(
86
- "Do not leave an inactive conditional slider visible while making the renderer ignore it; hide it with visibleWhen.",
86
+ "Do not use schema disabled: true or disabledWhen for product sliders, and do not leave a visible slider in a branch where its value has no product effect.",
87
87
  );
88
88
  expect(rangeSlider.decisionCatalog.requiredAcceptance).toContain(
89
89
  "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.",
@@ -140,13 +140,13 @@ describe("Toolcraft template component contracts: inputs", () => {
140
140
  "Visual discrete sliders must still drag smoothly; their canonical performance path adapter should use dragToolcraftSliderByTarget for real pointer drag and let the central path profile own the budget.",
141
141
  );
142
142
  expect(rangeSlider.aiUsageRules).toContain(
143
- "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.",
143
+ 'Every product range slider declares applicability as mode: "always" or mode: "conditional"; omitted applicability and legacy visibleWhen are runtime compatibility only.',
144
144
  );
145
145
  expect(rangeSlider.aiUsageRules).toContain(
146
- "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.",
146
+ "Use conditional applicability for range sliders that are meaningful only in some mode/type/source/include/count states; every predicate must match and inactive branches disappear.",
147
147
  );
148
148
  expect(rangeSlider.aiUsageRules).toContain(
149
- "Do not leave an inactive conditional range slider visible while making the renderer ignore it; hide it with visibleWhen.",
149
+ "Do not use schema disabled: true or disabledWhen for product range sliders, and do not leave a visible range slider in a branch where its value has no product effect.",
150
150
  );
151
151
  expect(rangeSlider.aiUsageRules).toContain(
152
152
  "Acceptance must prove both rangeSlider.lower and rangeSlider.upper change the product output; testing one handle is not enough.",
@@ -85,9 +85,9 @@ export const TOOLCRAFT_INPUT_COMPONENT_CONTRACTS = {
85
85
  'Specs, plans, and app-schema tests must assert explicit discrete sliders render as variant: "discrete" with markers derived from min, max, and step.',
86
86
  'Browser verification can inspect [data-slot="slider"][data-variant="discrete"] plus slider markers to prove the Toolcraft component variant rendered.',
87
87
  "Visual discrete sliders must still drag smoothly; their canonical performance path adapter should use dragToolcraftSliderByTarget for real pointer drag and let the central path profile own the budget.",
88
- "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.",
89
- "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.",
90
- "Do not leave an inactive conditional slider visible while making the renderer ignore it; hide it with visibleWhen.",
88
+ 'Every product slider declares applicability as mode: "always" or mode: "conditional"; omitted applicability and legacy visibleWhen are runtime compatibility only.',
89
+ "Use conditional applicability for sliders that are meaningful only in some mode/type/source/include/count states; every predicate must match and inactive branches disappear.",
90
+ "Do not use schema disabled: true or disabledWhen for product sliders, and do not leave a visible slider in a branch where its value has no product effect.",
91
91
  ],
92
92
  },
93
93
  rangeSlider: {
@@ -137,9 +137,9 @@ export const TOOLCRAFT_INPUT_COMPONENT_CONTRACTS = {
137
137
  "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.",
138
138
  'Specs, plans, and app-schema tests must assert explicit discrete range sliders render as variant: "discrete" with markers derived from min, max, and step.',
139
139
  "Visual discrete sliders must still drag smoothly; their canonical performance path adapter should use dragToolcraftSliderByTarget for real pointer drag and let the central path profile own the budget.",
140
- "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.",
141
- "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.",
142
- "Do not leave an inactive conditional range slider visible while making the renderer ignore it; hide it with visibleWhen.",
140
+ 'Every product range slider declares applicability as mode: "always" or mode: "conditional"; omitted applicability and legacy visibleWhen are runtime compatibility only.',
141
+ "Use conditional applicability for range sliders that are meaningful only in some mode/type/source/include/count states; every predicate must match and inactive branches disappear.",
142
+ "Do not use schema disabled: true or disabledWhen for product range sliders, and do not leave a visible range slider in a branch where its value has no product effect.",
143
143
  "Acceptance must prove both rangeSlider.lower and rangeSlider.upper change the product output; testing one handle is not enough.",
144
144
  ],
145
145
  },
@@ -14,13 +14,13 @@ describe("Toolcraft template component contracts: media and custom controls", ()
14
14
  "Every visible custom-control element must justify its space by enabling selection, ordering, preview, removal, upload, editing, or status that affects the product.",
15
15
  );
16
16
  expect(contract.aiUsageRules).toContain(
17
- "Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, CollectionActions, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
17
+ "Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, CollectionActions, SourceCollection, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
18
18
  );
19
19
  expect(contract.aiUsageRules).toContain(
20
20
  "Every custom-control builtInFitCheck declares typed capabilities. Use collection, reorder, selection, commands, custom-interaction, custom-value-model, and custom-visualization to describe behavior without relying on product nouns.",
21
21
  );
22
22
  expect(contract.aiUsageRules).toContain(
23
- "When a custom control owns a growable, removable, selectable, or reorderable runtime item set, its builtInFitCheck must explicitly check collectionActions and actions before choosing custom; this is based on the value model and user workflow, not on entity names such as masks or glyphs.",
23
+ "When a custom control owns a repeated runtime item set, its builtInFitCheck must explicitly check sourceCollection, collectionActions, and actions before choosing custom; this is based on source versus user cardinality and the value workflow, not on entity names such as masks or glyphs.",
24
24
  );
25
25
  expect(contract.aiUsageRules).toContain(
26
26
  "Do not justify custom controls with icons, layout, styling, compactness, or custom buttons alone. The fit check must name the product interaction or value model that built-ins cannot express.",
@@ -107,6 +107,12 @@ describe("Toolcraft template component contracts: media and custom controls", ()
107
107
  expect(contract.aiUsageRules).toContain(
108
108
  "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.",
109
109
  );
110
+ expect(contract.aiUsageRules).toContain(
111
+ 'Use fileDrop variant: "collection-actions" only with assetKind: "file" and multiple: true when the user explicitly wants collectionActions-style compact − / + cardinality. The variant keeps runtime media order, uses + to add an empty upload slot, and uses − to remove the final slot or attached file.',
112
+ );
113
+ expect(contract.aiUsageRules).toContain(
114
+ "Each collection-actions FileDrop slot and its itemControls settings form one logical item group; runtime renders one line only between adjacent groups.",
115
+ );
110
116
  expect(contract.aiUsageRules).toContain(
111
117
  "fileDrop recommendedMaxItems is advisory and never rejects an import. A finite nonnegative safe-integer hardMaxItems is an actual admission limit: additive imports count existing assets for the same source target plus the incoming logical batch, replacement imports count only the replacement batch, and overflow is rejected before decoding or repository allocation.",
112
118
  );
@@ -29,6 +29,9 @@ export const TOOLCRAFT_MEDIA_CUSTOM_COMPONENT_CONTRACTS = {
29
29
  "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.",
30
30
  "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.",
31
31
  "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.",
32
+ 'A multiple file-kind fileDrop may use variant: "collection-actions" when collection cardinality should use the same compact − / + header as collectionActions. In that variant + adds one empty FileDrop slot and − removes the final slot or its attached file.',
33
+ "A collection-actions fileDrop may declare itemControls so every attached file renders its own built-in settings immediately below its upload row. The parent target stores records keyed by mediaId while binary lifecycle remains in runtime media state.",
34
+ "Each collection-actions FileDrop slot and its itemControls settings form one logical item group; runtime renders one line only between adjacent groups.",
32
35
  "When fileDrop has assetKind: model, standard model preview remains runtime-owned even when renderDefaultCanvasMedia is false; custom presentation is selected only through ToolcraftAppComposition modelPresentation.",
33
36
  ],
34
37
  requiredAcceptance: [
@@ -56,6 +59,9 @@ export const TOOLCRAFT_MEDIA_CUSTOM_COMPONENT_CONTRACTS = {
56
59
  "In file mode, the runtime shows uploaded files as a sortable list with paperclip icons, file names, remove buttons, and --border/5 separators.",
57
60
  "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.",
58
61
  "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.",
62
+ 'Use fileDrop variant: "collection-actions" only with assetKind: "file" and multiple: true when the user explicitly wants collectionActions-style compact − / + cardinality. The variant keeps runtime media order, uses + to add an empty upload slot, and uses − to remove the final slot or attached file.',
63
+ "Use fileDrop itemControls when settings belong to individual attached files. Each nested control declares a defaultValue; only attached slots render settings, and the runtime commits a parent-target array keyed by mediaId for reset, undo, and persistence.",
64
+ "Each collection-actions FileDrop slot and its itemControls settings form one logical item group; runtime renders one line only between adjacent groups.",
59
65
  "fileDrop recommendedMaxItems is advisory and never rejects an import. A finite nonnegative safe-integer hardMaxItems is an actual admission limit: additive imports count existing assets for the same source target plus the incoming logical batch, replacement imports count only the replacement batch, and overflow is rejected before decoding or repository allocation.",
60
66
  "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.",
61
67
  "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.",
@@ -134,9 +140,9 @@ export const TOOLCRAFT_MEDIA_CUSTOM_COMPONENT_CONTRACTS = {
134
140
  }),
135
141
  aiUsageRules: [
136
142
  "Use custom controls only for product interactions that built-in controls cannot express.",
137
- "Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, CollectionActions, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
143
+ "Do not use a custom control to recreate a built-in Slider, RangeSlider, Select, Segmented, Switch, Checkbox, Color, ColorOpacity, Gradient, FontPicker, ImagePicker, FileDrop, TextInput, CodeTextarea, RangeInput, Palette, Actions, CollectionActions, SourceCollection, Curves, AnchorGrid, ChannelMixer, Vector, or PanelActions control.",
138
144
  "Every custom-control builtInFitCheck declares typed capabilities. Use collection, reorder, selection, commands, custom-interaction, custom-value-model, and custom-visualization to describe behavior without relying on product nouns.",
139
- "When a custom control owns a growable, removable, selectable, or reorderable runtime item set, its builtInFitCheck must explicitly check collectionActions and actions before choosing custom; this is based on the value model and user workflow, not on entity names such as masks or glyphs.",
145
+ "When a custom control owns a repeated runtime item set, its builtInFitCheck must explicitly check sourceCollection, collectionActions, and actions before choosing custom; this is based on source versus user cardinality and the value workflow, not on entity names such as masks or glyphs.",
140
146
  "Do not justify custom controls with icons, layout, styling, compactness, or custom buttons alone. The fit check must name the product interaction or value model that built-ins cannot express.",
141
147
  "Custom controls may use Toolcraft primitives for small app-specific chrome, but must not import or render low-level runtime surfaces or duplicate toolbar, timeline, layers, canvas, panel, or built-in control mechanics.",
142
148
  "Custom controls must render the minimum UI needed to understand the value, context, and available actions; avoid decorative metadata and text that repeats what the section, label, or visible item already explains.",
@@ -38,16 +38,16 @@ describe("Toolcraft template component contracts: runtime and composition", () =
38
38
  "Conditional entities require fixtures that make the condition observable.",
39
39
  );
40
40
  expect(contract.aiUsageRules).toContain(
41
- "Use visibleWhen for mode-, type-, source-, include-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
41
+ 'Every generated product control declares applicability as mode: "always" or mode: "conditional"; omitted applicability and legacy visibleWhen are runtime compatibility only and fail starter acceptance.',
42
42
  );
43
43
  expect(contract.aiUsageRules).toContain(
44
- "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.",
44
+ "Conditional applicability combines every predicate with AND, hides controls outside mode/type/source/include/variant/count branches, and preserves their runtime values while hidden.",
45
45
  );
46
46
  expect(contract.aiUsageRules).toContain(
47
- "If a switch/select/segmented/tabs/imagePicker/checkbox chooses a branch for the same product entity, controls outside the current branch use visibleWhen, not disabledWhen.",
47
+ "A visible product control must prove its existing product outcome in every supported finite sibling-selector branch from its Control Section Inventory, including always controls and selectors omitted from its predicates.",
48
48
  );
49
49
  expect(contract.aiUsageRules).toContain(
50
- "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.",
50
+ "Do not use schema disabled: true or disabledWhen for generated product availability; runtime primitives may use disabled styling internally, but product controls outside the active branch are absent.",
51
51
  );
52
52
  expect(contract.aiUsageRules).toContain(
53
53
  "Do not leave inactive conditional controls visible while making the renderer ignore them.",
@@ -24,7 +24,13 @@ describe("Toolcraft template component contracts: runtime and composition", () =
24
24
  "Acceptance validators suggest semantic replacement labels for weak generic labels; fix the schema label instead of relying on runtime fallback rewriting.",
25
25
  );
26
26
  expect(contract.aiUsageRules).toContain(
27
- "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.",
27
+ "One to seven controls is the normal section size. Eight to ten controls are allowed only for one cohesive entity and require semanticGroup on every control; ten is the hard maximum.",
28
+ );
29
+ expect(contract.aiUsageRules).toContain(
30
+ "Every Control Section Inventory entry declares required entityId, entity, exact targets, and groupingReason. One entity with ten or fewer controls stays in one section regardless of control type, visual height, or target namespace.",
31
+ );
32
+ expect(contract.aiUsageRules).toContain(
33
+ "An entity above ten controls splits into balanced workflow sections of two to ten controls. Every split section keeps the same entityId and entity and declares a unique workflowStage plus splitReason.",
28
34
  );
29
35
  expect(contract.aiUsageRules).toContain(
30
36
  "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.",
@@ -61,6 +61,9 @@ describe("Toolcraft template component contracts: runtime and composition", () =
61
61
  expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
62
62
  '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.',
63
63
  );
64
+ expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
65
+ 'Export Video is valid only when productReadiness.exportIntent.video is "user-requested" with non-empty explicit user-request evidence; animation, playback, keyframes, and timeline presence never authorize video export.',
66
+ );
64
67
  expect(TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel.aiUsageRules).toContain(
65
68
  "Video export frames are rendered deterministically from the runtime-owned 30 FPS timeline schedule; product code must not use autonomous wall-clock time, captureStream, or MediaRecorder as the source or fallback for product duration.",
66
69
  );
@@ -60,6 +60,9 @@ describe("Toolcraft template component contracts: runtime and composition", () =
60
60
  expect(contract.aiUsageRules).toContain(
61
61
  "Infinite exports use canonical world-space scene frames: ToolcraftAppComposition.sceneBoundsProvider contributes exact-state product bounds, runtime image and model bounds are unioned, hidden or suppressed content is excluded, PNG crops tightly, and runtime video unions every scheduled frame state into one envelope.",
62
62
  );
63
+ expect(contract.aiUsageRules).toContain(
64
+ "Infinite live preview uses that same exact-state product frame: runtime positions one product scene surface from sceneBoundsProvider, and custom raster/WebGL output consumes useToolcraftProductSceneFrame for backing size and world-to-local translation instead of dormant canvas.size.",
65
+ );
63
66
  expect(contract.aiUsageRules).toContain(
64
67
  "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.",
65
68
  );
@@ -27,6 +27,7 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
27
27
  "Infinity canvas suppresses the bounded product-rendered preview background so the dormant finite output does not appear as a second canvas; when the standard Background pair is enabled, CanvasShell fills the complete infinite viewport with the selected Background color.",
28
28
  "When the standard Background pair exists, disabling Background atomically restores finite mode and disables Infinity canvas; re-enabling Background restores switch availability without enabling Infinity automatically.",
29
29
  "Infinite exports use canonical world-space scene frames: ToolcraftAppComposition.sceneBoundsProvider contributes exact-state product bounds, runtime image and model bounds are unioned, hidden or suppressed content is excluded, PNG crops tightly, and runtime video unions every scheduled frame state into one envelope.",
30
+ "Infinite live preview uses that same exact-state product frame: runtime positions one product scene surface from sceneBoundsProvider, and custom raster/WebGL output consumes useToolcraftProductSceneFrame for backing size and world-to-local translation instead of dormant canvas.size.",
30
31
  "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.",
31
32
  "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.",
32
33
  "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.",
@@ -127,11 +128,12 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
127
128
  "A generic canvas hash difference is not enough for workload or semantic controls; assert the intended direction of the effect.",
128
129
  "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.",
129
130
  "Conditional entities require fixtures that make the condition observable.",
130
- "Use visibleWhen for mode-, type-, source-, include-, variant-, or count-exclusive sections or controls that do not belong to the current selected state.",
131
- "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.",
132
- "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.",
133
- "If a switch/select/segmented/tabs/imagePicker/checkbox chooses a branch for the same product entity, controls outside the current branch use visibleWhen, not disabledWhen.",
134
- "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.",
131
+ 'Every generated product control declares applicability as mode: "always" or mode: "conditional"; omitted applicability and legacy visibleWhen are runtime compatibility only and fail starter acceptance.',
132
+ "Conditional applicability combines every predicate with AND, hides controls outside mode/type/source/include/variant/count branches, and preserves their runtime values while hidden.",
133
+ "A visible product control must prove its existing product outcome in every supported finite sibling-selector branch from its Control Section Inventory, including always controls and selectors omitted from its predicates.",
134
+ "When applicability 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.",
135
+ "When a count/quantity control determines how many sibling controls are applicable, conditional applicability hides unavailable siblings; do not render all possible controls while the renderer reads only the first N.",
136
+ "Do not use schema disabled: true or disabledWhen for generated product availability; runtime primitives may use disabled styling internally, but product controls outside the active branch are absent.",
135
137
  "Do not leave inactive conditional controls visible while making the renderer ignore them.",
136
138
  "Before controls or canvas interactions, declare typed interactionOwnership for every operation that could plausibly live on either surface. User request, inspected reference, or product usability selects one primary owner.",
137
139
  "Do not mirror one operation across canvas and panel, even with different labels, ids, styling, or custom chrome. The same target and operation capability has one surface owner.",
@@ -238,9 +240,10 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
238
240
  "A concise property label such as Speed, Color, Size, or Opacity is allowed when the nearest visible section or group clearly names the affected product entity.",
239
241
  "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.",
240
242
  "Acceptance validators suggest semantic replacement labels for weak generic labels; fix the schema label instead of relying on runtime fallback rewriting.",
241
- "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.",
242
- "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.",
243
- "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.",
243
+ "One to seven controls is the normal section size. Eight to ten controls are allowed only for one cohesive entity and require semanticGroup on every control; ten is the hard maximum.",
244
+ "Every Control Section Inventory entry declares required entityId, entity, exact targets, and groupingReason. One entity with ten or fewer controls stays in one section regardless of control type, visual height, or target namespace.",
245
+ "An entity above ten controls splits into balanced workflow sections of two to ten controls. Every split section keeps the same entityId and entity and declares a unique workflowStage plus splitReason.",
246
+ "Section splitting must preserve dependency cohesion: a selector stays with the applicability-gated controls it owns 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.",
244
247
  "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.",
245
248
  "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.",
246
249
  "Controls-panel section expand and collapse uses the standard runtime height/opacity animation; generated apps must not replace it with instant custom section visibility.",
@@ -319,6 +322,7 @@ export const TOOLCRAFT_RUNTIME_COMPONENT_CONTRACTS = {
319
322
  "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.",
320
323
  '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.',
321
324
  '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.',
325
+ 'Export Video is valid only when productReadiness.exportIntent.video is "user-requested" with non-empty explicit user-request evidence; animation, playback, keyframes, and timeline presence never authorize video export.',
322
326
  '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.',
323
327
  '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.',
324
328
  "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.",
@@ -26,6 +26,7 @@ describe("Toolcraft template component contracts: core", () => {
26
26
  "checkbox",
27
27
  "actions",
28
28
  "collectionActions",
29
+ "sourceCollection",
29
30
  "panelActions",
30
31
  "colorOpacity",
31
32
  "palette",
@@ -72,6 +73,7 @@ describe("Toolcraft template component contracts: core", () => {
72
73
  "palette",
73
74
  "actions",
74
75
  "collectionActions",
76
+ "sourceCollection",
75
77
  "panelActions",
76
78
  "customControl",
77
79
  ] as const) {
@@ -54,6 +54,8 @@ describe("Toolcraft template decision contract", () => {
54
54
 
55
55
  expect(rule?.level).toBe("invariant");
56
56
  expect(rule?.desiredBehavior).toMatch(/sceneBoundsProvider/i);
57
+ expect(rule?.desiredBehavior).toMatch(/useToolcraftProductSceneFrame/i);
58
+ expect(rule?.desiredBehavior).toMatch(/live infinite preview/i);
57
59
  expect(rule?.desiredBehavior).toMatch(/one exact state/i);
58
60
  expect(rule?.desiredBehavior).toMatch(/runtime-owned timestamp schedule/i);
59
61
  expect(rule?.desiredBehavior).toMatch(/finite canvas size/i);
@@ -116,6 +118,22 @@ describe("Toolcraft template decision contract", () => {
116
118
  );
117
119
  });
118
120
 
121
+ it("requires artifact delivery to correspond exactly to explicit product export intent", () => {
122
+ const rule = getToolcraftDecisionRule("output-export-required");
123
+
124
+ expect(rule?.level).toBe("invariant");
125
+ expect(rule?.desiredBehavior).toMatch(/productReadiness\.exportIntent/);
126
+ expect(rule?.desiredBehavior).toMatch(/image[^.]*Toolcraft default/i);
127
+ expect(rule?.desiredBehavior).toMatch(/video[^.]*explicit user request[^.]*evidence/i);
128
+ expect(rule?.desiredBehavior).toMatch(
129
+ /animation, playback, keyframes, or timeline[^.]*never/i,
130
+ );
131
+ expect(rule?.desiredBehavior).toMatch(/image[^.]*removed[^.]*explicit user[^.]*evidence/i);
132
+ expect(rule?.desiredBehavior).toMatch(
133
+ /intent[^.]*correspond exactly[^.]*schema actions, settings sections, and acceptance coverage/i,
134
+ );
135
+ });
136
+
119
137
  it("makes renderer technology a default with escape hatches", () => {
120
138
  const rule = getToolcraftDecisionRule("renderer-technique-inventory");
121
139