@pixel-point/toolcraft 0.0.16 → 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 (313) hide show
  1. package/package.json +1 -1
  2. package/src/generate-performance-iteration-execution.test.mjs +2 -2
  3. package/src/generate-test-demand-only-lifecycle.mjs +14 -3
  4. package/src/generate-test-dependency-sandbox.mjs +42 -5
  5. package/src/generate-test-doc-assertions.mjs +109 -69
  6. package/src/generate-test-doc-assertions.test.mjs +65 -0
  7. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  8. package/src/generate-test-file-assertions.mjs +36 -118
  9. package/src/generate-test-output-assertions.mjs +95 -0
  10. package/src/generate-test-package-assertions.mjs +6 -0
  11. package/src/generate-test-performance-boundary-assertions.mjs +45 -0
  12. package/src/generate-test-product-fixture.mjs +1 -0
  13. package/src/generate-test-product-fixture.test.mjs +18 -0
  14. package/src/generate-test-product-iteration.mjs +1 -0
  15. package/src/generate-test-product-iteration.test.mjs +18 -0
  16. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  17. package/src/generate-test-standalone-assertions.mjs +10 -0
  18. package/src/generate-test-standalone-delivery-stages.mjs +14 -6
  19. package/src/generate.test.mjs +14 -0
  20. package/templates/runtime/contracts/component-contracts.choices.test.ts +59 -12
  21. package/templates/runtime/contracts/component-contracts.choices.ts +58 -12
  22. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  23. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  24. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  25. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  26. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +21 -6
  27. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  28. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +4 -1
  29. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +4 -1
  30. package/templates/runtime/contracts/component-contracts.runtime.ts +15 -10
  31. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  32. package/templates/runtime/contracts/decision-contracts.test.ts +43 -4
  33. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  34. package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
  35. package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
  36. package/templates/runtime/export/artifact-download.test.ts +71 -0
  37. package/templates/runtime/export/artifact-download.ts +80 -0
  38. package/templates/runtime/export/artifact-export-request.ts +23 -0
  39. package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
  40. package/templates/runtime/export/artifact-export-settings.ts +96 -0
  41. package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
  42. package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
  43. package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
  44. package/templates/runtime/export/artifact-frame-state.ts +44 -0
  45. package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
  46. package/templates/runtime/export/artifact-scene-frame.ts +118 -0
  47. package/templates/runtime/export/export-background.ts +46 -0
  48. package/templates/runtime/export/export-error.ts +43 -0
  49. package/templates/runtime/export/export.test.ts +49 -226
  50. package/templates/runtime/export/image-artifact-export.test.ts +111 -0
  51. package/templates/runtime/export/image-artifact-export.ts +145 -0
  52. package/templates/runtime/export/index.ts +44 -3
  53. package/templates/runtime/export/product-export-renderer.test.ts +77 -0
  54. package/templates/runtime/export/product-export-renderer.ts +63 -0
  55. package/templates/runtime/export/video-artifact-export.test.ts +166 -0
  56. package/templates/runtime/export/video-artifact-export.ts +164 -0
  57. package/templates/runtime/export/video-encoding-backend.ts +124 -0
  58. package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
  59. package/templates/runtime/export/video-encoding-policy.ts +86 -0
  60. package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
  61. package/templates/runtime/export/video-frame-schedule.ts +42 -0
  62. package/templates/runtime/index.ts +3 -0
  63. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  64. package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
  65. package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
  66. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +15 -304
  67. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +41 -27
  68. package/templates/runtime/react/app-shell/toolcraft-app.tsx +39 -11
  69. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
  70. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
  71. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
  72. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
  73. package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
  74. package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
  75. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
  76. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
  77. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  78. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  79. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  80. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  81. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  82. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  83. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  84. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  85. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  86. package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
  87. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +18 -84
  88. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  89. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  90. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +43 -87
  91. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +68 -0
  92. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  93. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  94. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  95. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  96. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  97. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  98. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  99. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  100. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  101. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  102. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  103. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  104. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  105. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  106. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  107. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  108. package/templates/runtime/react/index.ts +4 -0
  109. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  110. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  111. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  112. package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
  113. package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
  114. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  115. package/templates/runtime/scene/scene-bounds.ts +32 -19
  116. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  117. package/templates/runtime/schema/canvas-render-scale.ts +62 -0
  118. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  119. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  120. package/templates/runtime/schema/control-applicability.ts +433 -0
  121. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  122. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  123. package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
  124. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  125. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +62 -3
  126. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  127. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  128. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  129. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  130. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  131. package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
  132. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  133. package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
  134. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  135. package/templates/runtime/schema/define-toolcraft.ts +1 -1
  136. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  137. package/templates/runtime/schema/schema-resolvers.ts +0 -56
  138. package/templates/runtime/schema/types.ts +72 -17
  139. package/templates/runtime/state/canvas-background-state.ts +6 -0
  140. package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
  141. package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
  142. package/templates/starter/AGENTS.md +18 -15
  143. package/templates/starter/NOTICE.md +6 -0
  144. package/templates/starter/docs/toolcraft/README.md +2 -1
  145. package/templates/starter/docs/toolcraft/acceptance-testing.md +23 -35
  146. package/templates/starter/docs/toolcraft/agent-worklog.md +18 -3
  147. package/templates/starter/docs/toolcraft/assembly-workflow.md +13 -5
  148. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  149. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  150. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  151. package/templates/starter/docs/toolcraft/core/media-upload.md +4 -2
  152. package/templates/starter/docs/toolcraft/core/performance.md +14 -3
  153. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +16 -3
  154. package/templates/starter/docs/toolcraft/core/setup-export.md +49 -19
  155. package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
  156. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  157. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  158. package/templates/starter/docs/toolcraft/performance.md +20 -6
  159. package/templates/starter/docs/toolcraft/renderer-technique.md +5 -1
  160. package/templates/starter/docs/toolcraft/schema-reference.md +23 -25
  161. package/templates/starter/docs/toolcraft/workflow.md +7 -2
  162. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  163. package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
  164. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +50 -3
  165. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  166. package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
  167. package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
  168. package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
  169. package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
  170. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +5 -34
  171. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -16
  172. package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
  173. package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
  174. package/templates/starter/e2e/app-performance.spec.ts +16 -9
  175. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  176. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  177. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +29 -341
  178. package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
  179. package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
  180. package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
  181. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  182. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  183. package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
  184. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  185. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +13 -3
  186. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +72 -35
  187. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  188. package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
  189. package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
  190. package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
  191. package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
  192. package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
  193. package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
  194. package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
  195. package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
  196. package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
  197. package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
  198. package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
  199. package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
  200. package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
  201. package/templates/starter/e2e/performance-path-helpers.ts +210 -91
  202. package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
  203. package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
  204. package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
  205. package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
  206. package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
  207. package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
  208. package/templates/starter/gitignore +1 -0
  209. package/templates/starter/package.json +1 -0
  210. package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
  211. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  212. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
  213. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
  214. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
  215. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +95 -103
  216. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  217. package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
  218. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -26
  219. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  220. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  221. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  222. package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
  223. package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
  224. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +157 -0
  225. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  226. package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
  227. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  228. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  229. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
  230. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +157 -206
  231. package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
  232. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
  233. package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
  234. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  235. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  236. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  237. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  238. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  239. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  240. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  241. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  242. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  243. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  244. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  245. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  246. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  247. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  248. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  249. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  250. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  251. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  252. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  253. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
  254. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  255. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  256. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  257. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  258. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  259. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  260. package/templates/starter/src/app/acceptance/output-export.ts +35 -12
  261. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  262. package/templates/starter/src/app/acceptance/types.ts +30 -8
  263. package/templates/starter/src/app/acceptance/validate-coverage.ts +20 -2
  264. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  265. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  266. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  267. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  268. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  269. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  270. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  271. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  272. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  273. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  274. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  275. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
  276. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  277. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  278. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  279. package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
  280. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  281. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  282. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  283. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +46 -1
  284. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  285. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  286. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  287. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  288. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  289. package/templates/starter/src/app/starter-acceptance.ts +13 -2
  290. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  291. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  292. package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
  293. package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
  294. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
  295. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +4 -2
  296. package/templates/starter/src/routes/index.tsx +2 -0
  297. package/templates/ui/components/composites/tabs.tsx +2 -2
  298. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  299. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  300. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  301. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  302. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  303. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  304. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  305. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  306. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  307. package/templates/ui/components/controls/index.ts +3 -0
  308. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  309. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  310. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  311. package/templates/runtime/export/export.ts +0 -166
  312. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  313. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
@@ -1,10 +1,29 @@
1
1
  import { getToolcraftSegmentedStaticFitError } from "../contracts/segmented-control-fit";
2
2
  import { normalizeToolcraftModelFileDrop } from "../model-import/model-import-limits";
3
+ import { isToolcraftCollectionItemControlType } from "./collection-item-controls";
4
+ import { resolveToolcraftControlApplicability } from "./control-applicability";
3
5
  import type {
4
6
  ResolvedToolcraftControlSchema,
7
+ ToolcraftModelFileDropSchema,
8
+ ToolcraftNonModelControlSchema,
9
+ ToolcraftResolvedControlApplicabilitySchema,
5
10
  ToolcraftControlSchema,
6
11
  } from "./types";
7
12
 
13
+ type ControlWithResolvedApplicability =
14
+ | (Omit<
15
+ ToolcraftNonModelControlSchema,
16
+ "applicability" | "visibleWhen"
17
+ > & {
18
+ applicability: ToolcraftResolvedControlApplicabilitySchema;
19
+ })
20
+ | (Omit<
21
+ ToolcraftModelFileDropSchema,
22
+ "applicability" | "visibleWhen"
23
+ > & {
24
+ applicability: ToolcraftResolvedControlApplicabilitySchema;
25
+ });
26
+
8
27
  const modelOnlyControlFieldNames = [
9
28
  "modelFormats",
10
29
  "modelLimits",
@@ -82,6 +101,117 @@ function assertFileDropHardMaxItems(control: ToolcraftControlSchema): void {
82
101
  }
83
102
  }
84
103
 
104
+ function assertFileDropVariant(control: ToolcraftControlSchema): void {
105
+ if (
106
+ control.type !== "fileDrop" ||
107
+ control.variant !== "collection-actions"
108
+ ) {
109
+ return;
110
+ }
111
+
112
+ if (control.assetKind !== "file" || control.multiple !== true) {
113
+ throw new Error(
114
+ 'Toolcraft fileDrop validation [filedrop-collection-actions]: variant "collection-actions" requires assetKind: "file" with multiple: true.',
115
+ );
116
+ }
117
+ }
118
+
119
+ type ItemControlsOwner = Readonly<{
120
+ minimumFields: number;
121
+ name: "collectionActions" | "fileDrop";
122
+ }>;
123
+
124
+ function getItemControlsOwner(
125
+ control: ToolcraftControlSchema,
126
+ ): ItemControlsOwner | null {
127
+ if (control.type === "collectionActions") {
128
+ return { minimumFields: 2, name: "collectionActions" };
129
+ }
130
+
131
+ if (
132
+ control.type === "fileDrop" &&
133
+ control.variant === "collection-actions" &&
134
+ control.assetKind === "file" &&
135
+ control.multiple === true
136
+ ) {
137
+ return { minimumFields: 1, name: "fileDrop" };
138
+ }
139
+
140
+ return null;
141
+ }
142
+
143
+ function isPlainRecord(value: unknown): boolean {
144
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
145
+ return false;
146
+ }
147
+
148
+ const prototype = Object.getPrototypeOf(value);
149
+ return prototype === Object.prototype || prototype === null;
150
+ }
151
+
152
+ function assertItemControls(control: ToolcraftControlSchema): void {
153
+ if (!control.itemControls) {
154
+ return;
155
+ }
156
+
157
+ const owner = getItemControlsOwner(control);
158
+
159
+ if (!owner) {
160
+ throw new Error(
161
+ `Toolcraft control validation [collection-item-controls] for target "${control.target}": itemControls require collectionActions or a multiple file-kind collection-actions fileDrop.`,
162
+ );
163
+ }
164
+
165
+ if (control.itemControl) {
166
+ throw new Error(
167
+ `Toolcraft control validation [collection-item-template] for target "${control.target}": itemControl and itemControls are mutually exclusive.`,
168
+ );
169
+ }
170
+
171
+ if (Object.prototype.hasOwnProperty.call(control, "itemDefaultValue")) {
172
+ throw new Error(
173
+ `Toolcraft control validation [collection-item-default] for target "${control.target}": itemDefaultValue cannot be used with itemControls; field defaults own the new record.`,
174
+ );
175
+ }
176
+
177
+ const entries = Object.entries(control.itemControls);
178
+ if (entries.length < owner.minimumFields) {
179
+ throw new Error(
180
+ `Toolcraft control validation [collection-item-controls] for target "${control.target}": ${owner.name} itemControls require at least ${owner.minimumFields === 1 ? "one" : "two"} built-in field${owner.minimumFields === 1 ? "" : "s"}.`,
181
+ );
182
+ }
183
+
184
+ for (const [id, itemControl] of entries) {
185
+ if (!id.trim() || !isToolcraftCollectionItemControlType(itemControl.type)) {
186
+ throw new Error(
187
+ `Toolcraft control validation [collection-item-controls] for target "${control.target}" itemControls.${id}: unsupported built-in field.`,
188
+ );
189
+ }
190
+ if (!Object.prototype.hasOwnProperty.call(itemControl, "defaultValue")) {
191
+ throw new Error(
192
+ `Toolcraft control validation [collection-item-controls] for target "${control.target}" itemControls.${id}: defaultValue is required.`,
193
+ );
194
+ }
195
+
196
+ const fitError = getToolcraftSegmentedStaticFitError(itemControl);
197
+ if (fitError) {
198
+ throw new Error(
199
+ `Toolcraft control validation [segmented-control-fit] for target "${control.target}" itemControls.${id}: ${fitError}`,
200
+ );
201
+ }
202
+ }
203
+
204
+ if (
205
+ owner.name === "collectionActions" &&
206
+ Array.isArray(control.defaultValue) &&
207
+ control.defaultValue.some((item) => !isPlainRecord(item))
208
+ ) {
209
+ throw new Error(
210
+ `Toolcraft control validation [collection-item-value] for target "${control.target}": every initial compound item must be a plain record.`,
211
+ );
212
+ }
213
+ }
214
+
85
215
  function assertSegmentedControlFit(control: ToolcraftControlSchema): void {
86
216
  const fitError = getToolcraftSegmentedStaticFitError(control);
87
217
 
@@ -92,13 +222,29 @@ function assertSegmentedControlFit(control: ToolcraftControlSchema): void {
92
222
  }
93
223
  }
94
224
 
95
- function assertCollectionItemSegmentedControlFit(
96
- control: ToolcraftControlSchema,
97
- ): void {
98
- if (control.type !== "collectionActions" || !control.itemControl) {
225
+ function assertCollectionItemControl(control: ToolcraftControlSchema): void {
226
+ if (
227
+ control.type !== "collectionActions" &&
228
+ control.type !== "sourceCollection"
229
+ ) {
230
+ return;
231
+ }
232
+
233
+ if (!control.itemControl) {
234
+ if (control.type === "sourceCollection") {
235
+ throw new Error(
236
+ `Toolcraft control validation [source-collection-item-control] for target "${control.target}": itemControl is required.`,
237
+ );
238
+ }
99
239
  return;
100
240
  }
101
241
 
242
+ if (!isToolcraftCollectionItemControlType(control.itemControl.type)) {
243
+ throw new Error(
244
+ `Toolcraft control validation [collection-item-control] for target "${control.target}": unsupported collection item control "${String(control.itemControl.type)}".`,
245
+ );
246
+ }
247
+
102
248
  const fitError = getToolcraftSegmentedStaticFitError(control.itemControl);
103
249
 
104
250
  if (fitError) {
@@ -108,11 +254,15 @@ function assertCollectionItemSegmentedControlFit(
108
254
  }
109
255
  }
110
256
 
111
- function isSliderLikeControl(control: ToolcraftControlSchema): boolean {
257
+ function isSliderLikeControl(
258
+ control: ControlWithResolvedApplicability,
259
+ ): boolean {
112
260
  return control.type === "slider" || control.type === "rangeSlider";
113
261
  }
114
262
 
115
- function getStepMarkerCount(control: ToolcraftControlSchema): number | undefined {
263
+ function getStepMarkerCount(
264
+ control: ControlWithResolvedApplicability,
265
+ ): number | undefined {
116
266
  if (
117
267
  typeof control.step !== "number" ||
118
268
  typeof control.min !== "number" ||
@@ -137,8 +287,8 @@ function getStepMarkerCount(control: ToolcraftControlSchema): number | undefined
137
287
  }
138
288
 
139
289
  function normalizeSliderControlSchema(
140
- control: ToolcraftControlSchema,
141
- ): ToolcraftControlSchema {
290
+ control: ControlWithResolvedApplicability,
291
+ ): ControlWithResolvedApplicability {
142
292
  if (
143
293
  !isSliderLikeControl(control) ||
144
294
  typeof control.step !== "number" ||
@@ -155,7 +305,7 @@ function normalizeSliderControlSchema(
155
305
  }
156
306
 
157
307
  function normalizeFileDropControlSchema(
158
- control: ToolcraftControlSchema,
308
+ control: ControlWithResolvedApplicability,
159
309
  ): ResolvedToolcraftControlSchema {
160
310
  if (control.assetKind === "model") {
161
311
  return normalizeToolcraftModelFileDrop(control);
@@ -180,10 +330,26 @@ function normalizeControlSchema(
180
330
  assertRequiredControlStringField(controlSnapshot, "target");
181
331
  assertControlRuntimeBoundary(controlSnapshot);
182
332
  assertFileDropHardMaxItems(controlSnapshot);
333
+ assertFileDropVariant(controlSnapshot);
334
+ assertItemControls(controlSnapshot);
183
335
  assertSegmentedControlFit(controlSnapshot);
184
- assertCollectionItemSegmentedControlFit(controlSnapshot);
336
+ assertCollectionItemControl(controlSnapshot);
337
+
338
+ const {
339
+ applicability: authoredApplicability,
340
+ visibleWhen,
341
+ ...controlWithoutApplicability
342
+ } = controlSnapshot;
343
+ const applicability = resolveToolcraftControlApplicability({
344
+ applicability: authoredApplicability,
345
+ visibleWhen,
346
+ });
347
+
185
348
  return normalizeFileDropControlSchema(
186
- normalizeSliderControlSchema(controlSnapshot),
349
+ normalizeSliderControlSchema({
350
+ ...controlWithoutApplicability,
351
+ applicability,
352
+ }),
187
353
  );
188
354
  }
189
355
 
@@ -7,15 +7,15 @@ import {
7
7
  import type {
8
8
  ResolvedToolcraftControlSchema,
9
9
  ResolvedToolcraftControlSectionSchema,
10
+ ResolvedToolcraftNonModelControlSchema,
10
11
  ToolcraftControlSchema,
11
- ToolcraftNonModelControlSchema,
12
12
  } from "./types";
13
13
 
14
14
  type ToolcraftControlActionSchema = NonNullable<
15
15
  ToolcraftControlSchema["actions"]
16
16
  >[number];
17
17
 
18
- type ToolcraftPanelActionsControl = ToolcraftNonModelControlSchema & {
18
+ type ToolcraftPanelActionsControl = ResolvedToolcraftNonModelControlSchema & {
19
19
  type: "panelActions";
20
20
  };
21
21
 
@@ -140,7 +140,7 @@ describe("Toolcraft control section identity", () => {
140
140
  )?.id,
141
141
  ).toBe(legacyId);
142
142
  expect(
143
- defineToolcraft(resolved as ToolcraftAppSchema).panels.controls?.sections.find(
143
+ defineToolcraft(resolved as unknown as ToolcraftAppSchema).panels.controls?.sections.find(
144
144
  (section) => section.id.startsWith("legacy."),
145
145
  )?.id,
146
146
  ).toBe(legacyId);
@@ -9,14 +9,16 @@ import {
9
9
  registerToolcraftInternalControlSection,
10
10
  } from "./controls-panel-section-id";
11
11
  import { withImplicitStandaloneSectionTitle } from "./controls-panel-section-titles";
12
+ import {
13
+ getToolcraftApplicabilityTargets,
14
+ } from "./control-applicability";
12
15
  import type {
13
16
  ResolvedToolcraftControlSchema,
14
17
  ResolvedToolcraftControlSectionSchema,
15
- ToolcraftControlSchema,
16
18
  } from "./types";
17
19
 
18
20
  function getControlDefaultSectionLayout(
19
- control: ToolcraftControlSchema,
21
+ control: ResolvedToolcraftControlSchema,
20
22
  ): "grouped" | "standalone" {
21
23
  const contract = (
22
24
  TOOLCRAFT_COMPONENT_CONTRACTS as Record<
@@ -31,12 +33,15 @@ function getControlDefaultSectionLayout(
31
33
  }
32
34
 
33
35
  function isControlGatedBySameSectionControl(
34
- control: ToolcraftControlSchema,
35
- entries: readonly [string, ToolcraftControlSchema][],
36
+ control: ResolvedToolcraftControlSchema,
37
+ entries: readonly [string, ResolvedToolcraftControlSchema][],
36
38
  ): boolean {
37
- const gateTargets = [control.visibleWhen?.target, control.disabledWhen?.target].filter(
38
- (target): target is string => Boolean(target),
39
- );
39
+ const gateTargets = [
40
+ ...getToolcraftApplicabilityTargets(
41
+ control.applicability,
42
+ ),
43
+ ...(control.disabledWhen ? [control.disabledWhen.target] : []),
44
+ ];
40
45
 
41
46
  if (gateTargets.length === 0) {
42
47
  return false;
@@ -49,8 +54,8 @@ function isControlGatedBySameSectionControl(
49
54
  }
50
55
 
51
56
  function getControlSectionLayout(
52
- control: ToolcraftControlSchema,
53
- entries: readonly [string, ToolcraftControlSchema][],
57
+ control: ResolvedToolcraftControlSchema,
58
+ entries: readonly [string, ResolvedToolcraftControlSchema][],
54
59
  ): "grouped" | "standalone" {
55
60
  if (isControlGatedBySameSectionControl(control, entries)) {
56
61
  return "grouped";
@@ -13,6 +13,65 @@ const opacityControl = {
13
13
  } as const;
14
14
 
15
15
  describe("Toolcraft control section identity roundtrips", () => {
16
+ it("preserves explicit, legacy, and implicit applicability origins", () => {
17
+ const resolved = defineToolcraft({
18
+ canvas: { enabled: true },
19
+ panels: {
20
+ controls: {
21
+ sections: [
22
+ {
23
+ controls: {
24
+ explicit: {
25
+ applicability: { mode: "always" },
26
+ target: "appearance.explicit",
27
+ type: "switch",
28
+ },
29
+ implicit: {
30
+ target: "appearance.implicit",
31
+ type: "switch",
32
+ },
33
+ legacy: {
34
+ target: "appearance.legacy",
35
+ type: "slider",
36
+ visibleWhen: {
37
+ equals: true,
38
+ target: "appearance.explicit",
39
+ },
40
+ },
41
+ },
42
+ id: "appearance",
43
+ title: "Appearance",
44
+ },
45
+ ],
46
+ title: "Controls",
47
+ },
48
+ },
49
+ });
50
+ const roundTripped = defineToolcraft(
51
+ resolved as unknown as ToolcraftAppSchema,
52
+ );
53
+ const readApplicability = (schema: typeof resolved) =>
54
+ Object.fromEntries(
55
+ schema.panels.controls?.sections
56
+ .filter((section) => section.id.startsWith("appearance"))
57
+ .flatMap((section) => Object.entries(section.controls))
58
+ .map(([id, control]) => [id, control.applicability]) ?? [],
59
+ );
60
+
61
+ expect(readApplicability(resolved)).toEqual({
62
+ explicit: { mode: "always", origin: "explicit" },
63
+ implicit: { mode: "always", origin: "implicit" },
64
+ legacy: {
65
+ all: [{ equals: true, target: "appearance.explicit" }],
66
+ mode: "conditional",
67
+ origin: "legacy",
68
+ },
69
+ });
70
+ expect(readApplicability(roundTripped)).toEqual(
71
+ readApplicability(resolved),
72
+ );
73
+ });
74
+
16
75
  it("round-trips resolved legacy and runtime section ids without duplicating runtime sections", () => {
17
76
  const resolved = defineToolcraft({
18
77
  canvas: { enabled: true },
@@ -38,7 +97,7 @@ describe("Toolcraft control section identity roundtrips", () => {
38
97
  },
39
98
  },
40
99
  });
41
- const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
100
+ const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
42
101
  const resolvedIds = resolved.panels.controls?.sections.map((section) => section.id);
43
102
  const roundTrippedIds = roundTripped.panels.controls?.sections.map(
44
103
  (section) => section.id,
@@ -101,7 +160,7 @@ describe("Toolcraft control section identity roundtrips", () => {
101
160
  const productIds = resolved.panels.controls?.sections
102
161
  .map((section) => section.id)
103
162
  .filter((id) => !id.startsWith("runtime."));
104
- const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
163
+ const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
105
164
  const roundTrippedProductIds = roundTripped.panels.controls?.sections
106
165
  .map((section) => section.id)
107
166
  .filter((id) => !id.startsWith("runtime."));
@@ -147,7 +206,7 @@ describe("Toolcraft control section identity roundtrips", () => {
147
206
  productIds?.every((id) => (id.match(/\.part-/gu) ?? []).length === 2),
148
207
  ).toBe(true);
149
208
 
150
- const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
209
+ const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
151
210
  const roundTrippedProductIds = roundTripped.panels.controls?.sections
152
211
  .map((section) => section.id)
153
212
  .filter((id) => !id.startsWith("runtime."));
@@ -0,0 +1,127 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { defineToolcraft } from "./define-toolcraft";
4
+ import type { ToolcraftControlSchema } from "./types";
5
+
6
+ function createSchema(control: ToolcraftControlSchema) {
7
+ return {
8
+ canvas: { enabled: false },
9
+ panels: {
10
+ controls: {
11
+ sections: [{ controls: { layers: control }, title: "Layers" }],
12
+ title: "Controls",
13
+ },
14
+ },
15
+ };
16
+ }
17
+
18
+ function compoundCollection(
19
+ overrides: Record<string, unknown> = {},
20
+ ): ToolcraftControlSchema {
21
+ return {
22
+ defaultValue: [{ invert: false, strength: 0.4 }],
23
+ itemControls: {
24
+ strength: {
25
+ defaultValue: 0.5,
26
+ label: "Strength",
27
+ max: 1,
28
+ min: 0,
29
+ step: 0.01,
30
+ type: "slider",
31
+ },
32
+ invert: {
33
+ defaultValue: false,
34
+ label: "Invert",
35
+ type: "switch",
36
+ },
37
+ },
38
+ itemLabel: "Layer",
39
+ minItems: 1,
40
+ target: "surface.layers",
41
+ type: "collectionActions",
42
+ ...overrides,
43
+ } as ToolcraftControlSchema;
44
+ }
45
+
46
+ describe("defineToolcraft compound collectionActions items", () => {
47
+ it("accepts one logical record made from several built-in fields", () => {
48
+ const app = defineToolcraft(createSchema(compoundCollection()));
49
+ const control = app.panels.controls?.sections.find(
50
+ (section) => section.title === "Layers",
51
+ )?.controls.layers;
52
+
53
+ expect(Object.keys(control?.itemControls ?? {})).toEqual([
54
+ "strength",
55
+ "invert",
56
+ ]);
57
+ });
58
+
59
+ it("rejects competing scalar and record templates", () => {
60
+ expect(() =>
61
+ defineToolcraft(
62
+ createSchema(
63
+ compoundCollection({
64
+ itemControl: { defaultValue: 0, type: "slider" },
65
+ }),
66
+ ),
67
+ ),
68
+ ).toThrow(/collection-item-template.*itemControl.*itemControls/u);
69
+ });
70
+
71
+ it("requires at least two fields in a compound collection item", () => {
72
+ expect(() =>
73
+ defineToolcraft(
74
+ createSchema(
75
+ compoundCollection({
76
+ itemControls: {
77
+ strength: { defaultValue: 0.5, type: "slider" },
78
+ },
79
+ }),
80
+ ),
81
+ ),
82
+ ).toThrow(/collection-item-controls.*at least two/u);
83
+ });
84
+
85
+ it("requires one default source per compound field", () => {
86
+ expect(() =>
87
+ defineToolcraft(
88
+ createSchema(
89
+ compoundCollection({
90
+ itemControls: {
91
+ invert: { defaultValue: false, type: "switch" },
92
+ strength: { type: "slider" },
93
+ },
94
+ }),
95
+ ),
96
+ ),
97
+ ).toThrow(/itemControls\.strength.*defaultValue/u);
98
+ });
99
+
100
+ it("rejects a competing itemDefaultValue", () => {
101
+ expect(() =>
102
+ defineToolcraft(
103
+ createSchema(
104
+ compoundCollection({
105
+ itemDefaultValue: { invert: true, strength: 1 },
106
+ }),
107
+ ),
108
+ ),
109
+ ).toThrow(/collection-item-default.*itemDefaultValue/u);
110
+ });
111
+
112
+ it("rejects non-record initial compound items", () => {
113
+ expect(() =>
114
+ defineToolcraft(
115
+ createSchema(compoundCollection({ defaultValue: [new Date()] })),
116
+ ),
117
+ ).toThrow(/collection-item-value.*plain record/u);
118
+ });
119
+
120
+ it("keeps sourceCollection single-field only", () => {
121
+ expect(() =>
122
+ defineToolcraft(
123
+ createSchema(compoundCollection({ type: "sourceCollection" })),
124
+ ),
125
+ ).toThrow(/collection-item-controls.*collectionActions.*fileDrop/u);
126
+ });
127
+ });
@@ -4,7 +4,7 @@ import { defineToolcraft } from "./define-toolcraft";
4
4
  import { getProductSections } from "./define-toolcraft-test-utils";
5
5
 
6
6
  describe("defineToolcraft control section semantics", () => {
7
- it("preserves conditional visibility on sections and controls", () => {
7
+ it("preserves section visibility and resolves legacy control visibility", () => {
8
8
  const app = defineToolcraft({
9
9
  canvas: { enabled: true },
10
10
  panels: {
@@ -38,9 +38,15 @@ describe("defineToolcraft control section semantics", () => {
38
38
  equals: "co-brand",
39
39
  target: "cover.templateId",
40
40
  });
41
- expect(section?.controls.partnerLogo?.visibleWhen).toEqual({
42
- equals: "logo",
43
- target: "coBrand.identityMode",
41
+ expect(section?.controls.partnerLogo?.applicability).toEqual({
42
+ all: [
43
+ {
44
+ equals: "logo",
45
+ target: "coBrand.identityMode",
46
+ },
47
+ ],
48
+ mode: "conditional",
49
+ origin: "legacy",
44
50
  });
45
51
  });
46
52
 
@@ -0,0 +1,98 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { defineToolcraft } from "./define-toolcraft";
4
+ import type { ToolcraftControlSchema } from "./types";
5
+
6
+ function createAppSchema(control: ToolcraftControlSchema) {
7
+ return {
8
+ canvas: { enabled: false },
9
+ panels: {
10
+ controls: {
11
+ sections: [
12
+ {
13
+ controls: { bumpMaps: control },
14
+ title: "Bump Maps",
15
+ },
16
+ ],
17
+ title: "Controls",
18
+ },
19
+ },
20
+ };
21
+ }
22
+
23
+ function createFileDrop(
24
+ overrides: Record<string, unknown> = {},
25
+ ): ToolcraftControlSchema {
26
+ return {
27
+ assetKind: "file",
28
+ defaultValue: [],
29
+ itemControls: {
30
+ depth: { defaultValue: 0.2, max: 1, min: 0, type: "slider" },
31
+ levels: {
32
+ defaultValue: [0, 1],
33
+ max: 1,
34
+ min: 0,
35
+ step: 0.01,
36
+ type: "rangeSlider",
37
+ },
38
+ invert: { defaultValue: false, type: "switch" },
39
+ offset: {
40
+ coordinateMode: "cartesian",
41
+ defaultValue: { x: "0", y: "0" },
42
+ type: "vector",
43
+ },
44
+ },
45
+ multiple: true,
46
+ target: "media.bumpMaps",
47
+ type: "fileDrop",
48
+ variant: "collection-actions",
49
+ ...overrides,
50
+ } as ToolcraftControlSchema;
51
+ }
52
+
53
+ describe("defineToolcraft fileDrop item controls", () => {
54
+ it("accepts built-in settings owned by each attached file", () => {
55
+ const app = defineToolcraft(createAppSchema(createFileDrop()));
56
+ const control = app.panels.controls?.sections.find(
57
+ (section) => section.title === "Bump Maps",
58
+ )?.controls.bumpMaps;
59
+
60
+ expect(control?.itemControls?.depth?.defaultValue).toBe(0.2);
61
+ expect(control?.itemControls?.levels?.type).toBe("rangeSlider");
62
+ expect(control?.itemControls?.offset?.type).toBe("vector");
63
+ });
64
+
65
+ it("requires the file collection-actions variant", () => {
66
+ expect(() =>
67
+ defineToolcraft(
68
+ createAppSchema(createFileDrop({ variant: undefined })),
69
+ ),
70
+ ).toThrow(/collection-item-controls.*collectionActions.*fileDrop/u);
71
+ });
72
+
73
+ it("requires every nested setting to declare a reset default", () => {
74
+ expect(() =>
75
+ defineToolcraft(
76
+ createAppSchema(
77
+ createFileDrop({ itemControls: { depth: { type: "slider" } } }),
78
+ ),
79
+ ),
80
+ ).toThrow(/itemControls\.depth.*defaultValue/u);
81
+ });
82
+
83
+ it("rejects an empty nested settings declaration", () => {
84
+ expect(() =>
85
+ defineToolcraft(createAppSchema(createFileDrop({ itemControls: {} }))),
86
+ ).toThrow(/itemControls require at least one built-in field/u);
87
+ });
88
+
89
+ it("rejects itemDefaultValue beside per-file field defaults", () => {
90
+ expect(() =>
91
+ defineToolcraft(
92
+ createAppSchema(
93
+ createFileDrop({ itemDefaultValue: { depth: 1 } }),
94
+ ),
95
+ ),
96
+ ).toThrow(/collection-item-default.*itemDefaultValue/u);
97
+ });
98
+ });