@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
@@ -52,6 +52,7 @@ describe("controls-panel media renderer", () => {
52
52
  dispatchCommand,
53
53
  id: "source-model",
54
54
  mediaAssets: [],
55
+ name: "Model",
55
56
  sourceAssetCoordinator,
56
57
  });
57
58
 
@@ -102,6 +103,7 @@ describe("controls-panel media renderer", () => {
102
103
  dispatchCommand: vi.fn(),
103
104
  id: "source-files",
104
105
  mediaAssets: [],
106
+ name: "Source files",
105
107
  sourceAssetCoordinator,
106
108
  })}
107
109
  </>,
@@ -113,7 +115,75 @@ describe("controls-panel media renderer", () => {
113
115
  undefined,
114
116
  );
115
117
  expect(legacyFileDrop.mock.calls[0]?.[0]).toEqual(
116
- expect.objectContaining({ onFolderSelect: undefined }),
118
+ expect.objectContaining({
119
+ collectionActions: undefined,
120
+ onFolderSelect: undefined,
121
+ }),
122
+ );
123
+ });
124
+
125
+ it("maps the collection-actions variant to compact FileDrop cardinality controls", () => {
126
+ const control = {
127
+ assetKind: "file" as const,
128
+ itemLabel: "bump map",
129
+ multiple: true,
130
+ target: "surface.bumpMaps",
131
+ type: "fileDrop" as const,
132
+ variant: "collection-actions",
133
+ };
134
+ const operation = { phase: "idle" as const, target: control.target };
135
+ const presentation = {
136
+ accept: "image/*",
137
+ allowsFileBatch: true,
138
+ allowsFolderSelection: false,
139
+ emptyDescription: "Drop bump maps",
140
+ emptyTitle: "Upload bump maps",
141
+ items: [{
142
+ assetKind: "file" as const,
143
+ fileName: "grain.png",
144
+ id: "bump-grain",
145
+ }],
146
+ presenter: "file-list" as const,
147
+ secondaryActions: [],
148
+ };
149
+ const sourceAssetCoordinator = {
150
+ cancelTarget: vi.fn(),
151
+ clearPresentationFeedback: vi.fn(),
152
+ dispose: vi.fn(async () => undefined),
153
+ getOperation: vi.fn(() => operation),
154
+ getPresentation: vi.fn(() => presentation),
155
+ hydrateBinaryMedia: vi.fn(async () => undefined),
156
+ hydrateModels: vi.fn(async () => undefined),
157
+ importBatch: vi.fn(),
158
+ repairModel: vi.fn(),
159
+ reportPresentationFeedback: vi.fn(),
160
+ subscribe: () => () => undefined,
161
+ } satisfies ToolcraftSourceAssetCoordinator;
162
+
163
+ render(
164
+ <>
165
+ {renderFileDropControl({
166
+ canvasSize: { height: 720, unit: "px", width: 1280 },
167
+ control,
168
+ dispatchCommand: vi.fn(),
169
+ id: "surface-bump-maps",
170
+ mediaAssets: [],
171
+ name: "Bump maps",
172
+ sourceAssetCoordinator,
173
+ })}
174
+ </>,
175
+ );
176
+
177
+ expect(legacyFileDrop.mock.calls[0]?.[0]).toEqual(
178
+ expect.objectContaining({
179
+ collectionActions: {
180
+ addLabel: "Add bump map",
181
+ canAdd: true,
182
+ itemLabel: "bump map",
183
+ name: "Bump maps",
184
+ removeLabel: "Remove bump map",
185
+ },
186
+ }),
117
187
  );
118
188
  });
119
189
 
@@ -157,6 +227,7 @@ describe("controls-panel media renderer", () => {
157
227
  dispatchCommand: vi.fn(),
158
228
  id: "source-model",
159
229
  mediaAssets: [],
230
+ name: "Model",
160
231
  sourceAssetCoordinator,
161
232
  })}
162
233
  </>,
@@ -226,6 +297,7 @@ describe("controls-panel media renderer", () => {
226
297
  dispatchCommand: vi.fn(),
227
298
  id: "source-model",
228
299
  mediaAssets: [],
300
+ name: "Model",
229
301
  sourceAssetCoordinator,
230
302
  })}
231
303
  </>,
@@ -1,5 +1,9 @@
1
1
  import * as React from "react";
2
- import { FileDrop } from "@repo/ui";
2
+ import {
3
+ FileDrop,
4
+ type ControlChangeMeta,
5
+ type FileDropPresentationItem,
6
+ } from "@repo/ui";
3
7
 
4
8
  import type { ToolcraftCanvasSize, ToolcraftControlSchema } from "../../../schema/types";
5
9
  import type { ToolcraftSourceAssetCoordinator } from "../../../source-assets/source-asset-coordinator";
@@ -11,6 +15,7 @@ import type {
11
15
  } from "../../../state/types";
12
16
  import { useToolcraftMediaPresentationUrls } from "../../app-shell/toolcraft-source-asset-context";
13
17
  import { useToolcraftSelector } from "../../app-shell/use-toolcraft";
18
+ import { ControlsPanelFileDropItemControls } from "./controls-panel-file-drop-item-controls";
14
19
 
15
20
  export type FileDropControlRenderArgs = {
16
21
  canvasSize: ToolcraftCanvasSize;
@@ -19,7 +24,15 @@ export type FileDropControlRenderArgs = {
19
24
  id: string;
20
25
  layerSelectionEnabled?: boolean;
21
26
  mediaAssets: readonly ToolcraftMediaAsset[];
27
+ name: string;
28
+ setControlValue?: (
29
+ target: string,
30
+ value: unknown,
31
+ label?: string,
32
+ meta?: ControlChangeMeta,
33
+ ) => void;
22
34
  sourceAssetCoordinator: ToolcraftSourceAssetCoordinator;
35
+ value?: unknown;
23
36
  };
24
37
 
25
38
  const IMAGE_TRANSFORM_OPERATIONS = new Set<ToolcraftMediaTransformOperation>([
@@ -43,8 +56,11 @@ function FileDropControlRenderer({
43
56
  id,
44
57
  layerSelectedImageAsset = null,
45
58
  mediaAssets,
59
+ name,
60
+ setControlValue = () => undefined,
46
61
  sourceAssetCoordinator,
47
62
  suppressMediaCollection = false,
63
+ value,
48
64
  }: Omit<
49
65
  FileDropControlRenderArgs,
50
66
  "canvasSize" | "layerSelectionEnabled"
@@ -112,6 +128,10 @@ function FileDropControlRenderer({
112
128
  ? { ...basePresentation, secondaryActions: [] }
113
129
  : basePresentation
114
130
  : null;
131
+ const usesCollectionActions = control.variant === "collection-actions";
132
+ const canAddCollectionItem =
133
+ typeof control.hardMaxItems !== "number" ||
134
+ (basePresentation?.items.length ?? 0) < control.hardMaxItems;
115
135
 
116
136
  React.useEffect(() => {
117
137
  if (selectedMediaId && !previewMediaIds.includes(selectedMediaId)) {
@@ -156,6 +176,35 @@ function FileDropControlRenderer({
156
176
 
157
177
  return (
158
178
  <FileDrop
179
+ collectionActions={
180
+ usesCollectionActions
181
+ ? {
182
+ addLabel: control.addLabel ?? `Add ${control.itemLabel ?? "file"}`,
183
+ canAdd: canAddCollectionItem,
184
+ itemLabel: control.itemLabel ?? "file",
185
+ name,
186
+ removeLabel:
187
+ control.removeLabel ?? `Remove ${control.itemLabel ?? "file"}`,
188
+ ...(control.itemControls
189
+ ? {
190
+ renderItemContent: (
191
+ item: FileDropPresentationItem,
192
+ index: number,
193
+ ) => (
194
+ <ControlsPanelFileDropItemControls
195
+ control={control}
196
+ index={index}
197
+ mediaId={item.id}
198
+ name={name}
199
+ setControlValue={setControlValue}
200
+ value={value}
201
+ />
202
+ ),
203
+ }
204
+ : {}),
205
+ }
206
+ : undefined
207
+ }
159
208
  key={id}
160
209
  onAction={
161
210
  activeImageId
@@ -30,7 +30,7 @@ describe("Toolcraft controls-panel renderer registry", () => {
30
30
  "canvas-handle": [],
31
31
  collection: ["value"],
32
32
  compound: ["value"],
33
- media: ["canvas.size", "mediaAssets"],
33
+ media: ["canvas.size", "mediaAssets", "value"],
34
34
  settings: [],
35
35
  } as const;
36
36
 
@@ -23,7 +23,7 @@ export const TOOLCRAFT_CONTROL_RENDERER_STATE_DEPENDENCIES = {
23
23
  "canvas-handle": [],
24
24
  collection: ["value"],
25
25
  compound: ["value"],
26
- media: ["canvas.size", "mediaAssets"],
26
+ media: ["canvas.size", "mediaAssets", "value"],
27
27
  settings: [],
28
28
  } as const satisfies Record<
29
29
  ToolcraftControlRendererKind,
@@ -38,6 +38,7 @@ export const TOOLCRAFT_CONTROL_RENDERER_REGISTRY = {
38
38
  checkbox: "basic",
39
39
  code: "basic",
40
40
  collectionActions: "collection",
41
+ sourceCollection: "collection",
41
42
  color: "compound",
42
43
  colorOpacity: "compound",
43
44
  curves: "compound",
@@ -1,8 +1,18 @@
1
+ import type { ToolcraftCollectionItemControlType } from "../../../schema/collection-item-controls";
1
2
  import type { ToolcraftControlSchema } from "../../../schema/types";
2
3
  import { asFontPickerValue } from "./controls-panel-font-values";
3
4
  import { asNumber } from "./controls-panel-value-primitives";
4
5
 
5
- export function asCollectionItems(value: unknown, fallback: unknown): unknown[] {
6
+ function assertNeverCollectionItemControl(value: never): never {
7
+ throw new Error(
8
+ `Unsupported Toolcraft collection item control: ${String(value)}`,
9
+ );
10
+ }
11
+
12
+ export function asCollectionItems(
13
+ value: unknown,
14
+ fallback: unknown,
15
+ ): unknown[] {
6
16
  if (Array.isArray(value)) {
7
17
  return [...value];
8
18
  }
@@ -17,18 +27,25 @@ export function getCollectionMinItems(control: ToolcraftControlSchema): number {
17
27
  export function getCollectionHardMaxItems(
18
28
  control: ToolcraftControlSchema,
19
29
  ): number | null {
20
- if (typeof control.hardMaxItems !== "number" || !Number.isFinite(control.hardMaxItems)) {
30
+ if (
31
+ typeof control.hardMaxItems !== "number" ||
32
+ !Number.isFinite(control.hardMaxItems)
33
+ ) {
21
34
  return null;
22
35
  }
23
36
 
24
37
  return Math.max(0, Math.floor(control.hardMaxItems));
25
38
  }
26
39
 
27
- export function getCollectionItemType(control: ToolcraftControlSchema): string {
40
+ export function getCollectionItemType(
41
+ control: ToolcraftControlSchema,
42
+ ): ToolcraftCollectionItemControlType {
28
43
  return control.itemControl?.type ?? "color";
29
44
  }
30
45
 
31
- export function getCollectionItemBaseLabel(control: ToolcraftControlSchema): string {
46
+ export function getCollectionItemBaseLabel(
47
+ control: ToolcraftControlSchema,
48
+ ): string {
32
49
  if (control.itemLabel) {
33
50
  return control.itemLabel;
34
51
  }
@@ -54,6 +71,15 @@ export function getCollectionItemName(
54
71
  export function getCollectionItemDefaultValue(
55
72
  control: ToolcraftControlSchema,
56
73
  ): unknown {
74
+ if (control.itemControls) {
75
+ return Object.fromEntries(
76
+ Object.entries(control.itemControls).map(([id, itemControl]) => [
77
+ id,
78
+ itemControl.defaultValue,
79
+ ]),
80
+ );
81
+ }
82
+
57
83
  if ("itemDefaultValue" in control) {
58
84
  return control.itemDefaultValue;
59
85
  }
@@ -62,7 +88,9 @@ export function getCollectionItemDefaultValue(
62
88
  return control.itemControl.defaultValue;
63
89
  }
64
90
 
65
- switch (getCollectionItemType(control)) {
91
+ const itemType = getCollectionItemType(control);
92
+
93
+ switch (itemType) {
66
94
  case "color":
67
95
  return { hex: "#C1FF00" };
68
96
  case "colorOpacity":
@@ -74,6 +102,8 @@ export function getCollectionItemDefaultValue(
74
102
  return false;
75
103
  case "rangeInput":
76
104
  return { end: "100%", start: "0%" };
105
+ case "rangeSlider":
106
+ return [control.itemControl?.min ?? 0, control.itemControl?.max ?? 100];
77
107
  case "select":
78
108
  case "segmented":
79
109
  return control.itemControl?.options?.[0]?.value ?? "";
@@ -81,7 +111,9 @@ export function getCollectionItemDefaultValue(
81
111
  return control.itemControl?.min ?? 0;
82
112
  case "text":
83
113
  return "";
114
+ case "vector":
115
+ return { x: "0", y: "0" };
84
116
  default:
85
- return "";
117
+ return assertNeverCollectionItemControl(itemType);
86
118
  }
87
119
  }
@@ -38,6 +38,7 @@ export function formatControlValueLabel(
38
38
  return `${colorOpacityValue.hex} ${colorOpacityValue.opacity}%`;
39
39
  }
40
40
  case "collectionActions":
41
+ case "sourceCollection":
41
42
  return `${asCollectionItems(value, control.defaultValue).length} items`;
42
43
  case "fontPicker":
43
44
  return asFontPickerValue(value).fontId;
@@ -6,6 +6,10 @@ export { useToolcraftMediaPresentationUrls } from "./app-shell/toolcraft-media-p
6
6
  export * from "./app-shell/use-toolcraft-pipeline";
7
7
  export * from "./app-shell/use-toolcraft-pipeline-pass";
8
8
  export * from "./canvas/canvas-shell";
9
+ export {
10
+ useToolcraftProductSceneFrame,
11
+ type ToolcraftProductSceneFrame,
12
+ } from "./canvas/product-scene-surface";
9
13
  export * from "./controls-panel/control-renderers";
10
14
  export * from "./controls-panel/controls-panel";
11
15
  export * from "./layers/layers-panel";
@@ -2,7 +2,7 @@ import {
2
2
  resolveToolcraftExportFrame,
3
3
  ToolcraftSceneExportError,
4
4
  type ToolcraftExportFrame,
5
- } from "../../export";
5
+ } from "../../export/export-frame";
6
6
  import { getToolcraftCanvasFrame, type ToolcraftCanvasFrame } from "../../state/canvas-frame";
7
7
  import type { ToolcraftState } from "../../state/types";
8
8
  import { getSceneElementPresentationRect } from "../canvas/scene-element-presentation-rect";
@@ -1,6 +1,6 @@
1
1
  import type {
2
- ToolcraftControlSchema,
3
- ToolcraftControlSectionSchema,
2
+ ResolvedToolcraftControlSchema,
3
+ ResolvedToolcraftControlSectionSchema,
4
4
  } from "../../schema/types";
5
5
  import type { ToolcraftState } from "../../state/types";
6
6
  import {
@@ -9,13 +9,13 @@ import {
9
9
  } from "../controls-panel/conditions/control-conditions";
10
10
 
11
11
  export type ToolcraftOrientationControlEntry = {
12
- control: ToolcraftControlSchema;
12
+ control: ResolvedToolcraftControlSchema;
13
13
  id: string;
14
- section: ToolcraftControlSectionSchema;
14
+ section: ResolvedToolcraftControlSectionSchema;
15
15
  };
16
16
 
17
17
  export function getToolcraftOrientationControlEntries(
18
- sections: readonly ToolcraftControlSectionSchema[],
18
+ sections: readonly ResolvedToolcraftControlSectionSchema[],
19
19
  ): ToolcraftOrientationControlEntry[] {
20
20
  return sections.flatMap((section) =>
21
21
  Object.entries(section.controls).flatMap(([id, control]) =>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
- import type { ToolcraftControlSchema } from "../../schema/types";
5
+ import type { ResolvedToolcraftControlSchema } from "../../schema/types";
6
6
  import type { ToolcraftStoreDependency } from "../../state/toolcraft-external-store-dependencies";
7
7
  import type { ToolcraftState } from "../../state/types";
8
8
  import { useToolcraftDependencySelector } from "../app-shell/toolcraft-selectors";
@@ -18,7 +18,7 @@ import {
18
18
  } from "./orientation-gizmo-selection";
19
19
 
20
20
  export type ToolcraftOrientationControlSelection = Readonly<{
21
- control: ToolcraftControlSchema | null;
21
+ control: ResolvedToolcraftControlSchema | null;
22
22
  id: string | null;
23
23
  value: unknown;
24
24
  }>;
@@ -195,4 +195,34 @@ describe("ToolcraftPanelHost runtime placement", () => {
195
195
  );
196
196
  });
197
197
  });
198
+
199
+ it("keeps the CSS-anchored default placement during viewport resize", async () => {
200
+ const { container } = renderToolbar();
201
+ const host = container.querySelector<HTMLElement>(
202
+ "[data-slot='toolcraft-runtime-panel-host']",
203
+ );
204
+
205
+ if (!host) {
206
+ throw new Error("Expected the toolbar panel host to render.");
207
+ }
208
+ vi.spyOn(host, "getBoundingClientRect").mockReturnValue({
209
+ bottom: 900,
210
+ height: 100,
211
+ left: 1_500,
212
+ right: 1_700,
213
+ top: 800,
214
+ width: 200,
215
+ x: 1_500,
216
+ y: 800,
217
+ toJSON: () => ({}),
218
+ });
219
+
220
+ fireEvent(window, new Event("resize"));
221
+
222
+ await waitFor(() => {
223
+ expect(screen.getByTestId("toolbar-placement").textContent).toBe(
224
+ "0,0,free",
225
+ );
226
+ });
227
+ });
198
228
  });
@@ -191,6 +191,10 @@ function usePanelSnapControls({
191
191
  return;
192
192
  }
193
193
 
194
+ if (x.get() === 0 && y.get() === 0 && snapEdge === undefined) {
195
+ return;
196
+ }
197
+
194
198
  const currentPlacement = {
195
199
  offset: { x: x.get(), y: y.get() },
196
200
  ...(snapEdge ? { snapEdge } : {}),
@@ -6,6 +6,7 @@ import { createToolcraftState } from "../state/create-template-state";
6
6
  import {
7
7
  getToolcraftSceneElementRect,
8
8
  outwardRoundToolcraftSceneRect,
9
+ resolveToolcraftProductSceneBounds,
9
10
  resolveToolcraftSceneBounds,
10
11
  unionToolcraftSceneRects,
11
12
  } from "./scene-bounds";
@@ -17,6 +18,28 @@ function emptyState() {
17
18
  }
18
19
 
19
20
  describe("scene bounds", () => {
21
+ it("resolves one exact product frame before runtime contributors are added", () => {
22
+ expect(
23
+ resolveToolcraftProductSceneBounds([
24
+ { height: 20, width: 40, x: -10, y: -5 },
25
+ { height: 30, width: 10, x: 35, y: 15 },
26
+ ]),
27
+ ).toEqual({
28
+ bounds: { height: 50, width: 55, x: -10, y: -5 },
29
+ ok: true,
30
+ });
31
+ expect(resolveToolcraftProductSceneBounds([])).toMatchObject({
32
+ code: "empty-scene",
33
+ ok: false,
34
+ });
35
+ expect(
36
+ resolveToolcraftProductSceneBounds([
37
+ { height: 20, width: 40, x: -10, y: -5 },
38
+ { height: 30, width: Number.NaN, x: 35, y: 15 },
39
+ ]),
40
+ ).toMatchObject({ code: "scene-bounds-unavailable", ok: false });
41
+ });
42
+
20
43
  it("projects center-anchored frames and unions negative coordinates", () => {
21
44
  expect(
22
45
  getToolcraftSceneElementRect({
@@ -36,8 +36,7 @@ export function createToolcraftRuntimeSceneVisibility(
36
36
 
37
37
  export type ToolcraftProductSceneBoundsProvider = (
38
38
  context: Readonly<{
39
- state: ToolcraftState;
40
- timeRange?: Readonly<{ endSeconds: number; startSeconds: number }>;
39
+ state: Readonly<ToolcraftState>;
41
40
  }>,
42
41
  ) => readonly ToolcraftSceneRect[];
43
42
 
@@ -49,6 +48,18 @@ export type ToolcraftSceneBoundsResult =
49
48
  ok: false;
50
49
  }>;
51
50
 
51
+ const emptySceneBoundsResult: ToolcraftSceneBoundsResult = Object.freeze({
52
+ code: "empty-scene",
53
+ message: "The scene has no visible exportable elements.",
54
+ ok: false,
55
+ });
56
+
57
+ const unavailableSceneBoundsResult: ToolcraftSceneBoundsResult = Object.freeze({
58
+ code: "scene-bounds-unavailable",
59
+ message: "Product scene bounds are unavailable or invalid.",
60
+ ok: false,
61
+ });
62
+
52
63
  export function getToolcraftSceneElementRect(
53
64
  element: ToolcraftSceneElementFrame,
54
65
  ): ToolcraftSceneRect {
@@ -159,32 +170,34 @@ export function outwardRoundToolcraftSceneRect(
159
170
  };
160
171
  }
161
172
 
162
- export function resolveToolcraftSceneBounds(
163
- state: ToolcraftState,
164
- productRects: unknown = [],
165
- visibility: ToolcraftRuntimeSceneVisibility = defaultToolcraftRuntimeSceneVisibility,
173
+ export function resolveToolcraftProductSceneBounds(
174
+ productRects: unknown,
166
175
  ): ToolcraftSceneBoundsResult {
167
176
  if (
168
177
  !Array.isArray(productRects) ||
169
178
  !productRects.every(isFiniteSceneRect)
170
179
  ) {
171
- return {
172
- code: "scene-bounds-unavailable",
173
- message: "Product scene bounds are unavailable or invalid.",
174
- ok: false,
175
- };
180
+ return unavailableSceneBoundsResult;
181
+ }
182
+
183
+ const bounds = unionToolcraftSceneRects(productRects);
184
+ return bounds ? { bounds, ok: true } : emptySceneBoundsResult;
185
+ }
186
+
187
+ export function resolveToolcraftSceneBounds(
188
+ state: ToolcraftState,
189
+ productRects: unknown = [],
190
+ visibility: ToolcraftRuntimeSceneVisibility = defaultToolcraftRuntimeSceneVisibility,
191
+ ): ToolcraftSceneBoundsResult {
192
+ const productBounds = resolveToolcraftProductSceneBounds(productRects);
193
+ if (!productBounds.ok && productBounds.code === "scene-bounds-unavailable") {
194
+ return productBounds;
176
195
  }
177
196
 
178
197
  const bounds = unionToolcraftSceneRects([
179
198
  ...getRuntimeSceneRects(state, visibility),
180
- ...productRects,
199
+ ...(productBounds.ok ? [productBounds.bounds] : []),
181
200
  ]);
182
201
 
183
- return bounds
184
- ? { bounds, ok: true }
185
- : {
186
- code: "empty-scene",
187
- message: "The scene has no visible exportable elements.",
188
- ok: false,
189
- };
202
+ return bounds ? { bounds, ok: true } : emptySceneBoundsResult;
190
203
  }
@@ -45,6 +45,10 @@ describe("resolveToolcraftAppCapabilities", () => {
45
45
  {
46
46
  controls: {
47
47
  source: {
48
+ applicability: {
49
+ mode: "always",
50
+ origin: "implicit",
51
+ },
48
52
  assetKind: "file",
49
53
  target: "source.files",
50
54
  type: "fileDrop",
@@ -0,0 +1,62 @@
1
+ import type {
2
+ ResolvedToolcraftCanvasRenderScaleSchema,
3
+ ToolcraftCanvasRenderScaleSchema,
4
+ } from "./types";
5
+
6
+ export const TOOLCRAFT_CANVAS_RENDER_SCALE = Object.freeze({
7
+ defaultValue: 2,
8
+ max: 2,
9
+ min: 1,
10
+ step: 0.25,
11
+ });
12
+
13
+ const minimumCanvasRenderScaleStep = 0.01;
14
+ const canvasRenderScaleStepTolerance = 1e-9;
15
+
16
+ function resolveCanvasRenderScaleStep(value: number | undefined): number {
17
+ if (value === undefined) {
18
+ return TOOLCRAFT_CANVAS_RENDER_SCALE.step;
19
+ }
20
+
21
+ const maximumCanvasRenderScaleStep =
22
+ TOOLCRAFT_CANVAS_RENDER_SCALE.max - TOOLCRAFT_CANVAS_RENDER_SCALE.min;
23
+
24
+ if (
25
+ typeof value !== "number" ||
26
+ !Number.isFinite(value) ||
27
+ value < minimumCanvasRenderScaleStep ||
28
+ value > maximumCanvasRenderScaleStep
29
+ ) {
30
+ throw new Error(
31
+ "Canvas render scale step must be a finite value between 0.01 and 1.",
32
+ );
33
+ }
34
+
35
+ const stepCount = maximumCanvasRenderScaleStep / value;
36
+
37
+ if (Math.abs(stepCount - Math.round(stepCount)) > canvasRenderScaleStepTolerance) {
38
+ throw new Error("Canvas render scale step must divide the canonical range exactly.");
39
+ }
40
+
41
+ return value;
42
+ }
43
+
44
+ export function resolveToolcraftCanvasRenderScale(
45
+ renderScale: ToolcraftCanvasRenderScaleSchema | undefined,
46
+ ): ResolvedToolcraftCanvasRenderScaleSchema {
47
+ if (!renderScale) {
48
+ return {
49
+ ...TOOLCRAFT_CANVAS_RENDER_SCALE,
50
+ enabled: false,
51
+ };
52
+ }
53
+
54
+ return {
55
+ ...TOOLCRAFT_CANVAS_RENDER_SCALE,
56
+ enabled: true,
57
+ step:
58
+ renderScale === true
59
+ ? TOOLCRAFT_CANVAS_RENDER_SCALE.step
60
+ : resolveCanvasRenderScaleStep(renderScale.step),
61
+ };
62
+ }
@@ -0,0 +1,29 @@
1
+ export const TOOLCRAFT_COLLECTION_ITEM_CONTROL_TYPES = [
2
+ "checkbox",
3
+ "color",
4
+ "colorOpacity",
5
+ "fontPicker",
6
+ "rangeInput",
7
+ "rangeSlider",
8
+ "segmented",
9
+ "select",
10
+ "slider",
11
+ "switch",
12
+ "text",
13
+ "vector",
14
+ ] as const;
15
+
16
+ export type ToolcraftCollectionItemControlType =
17
+ (typeof TOOLCRAFT_COLLECTION_ITEM_CONTROL_TYPES)[number];
18
+
19
+ const toolcraftCollectionItemControlTypes = new Set<string>(
20
+ TOOLCRAFT_COLLECTION_ITEM_CONTROL_TYPES,
21
+ );
22
+
23
+ export function isToolcraftCollectionItemControlType(
24
+ value: unknown,
25
+ ): value is ToolcraftCollectionItemControlType {
26
+ return (
27
+ typeof value === "string" && toolcraftCollectionItemControlTypes.has(value)
28
+ );
29
+ }