@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
@@ -136,7 +136,7 @@ describe("ControlsPanel conditional control visibility", () => {
136
136
  ).toHaveLength(0);
137
137
  });
138
138
 
139
- it("renders only controls that match the current visibleWhen mode", async () => {
139
+ it("renders only applicable controls and preserves hidden values", async () => {
140
140
  const schema = defineToolcraft({
141
141
  canvas: { enabled: true },
142
142
  panels: {
@@ -145,6 +145,7 @@ describe("ControlsPanel conditional control visibility", () => {
145
145
  {
146
146
  controls: {
147
147
  identityMode: {
148
+ applicability: { mode: "always" },
148
149
  defaultValue: "text",
149
150
  label: "Identity",
150
151
  options: [
@@ -155,24 +156,30 @@ describe("ControlsPanel conditional control visibility", () => {
155
156
  type: "segmented",
156
157
  },
157
158
  partnerName: {
159
+ applicability: {
160
+ all: [
161
+ { equals: "text", target: "coBrand.identityMode" },
162
+ { equals: true, target: "coBrand.partnerEnabled" },
163
+ ],
164
+ mode: "conditional",
165
+ },
158
166
  defaultValue: "tRPC",
159
167
  label: "Partner",
160
168
  target: "coBrand.partnerName",
161
169
  type: "text",
162
- visibleWhen: {
163
- equals: "text",
164
- target: "coBrand.identityMode",
165
- },
166
170
  },
167
171
  partnerLogo: {
172
+ applicability: {
173
+ all: [
174
+ { equals: "logo", target: "coBrand.identityMode" },
175
+ { equals: true, target: "coBrand.partnerEnabled" },
176
+ ],
177
+ mode: "conditional",
178
+ },
168
179
  defaultValue: "logo.svg",
169
180
  label: "Partner logo",
170
181
  target: "coBrand.partnerLogo",
171
182
  type: "text",
172
- visibleWhen: {
173
- equals: "logo",
174
- target: "coBrand.identityMode",
175
- },
176
183
  },
177
184
  },
178
185
  title: "Partner Lockup",
@@ -183,7 +190,9 @@ describe("ControlsPanel conditional control visibility", () => {
183
190
  },
184
191
  });
185
192
 
186
- renderControlsPanelWithSchema(schema);
193
+ renderControlsPanelWithSchema(schema, {}, {
194
+ values: { "coBrand.partnerEnabled": true },
195
+ });
187
196
 
188
197
  const partnerInput = screen.getByDisplayValue("tRPC");
189
198
 
@@ -208,7 +217,7 @@ describe("ControlsPanel conditional control visibility", () => {
208
217
  });
209
218
  });
210
219
 
211
- it("renders count-dependent controls through numeric visibleWhen conditions", () => {
220
+ it("renders count-dependent controls through numeric applicability", () => {
212
221
  const schema = defineToolcraft({
213
222
  canvas: { enabled: true },
214
223
  panels: {
@@ -217,6 +226,7 @@ describe("ControlsPanel conditional control visibility", () => {
217
226
  {
218
227
  controls: {
219
228
  shadeCount: {
229
+ applicability: { mode: "always" },
220
230
  defaultValue: 2,
221
231
  label: "Shades",
222
232
  max: 5,
@@ -227,30 +237,41 @@ describe("ControlsPanel conditional control visibility", () => {
227
237
  variant: "discrete",
228
238
  },
229
239
  shade1: {
240
+ applicability: { mode: "always" },
230
241
  defaultValue: { hex: "#FFFFFF" },
231
242
  label: "Shade 1",
232
243
  target: "shapes.color1",
233
244
  type: "color",
234
245
  },
235
246
  shade2: {
247
+ applicability: {
248
+ all: [
249
+ {
250
+ greaterThanOrEqual: 2,
251
+ target: "shapes.shadeCount",
252
+ },
253
+ ],
254
+ mode: "conditional",
255
+ },
236
256
  defaultValue: { hex: "#DDDDDD" },
237
257
  label: "Shade 2",
238
258
  target: "shapes.color2",
239
259
  type: "color",
240
- visibleWhen: {
241
- greaterThanOrEqual: 2,
242
- target: "shapes.shadeCount",
243
- },
244
260
  },
245
261
  shade3: {
262
+ applicability: {
263
+ all: [
264
+ {
265
+ greaterThanOrEqual: 3,
266
+ target: "shapes.shadeCount",
267
+ },
268
+ ],
269
+ mode: "conditional",
270
+ },
246
271
  defaultValue: { hex: "#BBBBBB" },
247
272
  label: "Shade 3",
248
273
  target: "shapes.color3",
249
274
  type: "color",
250
- visibleWhen: {
251
- greaterThanOrEqual: 3,
252
- target: "shapes.shadeCount",
253
- },
254
275
  },
255
276
  },
256
277
  title: "Shapes",
@@ -66,7 +66,7 @@ describe("ControlsPanel footer actions and settings transfer", () => {
66
66
  expect(handledActions).toEqual(["download:Generation Controls"]);
67
67
  });
68
68
 
69
- it("exposes the runtime model compositor to product export actions", async () => {
69
+ it("keeps runtime export mechanics out of generic product action context", () => {
70
70
  const schema = defineToolcraft({
71
71
  canvas: { enabled: true },
72
72
  panels: {
@@ -93,24 +93,19 @@ describe("ControlsPanel footer actions and settings transfer", () => {
93
93
  },
94
94
  },
95
95
  });
96
- const canvas = {
97
- getContext: vi.fn(() => ({ drawImage: vi.fn() })),
98
- height: 1080,
99
- width: 1920,
100
- } as unknown as HTMLCanvasElement;
101
- let renderedModels: Promise<number> | undefined;
96
+ let actionContextKeys: string[] = [];
102
97
 
103
98
  renderControlsPanelWithSchema(schema, {
104
- onPanelAction: ({ renderModelsToCanvas }) => {
105
- renderedModels = renderModelsToCanvas(canvas);
106
- return renderedModels;
99
+ onPanelAction: (context) => {
100
+ actionContextKeys = Object.keys(context).sort();
107
101
  },
108
102
  });
109
103
 
110
104
  fireEvent.click(screen.getByRole("button", { name: "Export PNG" }));
111
105
 
112
- await expect(renderedModels).resolves.toBe(0);
113
- expect(canvas.getContext).toHaveBeenCalledWith("2d");
106
+ expect(actionContextKeys).not.toContain("renderModelsToCanvas");
107
+ expect(actionContextKeys).not.toContain("renderRuntimeSceneToCanvas");
108
+ expect(actionContextKeys).not.toContain("resolveSceneExportFrame");
114
109
  });
115
110
 
116
111
  it("renders runtime settings transfer as a headerless body section instead of sticky footer actions", () => {
@@ -1,4 +1,4 @@
1
- import { ToolcraftSceneExportError } from "../../../export";
1
+ import { ToolcraftSceneExportError } from "../../../export/export-frame";
2
2
  import { defaultToolcraftRuntimeSceneVisibility } from "../../../scene";
3
3
  import type { ToolcraftActionSchema } from "../../../schema/types";
4
4
  import {
@@ -39,83 +39,6 @@ function createExportSchema(
39
39
  }
40
40
 
41
41
  describe("ControlsPanel scene export actions", () => {
42
- it("skips product bounds for finite export and calls them once for infinite export", () => {
43
- const schema = createExportSchema();
44
- const provider = vi.fn(() => [
45
- { height: 10, width: 10, x: 100, y: 50 },
46
- ]);
47
- let finiteFrame: unknown;
48
- const finite = renderControlsPanelWithSchema(schema, {
49
- onPanelAction: ({ resolveSceneExportFrame }) => {
50
- finiteFrame = resolveSceneExportFrame();
51
- },
52
- sceneExport: {
53
- boundsProvider: provider,
54
- boundsRequired: true,
55
- visibility: defaultToolcraftRuntimeSceneVisibility,
56
- },
57
- });
58
-
59
- fireEvent.click(screen.getByRole("button", { name: "Export PNG" }));
60
- expect(finiteFrame).toEqual({
61
- frame: { height: 180, width: 320, x: 0, y: 0 },
62
- ok: true,
63
- });
64
- expect(provider).not.toHaveBeenCalled();
65
- finite.unmount();
66
-
67
- let infiniteFrame: unknown;
68
- renderControlsPanelWithSchema(
69
- schema,
70
- {
71
- onPanelAction: ({ resolveSceneExportFrame }) => {
72
- infiniteFrame = resolveSceneExportFrame({
73
- timeRange: { endSeconds: 8, startSeconds: 0 },
74
- });
75
- },
76
- sceneExport: {
77
- boundsProvider: provider,
78
- boundsRequired: true,
79
- visibility: defaultToolcraftRuntimeSceneVisibility,
80
- },
81
- },
82
- {
83
- canvas: { mode: "infinite" },
84
- layers: [
85
- {
86
- displayName: "Image",
87
- id: "image-layer",
88
- kind: "layer",
89
- name: "image",
90
- visible: true,
91
- },
92
- ],
93
- mediaAssets: [
94
- {
95
- assetKind: "image",
96
- dataUrl: "data:image/png;base64,test",
97
- fileName: "image.png",
98
- id: "image",
99
- layerId: "image-layer",
100
- mimeType: "image/png",
101
- position: { x: 0, y: 0 },
102
- size: { height: 20, unit: "px", width: 20 },
103
- },
104
- ],
105
- },
106
- );
107
-
108
- fireEvent.click(screen.getByRole("button", { name: "Export PNG" }));
109
- expect(provider).toHaveBeenCalledTimes(1);
110
- expect(provider).toHaveBeenCalledWith(
111
- expect.objectContaining({ timeRange: { endSeconds: 8, startSeconds: 0 } }),
112
- );
113
- expect(infiniteFrame).toEqual({
114
- frame: { height: 70, width: 120, x: -10, y: -10 },
115
- ok: true,
116
- });
117
- });
118
-
119
42
  it("shows only feedback reported by the latest panel action", async () => {
120
43
  const schema = createExportSchema([
121
44
  { label: "First export", value: "first", variant: "secondary" },
@@ -143,17 +66,27 @@ describe("ControlsPanel scene export actions", () => {
143
66
  });
144
67
 
145
68
  it("presents a missing required bounds provider as typed visible feedback", async () => {
69
+ const onPanelAction = vi.fn();
146
70
  renderControlsPanelWithSchema(
147
- createExportSchema(),
148
- {
149
- onPanelAction: ({ reportFeedback, resolveSceneExportFrame }) => {
150
- const result = resolveSceneExportFrame();
151
- if (!result.ok) reportFeedback(result);
71
+ createExportSchema([
72
+ {
73
+ command: "controls.apply",
74
+ label: "Export PNG",
75
+ role: "export-image",
76
+ value: "export",
77
+ variant: "default",
152
78
  },
79
+ ]),
80
+ {
153
81
  sceneExport: {
154
- boundsRequired: true,
82
+ exportRenderer: {
83
+ baseFileName: "scene",
84
+ renderFrame: vi.fn(),
85
+ },
86
+ productSceneRequired: true,
155
87
  visibility: defaultToolcraftRuntimeSceneVisibility,
156
88
  },
89
+ onPanelAction,
157
90
  },
158
91
  { canvas: { mode: "infinite" } },
159
92
  );
@@ -164,6 +97,7 @@ describe("ControlsPanel scene export actions", () => {
164
97
  "scene-bounds-unavailable",
165
98
  );
166
99
  expect(alert.textContent).toContain("does not provide export bounds");
100
+ expect(onPanelAction).not.toHaveBeenCalled();
167
101
  });
168
102
 
169
103
  it("presents rejected typed export failures as visible feedback", async () => {
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ defineToolcraft,
4
+ fireEvent,
5
+ renderControlsPanelWithSchema,
6
+ screen,
7
+ } from "../testing/controls-panel-test-utils";
8
+
9
+ describe("ControlsPanel source collection", () => {
10
+ it("renders source-sized built-in color controls without cardinality actions", () => {
11
+ const schema = defineToolcraft({
12
+ canvas: { enabled: false },
13
+ panels: {
14
+ controls: {
15
+ sections: [
16
+ {
17
+ controls: {
18
+ sections: {
19
+ defaultValue: [{ hex: "#DFFF1A" }, { hex: "#8CFF3A" }],
20
+ itemControl: {
21
+ label: "Section",
22
+ type: "color",
23
+ },
24
+ label: "Sections",
25
+ target: "fills.sections",
26
+ type: "sourceCollection",
27
+ },
28
+ },
29
+ title: "Fill Sections",
30
+ },
31
+ ],
32
+ title: "Controls",
33
+ },
34
+ },
35
+ });
36
+
37
+ const { container } = renderControlsPanelWithSchema(schema);
38
+
39
+ expect(screen.queryByRole("button", { name: /Add/u })).toBeNull();
40
+ expect(screen.queryByRole("button", { name: /Remove/u })).toBeNull();
41
+ expect(screen.getByRole("button", { name: "Pick Section 1" })).toBeTruthy();
42
+ expect(screen.getByRole("textbox", { name: "Section 2 hex" })).toBeTruthy();
43
+ expect(
44
+ container.querySelector('[data-slot="source-collection-items-grid"]')
45
+ ?.children,
46
+ ).toHaveLength(2);
47
+
48
+ const secondColor = screen.getByRole("textbox", { name: "Section 2 hex" });
49
+
50
+ fireEvent.change(secondColor, { target: { value: "#123456" } });
51
+ fireEvent.blur(secondColor);
52
+
53
+ const values = JSON.parse(
54
+ screen.getByTestId("values-json").textContent ?? "{}",
55
+ );
56
+
57
+ expect(values["fills.sections"]).toEqual([
58
+ { hex: "#DFFF1A" },
59
+ { hex: "#123456" },
60
+ ]);
61
+ });
62
+ });
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ defineToolcraft,
4
+ fireEvent,
5
+ renderControlsPanelWithSchema,
6
+ screen,
7
+ } from "../testing/controls-panel-test-utils";
8
+
9
+ const sourceColors = Array.from({ length: 19 }, (_, index) => ({
10
+ hex: index === 18 ? "#654321" : "#DFFF1A",
11
+ }));
12
+
13
+ describe("ControlsPanel source color bank", () => {
14
+ it("keeps a source-sized badge palette on built-in color controls", () => {
15
+ const schema = defineToolcraft({
16
+ canvas: { enabled: false },
17
+ panels: {
18
+ controls: {
19
+ sections: [
20
+ {
21
+ controls: {
22
+ sections: {
23
+ defaultValue: sourceColors,
24
+ itemControl: {
25
+ label: "Section",
26
+ type: "color",
27
+ },
28
+ label: "Sections",
29
+ target: "fills.sections",
30
+ type: "sourceCollection",
31
+ },
32
+ },
33
+ title: "Fill Sections",
34
+ },
35
+ ],
36
+ title: "Controls",
37
+ },
38
+ },
39
+ });
40
+
41
+ const { container } = renderControlsPanelWithSchema(schema);
42
+
43
+ expect(
44
+ screen.getAllByRole("button", { name: /^Pick Section \d+$/u }),
45
+ ).toHaveLength(19);
46
+ expect(screen.getByRole("button", { name: "Pick Section 19" })).toBeTruthy();
47
+ expect(screen.queryByRole("button", { name: "Pick Section 20" })).toBeNull();
48
+ expect(screen.queryByRole("button", { name: /Add/u })).toBeNull();
49
+ expect(screen.queryByRole("button", { name: /Remove/u })).toBeNull();
50
+ expect(
51
+ container.querySelector('[data-slot="source-collection-items-grid"]')
52
+ ?.children,
53
+ ).toHaveLength(19);
54
+
55
+ const lastColor = screen.getByRole("textbox", { name: "Section 19 hex" });
56
+ fireEvent.change(lastColor, { target: { value: "#123456" } });
57
+ fireEvent.blur(lastColor);
58
+
59
+ const values = JSON.parse(
60
+ screen.getByTestId("values-json").textContent ?? "{}",
61
+ );
62
+ expect(values["fills.sections"]).toEqual([
63
+ ...sourceColors.slice(0, 18),
64
+ { hex: "#123456" },
65
+ ]);
66
+ });
67
+ });
@@ -2,19 +2,10 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
- import {
6
- resolveToolcraftExportFrame,
7
- ToolcraftSceneExportError,
8
- type ToolcraftExportFrame,
9
- type ToolcraftExportFrameResult,
10
- } from "../../../export";
5
+ import { ToolcraftArtifactExportError } from "../../../export/export-error";
6
+ import { ToolcraftSceneExportError } from "../../../export/export-frame";
11
7
  import type { ToolcraftRendererPipelineClient } from "../../../rendering";
12
- import {
13
- defaultToolcraftRuntimeSceneVisibility,
14
- resolveToolcraftSceneBounds,
15
- type ToolcraftProductSceneBoundsProvider,
16
- type ToolcraftRuntimeSceneVisibility,
17
- } from "../../../scene";
8
+ import { defaultToolcraftRuntimeSceneVisibility } from "../../../scene";
18
9
  import type {
19
10
  ToolcraftActionCommand,
20
11
  ToolcraftActionSchema,
@@ -25,46 +16,31 @@ import type {
25
16
  } from "../../../state/types";
26
17
  import { getToolcraftCanvasFrame } from "../../../state/canvas-frame";
27
18
  import type { ActionControlRunAction } from "../renderers/controls-panel-action-renderer";
28
- import {
29
- renderToolcraftRuntimeSceneToCanvas,
30
- type ToolcraftRuntimeSceneExportResult,
31
- } from "../../canvas/runtime-scene-export";
19
+ import { renderToolcraftRuntimeSceneToCanvas } from "../../canvas/runtime-scene-export";
32
20
  import { useToolcraftPipeline } from "../../app-shell/use-toolcraft-pipeline";
33
21
  import { useToolcraftSourceAssetCoordinator } from "../../app-shell/toolcraft-source-asset-context";
34
- import {
35
- renderToolcraftModelsToCanvas,
36
- type ToolcraftRenderModelsToCanvas,
37
- } from "../../model-rendering/model-export";
38
22
  import { useOptionalToolcraftModelRenderHost } from "../../model-rendering/model-render-provider";
23
+ import {
24
+ runToolcraftExportAction,
25
+ type ToolcraftControlsSceneExport,
26
+ } from "./export-action-runner";
27
+
28
+ export type { ToolcraftControlsSceneExport } from "./export-action-runner";
39
29
 
40
30
  export type ToolcraftPanelActionFeedback = Readonly<{
41
31
  code: string;
42
32
  message: string;
43
33
  }>;
44
34
 
45
- export type ToolcraftControlsSceneExport = Readonly<{
46
- boundsProvider?: ToolcraftProductSceneBoundsProvider;
47
- boundsRequired: boolean;
48
- visibility: ToolcraftRuntimeSceneVisibility;
49
- }>;
50
-
51
35
  export type ToolcraftPanelActionContext = {
52
36
  action: ToolcraftActionSchema;
53
37
  dispatch: React.Dispatch<ToolcraftCommand>;
54
- renderRuntimeSceneToCanvas: (
55
- canvas: HTMLCanvasElement,
56
- frame: ToolcraftExportFrame,
57
- ) => Promise<ToolcraftRuntimeSceneExportResult>;
58
38
  reportProgress: (progress: number) => void;
59
39
  reportFeedback: (feedback: ToolcraftPanelActionFeedback) => void;
60
- renderModelsToCanvas: ToolcraftRenderModelsToCanvas;
61
40
  resolveMediaResource: (
62
41
  resourceRef: string,
63
42
  options: Readonly<{ signal: AbortSignal }>,
64
43
  ) => Promise<Uint8Array | null>;
65
- resolveSceneExportFrame: (options?: Readonly<{
66
- timeRange?: Readonly<{ endSeconds: number; startSeconds: number }>;
67
- }>) => ToolcraftExportFrameResult;
68
44
  rendererPipeline?: ToolcraftRendererPipelineClient | null;
69
45
  state: ToolcraftState;
70
46
  };
@@ -90,42 +66,10 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
90
66
  function noopReportProgress(): void {}
91
67
 
92
68
  const defaultSceneExport: ToolcraftControlsSceneExport = Object.freeze({
93
- boundsRequired: false,
69
+ productSceneRequired: false,
94
70
  visibility: defaultToolcraftRuntimeSceneVisibility,
95
71
  });
96
72
 
97
- function unavailableProductBounds(message: string): ToolcraftExportFrameResult {
98
- return { code: "scene-bounds-unavailable", message, ok: false };
99
- }
100
-
101
- function resolveActionSceneExportFrame(
102
- sceneExport: ToolcraftControlsSceneExport,
103
- state: ToolcraftState,
104
- timeRange?: Readonly<{ endSeconds: number; startSeconds: number }>,
105
- ): ToolcraftExportFrameResult {
106
- const finiteFrame = resolveToolcraftExportFrame(state, null);
107
- if (finiteFrame.ok) return finiteFrame;
108
-
109
- if (sceneExport.boundsRequired && !sceneExport.boundsProvider) {
110
- return unavailableProductBounds(
111
- "This infinite product scene does not provide export bounds.",
112
- );
113
- }
114
-
115
- let productRects: unknown = [];
116
- try {
117
- productRects = sceneExport.boundsProvider?.({ state, timeRange }) ?? [];
118
- } catch {
119
- return unavailableProductBounds("Product scene bounds could not be resolved.");
120
- }
121
- const bounds = resolveToolcraftSceneBounds(
122
- state,
123
- productRects,
124
- sceneExport.visibility,
125
- );
126
- return resolveToolcraftExportFrame(state, bounds);
127
- }
128
-
129
73
  function clampFooterActionProgress(progress: number): number {
130
74
  if (!Number.isFinite(progress)) {
131
75
  return 0;
@@ -228,7 +172,10 @@ export function useControlsPanelActions({
228
172
 
229
173
  void Promise.resolve(result)
230
174
  .catch((error: unknown) => {
231
- if (error instanceof ToolcraftSceneExportError) {
175
+ if (
176
+ error instanceof ToolcraftSceneExportError ||
177
+ error instanceof ToolcraftArtifactExportError
178
+ ) {
232
179
  reportFeedback(error.feedback);
233
180
  } else {
234
181
  console.error("Toolcraft panel action failed.", error);
@@ -257,7 +204,11 @@ export function useControlsPanelActions({
257
204
  setPanelActionFeedback(feedback);
258
205
  }
259
206
  };
260
- const command = action.command ?? (onPanelAction ? null : getActionCommand(action));
207
+ const runtimeOwnsExport =
208
+ action.role === "export-image" || action.role === "export-video";
209
+ const command = runtimeOwnsExport
210
+ ? null
211
+ : action.command ?? (onPanelAction ? null : getActionCommand(action));
261
212
 
262
213
  if (command) {
263
214
  dispatch({ type: command });
@@ -270,36 +221,41 @@ export function useControlsPanelActions({
270
221
  const actionState = getState();
271
222
  let result: PromiseLike<unknown> | void;
272
223
  try {
273
- result = onPanelAction?.({
224
+ const reportProgress =
225
+ footerActionProgressTracker?.reportProgress ?? noopReportProgress;
226
+ const exportResult = runToolcraftExportAction({
274
227
  action,
275
- dispatch,
276
- renderRuntimeSceneToCanvas: (canvas, frame) =>
228
+ renderRuntimeScene: (canvas, frame, state) =>
277
229
  renderToolcraftRuntimeSceneToCanvas({
278
230
  canvas,
279
- canvasFrame: getToolcraftCanvasFrame(actionState.canvas),
231
+ canvasFrame: getToolcraftCanvasFrame(state.canvas),
280
232
  exportFrame: frame,
281
233
  host: modelRenderHost,
282
234
  resolveImageResource: sourceAssetCoordinator.resolveResource,
283
- state: actionState,
235
+ state,
284
236
  visibility: sceneExport.visibility,
285
237
  }),
286
- reportFeedback,
287
- reportProgress:
288
- footerActionProgressTracker?.reportProgress ?? noopReportProgress,
289
- renderModelsToCanvas: (canvas) =>
290
- renderToolcraftModelsToCanvas(modelRenderHost, actionState, canvas),
291
- resolveMediaResource,
292
- resolveSceneExportFrame: (resolveOptions) =>
293
- resolveActionSceneExportFrame(
294
- sceneExport,
295
- actionState,
296
- resolveOptions?.timeRange,
297
- ),
298
238
  rendererPipeline,
239
+ reportProgress,
240
+ sceneExport,
299
241
  state: actionState,
300
242
  });
243
+ result =
244
+ exportResult ??
245
+ onPanelAction?.({
246
+ action,
247
+ dispatch,
248
+ reportFeedback,
249
+ reportProgress,
250
+ resolveMediaResource,
251
+ rendererPipeline,
252
+ state: actionState,
253
+ });
301
254
  } catch (error) {
302
- if (error instanceof ToolcraftSceneExportError) {
255
+ if (
256
+ error instanceof ToolcraftSceneExportError ||
257
+ error instanceof ToolcraftArtifactExportError
258
+ ) {
303
259
  reportFeedback(error.feedback);
304
260
  result = undefined;
305
261
  } else {