@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
@@ -0,0 +1,113 @@
1
+ import type {
2
+ ResolvedToolcraftAppSchema,
3
+ ToolcraftActionSchema,
4
+ } from "@repo/toolcraft-runtime";
5
+
6
+ import { getControlActions } from "./actions";
7
+ import type {
8
+ ToolcraftComponentAcceptance,
9
+ ToolcraftExportArtifactCoverage,
10
+ } from "./types";
11
+
12
+ const requiredCoverageByRole = {
13
+ "export-image": "all-required-image-export-behavior",
14
+ "export-video": "all-required-video-export-behavior",
15
+ } as const satisfies Record<
16
+ "export-image" | "export-video",
17
+ ToolcraftExportArtifactCoverage
18
+ >;
19
+
20
+ function getCoverageValues(
21
+ entry: ToolcraftComponentAcceptance,
22
+ ): readonly ToolcraftExportArtifactCoverage[] {
23
+ if (!entry.exportArtifactCoverage) return [];
24
+ return typeof entry.exportArtifactCoverage === "string"
25
+ ? [entry.exportArtifactCoverage]
26
+ : entry.exportArtifactCoverage;
27
+ }
28
+
29
+ function isMediaExportAction(
30
+ action: ToolcraftActionSchema | string,
31
+ ): action is ToolcraftActionSchema & {
32
+ role: "export-image" | "export-video";
33
+ } {
34
+ return (
35
+ typeof action !== "string" &&
36
+ (action.role === "export-image" || action.role === "export-video")
37
+ );
38
+ }
39
+
40
+ function collectMediaExportActions(schema: ResolvedToolcraftAppSchema) {
41
+ return (schema.panels.controls?.sections ?? []).flatMap((section) =>
42
+ Object.values(section.controls).flatMap((control) =>
43
+ control.type !== "panelActions" || !control.target
44
+ ? []
45
+ : getControlActions(control)
46
+ .filter(isMediaExportAction)
47
+ .map((action) => ({
48
+ requiredCoverage: requiredCoverageByRole[action.role],
49
+ role: action.role,
50
+ target: control.target,
51
+ value: action.value,
52
+ })),
53
+ ),
54
+ );
55
+ }
56
+
57
+ export function getToolcraftExportArtifactCoverageErrors({
58
+ acceptance,
59
+ schema,
60
+ }: Readonly<{
61
+ acceptance: readonly ToolcraftComponentAcceptance[];
62
+ schema: ResolvedToolcraftAppSchema;
63
+ }>): string[] {
64
+ const errors: string[] = [];
65
+ const exportActions = collectMediaExportActions(schema);
66
+
67
+ for (const entry of acceptance) {
68
+ const coverage = getCoverageValues(entry);
69
+ if (coverage.length === 0) continue;
70
+ if (entry.evidence !== "exported-bytes") {
71
+ errors.push(
72
+ `Acceptance "${entry.id}" declares exportArtifactCoverage but evidence is not "exported-bytes".`,
73
+ );
74
+ }
75
+ if (!entry.automated || !entry.browser) {
76
+ errors.push(
77
+ `Acceptance "${entry.id}" exportArtifactCoverage requires automated and browser proof.`,
78
+ );
79
+ }
80
+ for (const value of coverage) {
81
+ const hasMatchingAction = exportActions.some(
82
+ (action) =>
83
+ action.target === entry.target &&
84
+ action.requiredCoverage === value &&
85
+ (entry.actionCoverage ?? []).includes(action.value),
86
+ );
87
+ if (!hasMatchingAction) {
88
+ errors.push(
89
+ `Acceptance "${entry.id}" declares ${value} without a matching typed export action in target/actionCoverage.`,
90
+ );
91
+ }
92
+ }
93
+ }
94
+
95
+ for (const action of exportActions) {
96
+ const hasCoverage = acceptance.some(
97
+ (entry) =>
98
+ entry.target === action.target &&
99
+ entry.evidence === "exported-bytes" &&
100
+ entry.automated &&
101
+ entry.browser &&
102
+ (entry.actionCoverage ?? []).includes(action.value) &&
103
+ getCoverageValues(entry).includes(action.requiredCoverage),
104
+ );
105
+ if (!hasCoverage) {
106
+ errors.push(
107
+ `Typed ${action.role} action "${action.value}" on "${action.target}" requires ${action.requiredCoverage} acceptance coverage.`,
108
+ );
109
+ }
110
+ }
111
+
112
+ return errors;
113
+ }
@@ -1,7 +1,7 @@
1
- import type {
2
- ResolvedToolcraftAppSchema,
3
- ResolvedToolcraftControlSchema,
4
- ToolcraftControlSchema,
1
+ import {
2
+ type ResolvedToolcraftAppSchema,
3
+ type ResolvedToolcraftControlSchema,
4
+ type ToolcraftControlSchema,
5
5
  } from "@repo/toolcraft-runtime";
6
6
 
7
7
  import {
@@ -9,10 +9,23 @@ import {
9
9
  isToolcraftProductSectionControl,
10
10
  } from "./controls";
11
11
  import { getToolcraftSectionLabel } from "./sections";
12
- import { getToolcraftLooseTargetPrefix } from "./semantic";
12
+ import {
13
+ getToolcraftLooseTargetPrefix,
14
+ normalizeToolcraftSemanticText,
15
+ } from "./semantic";
13
16
 
14
17
  const maxInlineSwitchLabelLength = 16;
15
18
  const maxInlineSwitchLabelWordCount = 2;
19
+ const normalizedVideoExportSectionTitle =
20
+ normalizeToolcraftSemanticText("Video Export");
21
+
22
+ type ToolcraftControlsSection = NonNullable<
23
+ ResolvedToolcraftAppSchema["panels"]["controls"]
24
+ >["sections"][number];
25
+
26
+ type ToolcraftControlLayoutGroup = NonNullable<
27
+ ToolcraftControlsSection["layoutGroups"]
28
+ >[number];
16
29
 
17
30
  function getInlineSwitchLabelText(
18
31
  controlId: string,
@@ -56,8 +69,42 @@ function controlsShareToolcraftTargetEntity(
56
69
  return Boolean(firstPrefix && firstPrefix === secondPrefix);
57
70
  }
58
71
 
72
+ function isToolcraftVideoExportSegmentedInlinePair({
73
+ layoutGroup,
74
+ section,
75
+ segmentedControlId,
76
+ }: {
77
+ layoutGroup: ToolcraftControlLayoutGroup;
78
+ section: ToolcraftControlsSection;
79
+ segmentedControlId: string;
80
+ }): boolean {
81
+ if (
82
+ normalizeToolcraftSemanticText(section.title) !==
83
+ normalizedVideoExportSectionTitle ||
84
+ layoutGroup.columns !== 2 ||
85
+ layoutGroup.controls.length !== 2
86
+ ) {
87
+ return false;
88
+ }
89
+
90
+ const formatControl = section.controls[segmentedControlId];
91
+ const resolutionControlId = layoutGroup.controls.find(
92
+ (controlId) => controlId !== segmentedControlId,
93
+ );
94
+ const resolutionControl = resolutionControlId
95
+ ? section.controls[resolutionControlId]
96
+ : undefined;
97
+
98
+ return (
99
+ formatControl?.type === "segmented" &&
100
+ formatControl.target === "export.video.format" &&
101
+ resolutionControl?.type === "select" &&
102
+ resolutionControl.target === "export.video.resolution"
103
+ );
104
+ }
105
+
59
106
  export function sectionHasInlineLayoutGroupForPair(
60
- section: NonNullable<ResolvedToolcraftAppSchema["panels"]["controls"]>["sections"][number],
107
+ section: ToolcraftControlsSection,
61
108
  firstControlId: string,
62
109
  secondControlId: string,
63
110
  ): boolean {
@@ -95,7 +142,13 @@ export function getToolcraftInlineLayoutErrors(
95
142
  }
96
143
 
97
144
  const segmentedIds = layoutGroup.controls.filter(
98
- (controlId) => section.controls[controlId]?.type === "segmented",
145
+ (controlId) =>
146
+ section.controls[controlId]?.type === "segmented" &&
147
+ !isToolcraftVideoExportSegmentedInlinePair({
148
+ layoutGroup,
149
+ section,
150
+ segmentedControlId: controlId,
151
+ }),
99
152
  );
100
153
 
101
154
  if (segmentedIds.length > 0) {
@@ -181,8 +234,10 @@ export function getToolcraftInlineLayoutErrors(
181
234
  !secondControlId ||
182
235
  !firstControl ||
183
236
  !secondControl ||
184
- firstControl.visibleWhen ||
185
- secondControl.visibleWhen ||
237
+ firstControl.applicability.mode ===
238
+ "conditional" ||
239
+ secondControl.applicability.mode ===
240
+ "conditional" ||
186
241
  !isBooleanControl(firstControl) ||
187
242
  !isBooleanControl(secondControl) ||
188
243
  !isInlineSwitchLabelSafe(firstControlId, firstControl) ||
@@ -148,7 +148,11 @@ export function getFileDropLifecycleCoverageErrors({
148
148
  );
149
149
  }
150
150
 
151
- if (control.multiple === true && !layersOwnMediaManagement) {
151
+ if (
152
+ control.multiple === true &&
153
+ control.variant !== "collection-actions" &&
154
+ !layersOwnMediaManagement
155
+ ) {
152
156
  if (!coverage.has("reorder") || !coverage.has("order-output")) {
153
157
  errors.push(
154
158
  `${label} multiple fileDrop acceptance must prove thumbnail/file reorder updates runtime media order and that preview, renderer, or export consumes that order.`,
@@ -1,10 +1,13 @@
1
1
  import type {
2
2
  ResolvedToolcraftAppSchema,
3
- ToolcraftControlSchema,
3
+ ResolvedToolcraftControlSchema,
4
+ } from "@repo/toolcraft-runtime";
5
+ import {
6
+ areToolcraftPredicateSetsProvablyExclusive,
7
+ getToolcraftApplicabilityPredicates,
4
8
  } from "@repo/toolcraft-runtime";
5
9
 
6
10
  import { isToolcraftProductSectionControl } from "./controls";
7
- import { areToolcraftConditionSetsProvablyExclusive } from "./condition-exclusivity";
8
11
 
9
12
  const vectorOrbitIntentPattern =
10
13
  /\b(camera\s+orbit|object\s+orbit|model\s+orbit|3d\s+(?:orientation|rotation)|view\s+orientation|model\s+rotation|object\s+rotation)\b/i;
@@ -34,7 +37,7 @@ function getCrossLength(
34
37
  );
35
38
  }
36
39
 
37
- function hasValidOrientationDefault(control: ToolcraftControlSchema): boolean {
40
+ function hasValidOrientationDefault(control: ResolvedToolcraftControlSchema): boolean {
38
41
  if (!control.defaultValue || typeof control.defaultValue !== "object") {
39
42
  return false;
40
43
  }
@@ -55,7 +58,7 @@ function getOrientationControlErrors({
55
58
  controlId,
56
59
  sectionTitle,
57
60
  }: {
58
- control: ToolcraftControlSchema;
61
+ control: ResolvedToolcraftControlSchema;
59
62
  controlId: string;
60
63
  sectionTitle: string;
61
64
  }): string[] {
@@ -112,16 +115,20 @@ export function getToolcraftOrientationGizmoErrors(
112
115
  }
113
116
 
114
117
  const leftConditions = [
115
- left.section.visibleWhen,
116
- left.control.visibleWhen,
117
- ].filter((condition) => condition !== undefined);
118
+ ...(left.section.visibleWhen ? [left.section.visibleWhen] : []),
119
+ ...getToolcraftApplicabilityPredicates(
120
+ left.control.applicability,
121
+ ),
122
+ ];
118
123
  const rightConditions = [
119
- right.section.visibleWhen,
120
- right.control.visibleWhen,
121
- ].filter((condition) => condition !== undefined);
124
+ ...(right.section.visibleWhen ? [right.section.visibleWhen] : []),
125
+ ...getToolcraftApplicabilityPredicates(
126
+ right.control.applicability,
127
+ ),
128
+ ];
122
129
 
123
130
  if (
124
- !areToolcraftConditionSetsProvablyExclusive(
131
+ !areToolcraftPredicateSetsProvablyExclusive(
125
132
  leftConditions,
126
133
  rightConditions,
127
134
  )
@@ -83,7 +83,7 @@ export function getToolcraftOutputBackgroundErrors({
83
83
 
84
84
  if (!schemaHasOutputBackgroundColorControl(controls)) {
85
85
  errors.push(
86
- "Product apps with Export PNG must expose a user-facing background color control such as appearance.background or scene.background. Preview, PNG export, and video export must read that runtime value instead of hardcoding the product background.",
86
+ "Product apps with artifact export must expose a user-facing background color control such as appearance.background or scene.background. Preview, image export, and video export must read that runtime value instead of hardcoding the product background.",
87
87
  );
88
88
  }
89
89
 
@@ -103,7 +103,7 @@ export function getToolcraftOutputBackgroundErrors({
103
103
 
104
104
  if (!schemaHasOutputBackgroundToggleControl(controls)) {
105
105
  errors.push(
106
- 'Product apps with Export PNG must expose export.includeBackground in runtime Setup as a Switch labeled "Background". PNG export must pass that runtime value to createToolcraftPngExportCanvas includeBackground; live preview must use shouldIncludeToolcraftPreviewBackground(state); video export keeps the background.',
106
+ 'Product apps with artifact export must expose export.includeBackground in runtime Setup as a Switch labeled "Background". Runtime artifact export owns image alpha and preserves the background for opaque image formats and video; bounded live preview must use shouldIncludeToolcraftPreviewBackground(state).',
107
107
  );
108
108
  }
109
109
 
@@ -1,20 +1,27 @@
1
+ import type { ToolcraftArtifactExportDelivery } from "./artifact-export-intent";
1
2
  import type { ToolcraftOutputExportFacts } from "./output-export-model";
2
3
 
3
- export function getToolcraftOutputExportLayoutErrors(
4
- facts: ToolcraftOutputExportFacts,
5
- ): string[] {
4
+ export function getToolcraftOutputExportLayoutErrors({
5
+ delivery,
6
+ facts,
7
+ }: {
8
+ delivery: ToolcraftArtifactExportDelivery;
9
+ facts: ToolcraftOutputExportFacts;
10
+ }): string[] {
11
+ if (!delivery.imageEnabled && !delivery.videoEnabled) {
12
+ return [];
13
+ }
14
+
6
15
  const errors: string[] = [];
7
16
 
8
17
  if (facts.backgroundSection) {
9
18
  errors.push(
10
- 'Product apps with Export PNG must not render a separate "Background" section; runtime Setup owns Background, Infinity canvas, and Color.',
19
+ 'Product apps with artifact export must not render a separate "Background" section; runtime Setup owns Background, Infinity canvas, and Color.',
11
20
  );
12
21
  }
13
22
 
14
23
  if (!facts.setupSection || facts.setupSectionIndex !== 0) {
15
- errors.push(
16
- "Runtime Setup must remain the first controls block.",
17
- );
24
+ errors.push("Runtime Setup must remain the first controls block.");
18
25
  }
19
26
 
20
27
  if (
@@ -23,18 +30,19 @@ export function getToolcraftOutputExportLayoutErrors(
23
30
  facts.finalExportSettingsIndex !== facts.panelActionsSectionIndex - 1
24
31
  ) {
25
32
  errors.push(
26
- 'Export settings must sit directly above sticky footer actions: Image Export for still apps, or Video Export after Image Export for animated apps.',
33
+ "The final enabled artifact export settings section must sit directly above sticky footer actions.",
27
34
  );
28
35
  }
29
36
 
30
37
  if (
31
- facts.hasVideoExportAction &&
38
+ delivery.imageEnabled &&
39
+ delivery.videoEnabled &&
32
40
  facts.imageExportSectionIndex >= 0 &&
33
41
  facts.videoExportSectionIndex >= 0 &&
34
42
  facts.imageExportSectionIndex !== facts.videoExportSectionIndex - 1
35
43
  ) {
36
44
  errors.push(
37
- 'Animated apps with both Export PNG and Export Video must place Image Export immediately before Video Export.',
45
+ 'Products with image and video delivery must place "Image Export" immediately before "Video Export".',
38
46
  );
39
47
  }
40
48
 
@@ -4,6 +4,10 @@ import type {
4
4
  } from "@repo/toolcraft-runtime";
5
5
 
6
6
  import { getControlLabelText } from "./controls";
7
+ import {
8
+ schemaHasPngExportPanelAction,
9
+ schemaHasVideoExportPanelAction,
10
+ } from "./output-export-actions";
7
11
  import { normalizeToolcraftSemanticText } from "./semantic";
8
12
 
9
13
  export type ToolcraftControlsSection = NonNullable<
@@ -14,6 +18,7 @@ export type ToolcraftOutputExportFacts = {
14
18
  backgroundColorEntry: readonly [string, ToolcraftControlSchema] | undefined;
15
19
  backgroundSection: ToolcraftControlsSection | undefined;
16
20
  finalExportSettingsIndex: number;
21
+ hasImageExportAction: boolean;
17
22
  hasVideoExportAction: boolean;
18
23
  imageExportSection: ToolcraftControlsSection | undefined;
19
24
  imageExportSectionIndex: number;
@@ -25,7 +30,10 @@ export type ToolcraftOutputExportFacts = {
25
30
  setupSection: ToolcraftControlsSection | undefined;
26
31
  setupSectionIndex: number;
27
32
  timelineEntry: readonly [string, ToolcraftControlSchema] | undefined;
33
+ videoExportSection: ToolcraftControlsSection | undefined;
28
34
  videoExportSectionIndex: number;
35
+ videoFormatEntry: readonly [string, ToolcraftControlSchema] | undefined;
36
+ videoResolutionEntry: readonly [string, ToolcraftControlSchema] | undefined;
29
37
  };
30
38
 
31
39
  export function getSearchableControlText({
@@ -47,9 +55,13 @@ export function getSearchableControlText({
47
55
  .replace(/([a-z])([A-Z])/g, "$1 $2");
48
56
  }
49
57
 
50
- function getFirstPanelActionsSectionIndex(schema: ResolvedToolcraftAppSchema): number {
58
+ function getFirstPanelActionsSectionIndex(
59
+ schema: ResolvedToolcraftAppSchema,
60
+ ): number {
51
61
  return (schema.panels.controls?.sections ?? []).findIndex((section) =>
52
- Object.values(section.controls).some((control) => control.type === "panelActions"),
62
+ Object.values(section.controls).some(
63
+ (control) => control.type === "panelActions",
64
+ ),
53
65
  );
54
66
  }
55
67
 
@@ -60,7 +72,8 @@ function getSchemaControlsSectionByTitle(
60
72
  const normalizedTitle = normalizeToolcraftSemanticText(title);
61
73
 
62
74
  return (schema.panels.controls?.sections ?? []).find(
63
- (section) => normalizeToolcraftSemanticText(section.title) === normalizedTitle,
75
+ (section) =>
76
+ normalizeToolcraftSemanticText(section.title) === normalizedTitle,
64
77
  );
65
78
  }
66
79
 
@@ -83,7 +96,9 @@ function getSectionControlEntryByTarget(
83
96
  return undefined;
84
97
  }
85
98
 
86
- return Object.entries(section.controls).find(([, control]) => control.target === target);
99
+ return Object.entries(section.controls).find(
100
+ ([, control]) => control.target === target,
101
+ );
87
102
  }
88
103
 
89
104
  function getOutputBackgroundColorEntry(
@@ -107,24 +122,43 @@ function getOutputBackgroundColorEntry(
107
122
  }
108
123
 
109
124
  export function buildToolcraftOutputExportFacts({
110
- hasVideoExportAction,
111
125
  schema,
112
126
  }: {
113
- hasVideoExportAction: boolean;
114
127
  schema: ResolvedToolcraftAppSchema;
115
128
  }): ToolcraftOutputExportFacts {
116
- const backgroundSection = getSchemaControlsSectionByTitle(schema, "Background");
129
+ const backgroundSection = getSchemaControlsSectionByTitle(
130
+ schema,
131
+ "Background",
132
+ );
117
133
  const setupSection = getSchemaControlsSectionByTitle(schema, "Setup");
118
- const imageExportSection = getSchemaControlsSectionByTitle(schema, "Image Export");
119
- const imageExportSectionIndex = getSchemaControlsSectionIndexByTitle(schema, "Image Export");
120
- const videoExportSectionIndex = getSchemaControlsSectionIndexByTitle(schema, "Video Export");
134
+ const imageExportSection = getSchemaControlsSectionByTitle(
135
+ schema,
136
+ "Image Export",
137
+ );
138
+ const videoExportSection = getSchemaControlsSectionByTitle(
139
+ schema,
140
+ "Video Export",
141
+ );
142
+ const imageExportSectionIndex = getSchemaControlsSectionIndexByTitle(
143
+ schema,
144
+ "Image Export",
145
+ );
146
+ const videoExportSectionIndex = getSchemaControlsSectionIndexByTitle(
147
+ schema,
148
+ "Video Export",
149
+ );
150
+ const hasImageExportAction = schemaHasPngExportPanelAction(schema);
151
+ const hasVideoExportAction = schemaHasVideoExportPanelAction(schema);
121
152
 
122
153
  return {
123
154
  backgroundColorEntry: getOutputBackgroundColorEntry(setupSection),
124
155
  backgroundSection,
125
156
  finalExportSettingsIndex: hasVideoExportAction
126
157
  ? videoExportSectionIndex
127
- : imageExportSectionIndex,
158
+ : hasImageExportAction
159
+ ? imageExportSectionIndex
160
+ : -1,
161
+ hasImageExportAction,
128
162
  hasVideoExportAction,
129
163
  imageExportSection,
130
164
  imageExportSectionIndex,
@@ -151,6 +185,15 @@ export function buildToolcraftOutputExportFacts({
151
185
  setupSection,
152
186
  "panels.timeline.extended",
153
187
  ),
188
+ videoExportSection,
154
189
  videoExportSectionIndex,
190
+ videoFormatEntry: getSectionControlEntryByTarget(
191
+ videoExportSection,
192
+ "export.video.format",
193
+ ),
194
+ videoResolutionEntry: getSectionControlEntryByTarget(
195
+ videoExportSection,
196
+ "export.video.resolution",
197
+ ),
155
198
  };
156
199
  }
@@ -1,5 +1,6 @@
1
1
  import type { ResolvedToolcraftAppSchema } from "@repo/toolcraft-runtime";
2
2
 
3
+ import { validateToolcraftArtifactExportIntentCorrespondence } from "./artifact-export-intent";
3
4
  import {
4
5
  getToolcraftOutputBackgroundErrors,
5
6
  isOutputBackgroundToggleControl,
@@ -11,7 +12,13 @@ import {
11
12
  import { getToolcraftOutputExportLayoutErrors } from "./output-export-layout-rules";
12
13
  import { buildToolcraftOutputExportFacts } from "./output-export-model";
13
14
  import { getToolcraftImageExportErrors } from "./output-image-export-rules";
14
- import type { ToolcraftVisibleControl } from "./types";
15
+ import { getToolcraftVideoExportErrors } from "./output-video-export-rules";
16
+ import { getToolcraftExportArtifactCoverageErrors } from "./export-artifact-coverage";
17
+ import type {
18
+ ToolcraftComponentAcceptance,
19
+ ToolcraftProductReadiness,
20
+ ToolcraftVisibleControl,
21
+ } from "./types";
15
22
 
16
23
  export {
17
24
  isOutputBackgroundToggleControl,
@@ -20,26 +27,42 @@ export {
20
27
  };
21
28
 
22
29
  export function getToolcraftOutputExportErrors({
30
+ acceptance,
23
31
  controls,
32
+ productReadiness,
24
33
  schema,
25
- hasVideoExportAction = schemaHasVideoExportPanelAction(schema),
26
34
  }: {
35
+ acceptance: readonly ToolcraftComponentAcceptance[];
27
36
  controls: readonly ToolcraftVisibleControl[];
28
- hasVideoExportAction?: boolean;
37
+ productReadiness: ToolcraftProductReadiness;
29
38
  schema: ResolvedToolcraftAppSchema;
30
39
  }): string[] {
31
- if (!schemaHasPngExportPanelAction(schema)) {
32
- return [];
33
- }
34
-
35
- const facts = buildToolcraftOutputExportFacts({
36
- hasVideoExportAction,
40
+ const exportArtifactCoverageErrors = getToolcraftExportArtifactCoverageErrors({
41
+ acceptance,
37
42
  schema,
38
43
  });
44
+ if (productReadiness.mode === "starter") {
45
+ return exportArtifactCoverageErrors;
46
+ }
47
+
48
+ const facts = buildToolcraftOutputExportFacts({ schema });
49
+ const { delivery, errors: intentErrors } =
50
+ validateToolcraftArtifactExportIntentCorrespondence({
51
+ hasImageExportAction: facts.hasImageExportAction,
52
+ hasImageExportSection: facts.imageExportSection !== undefined,
53
+ hasVideoExportAction: facts.hasVideoExportAction,
54
+ hasVideoExportSection: facts.videoExportSection !== undefined,
55
+ intent: productReadiness.exportIntent,
56
+ });
39
57
 
40
58
  return [
41
- ...getToolcraftOutputExportLayoutErrors(facts),
42
- ...getToolcraftOutputBackgroundErrors({ controls, facts }),
43
- ...getToolcraftImageExportErrors(facts),
59
+ ...intentErrors,
60
+ ...(delivery.imageEnabled ? getToolcraftImageExportErrors(facts) : []),
61
+ ...(delivery.videoEnabled ? getToolcraftVideoExportErrors(facts) : []),
62
+ ...(delivery.imageEnabled || delivery.videoEnabled
63
+ ? getToolcraftOutputBackgroundErrors({ controls, facts })
64
+ : []),
65
+ ...getToolcraftOutputExportLayoutErrors({ delivery, facts }),
66
+ ...exportArtifactCoverageErrors,
44
67
  ];
45
68
  }
@@ -0,0 +1,99 @@
1
+ import { sectionHasInlineLayoutGroupForPair } from "./inline-layout";
2
+ import type { ToolcraftOutputExportFacts } from "./output-export-model";
3
+
4
+ function hasExactOptionValueSet(
5
+ actual: readonly string[],
6
+ expected: readonly string[],
7
+ ): boolean {
8
+ return (
9
+ actual.length === expected.length &&
10
+ new Set(actual).size === expected.length &&
11
+ expected.every((value) => actual.includes(value))
12
+ );
13
+ }
14
+
15
+ export function getToolcraftVideoExportErrors(
16
+ facts: ToolcraftOutputExportFacts,
17
+ ): string[] {
18
+ const errors: string[] = [];
19
+ const videoFormatControl = facts.videoFormatEntry?.[1];
20
+ const videoResolutionControl = facts.videoResolutionEntry?.[1];
21
+ const videoFormatOptionValues =
22
+ videoFormatControl?.options?.map((option) => option.value.toLowerCase()) ??
23
+ [];
24
+ const videoResolutionOptionValues =
25
+ videoResolutionControl?.options?.map((option) =>
26
+ option.value.toLowerCase(),
27
+ ) ?? [];
28
+
29
+ if (!facts.videoExportSection) {
30
+ errors.push(
31
+ 'Apps with Export Video must expose video export settings in a separate controls section titled "Video Export" directly above sticky footer export actions.',
32
+ );
33
+ }
34
+
35
+ if (!videoFormatControl) {
36
+ errors.push(
37
+ 'The separate "Video Export" section must include a format control with target "export.video.format".',
38
+ );
39
+ } else {
40
+ if (
41
+ videoFormatControl.type !== "select" &&
42
+ videoFormatControl.type !== "segmented"
43
+ ) {
44
+ errors.push("Video Export format must be a Select or Segmented control.");
45
+ }
46
+
47
+ if (!hasExactOptionValueSet(videoFormatOptionValues, ["mp4", "webm"])) {
48
+ errors.push(
49
+ 'Video Export format options must be exactly "mp4" and "webm".',
50
+ );
51
+ }
52
+
53
+ if (videoFormatControl.defaultValue !== "mp4") {
54
+ errors.push('Video Export format must default to "mp4".');
55
+ }
56
+ }
57
+
58
+ if (!videoResolutionControl) {
59
+ errors.push(
60
+ 'The separate "Video Export" section must include a resolution control with target "export.video.resolution".',
61
+ );
62
+ } else {
63
+ if (videoResolutionControl.type !== "select") {
64
+ errors.push("Video Export resolution must be a Select control.");
65
+ }
66
+
67
+ if (
68
+ !hasExactOptionValueSet(videoResolutionOptionValues, ["current", "4k"])
69
+ ) {
70
+ errors.push(
71
+ 'Video Export resolution options must be exactly "current" and "4k".',
72
+ );
73
+ }
74
+
75
+ if (videoResolutionControl.defaultValue !== "current") {
76
+ errors.push('Video Export resolution must default to "current".');
77
+ }
78
+ }
79
+
80
+ const videoFormatControlId = facts.videoFormatEntry?.[0];
81
+ const videoResolutionControlId = facts.videoResolutionEntry?.[0];
82
+ const videoExportHasInlinePair =
83
+ facts.videoExportSection !== undefined &&
84
+ videoFormatControlId !== undefined &&
85
+ videoResolutionControlId !== undefined &&
86
+ sectionHasInlineLayoutGroupForPair(
87
+ facts.videoExportSection,
88
+ videoFormatControlId,
89
+ videoResolutionControlId,
90
+ );
91
+
92
+ if (!videoExportHasInlinePair) {
93
+ errors.push(
94
+ "Video Export format and resolution must render as one exact two-column inline row.",
95
+ );
96
+ }
97
+
98
+ return errors;
99
+ }