@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,370 @@
1
+ import { expect } from "@playwright/test";
2
+
3
+ import { attachToolcraftBrowserRuntimeEvidence } from "./browser-runtime-evidence";
4
+ import {
5
+ assertToolcraftBrowserProofSession,
6
+ getToolcraftBrowserActionTarget,
7
+ readToolcraftBrowserObservation,
8
+ runToolcraftBrowserAction,
9
+ runToolcraftBrowserValueAction,
10
+ type ToolcraftBrowserAction,
11
+ type ToolcraftBrowserObservation,
12
+ type ToolcraftBrowserProofSession,
13
+ } from "./browser-proof-session";
14
+ import {
15
+ assertToolcraftProducedArtifact,
16
+ validateToolcraftExportArtifactInspection,
17
+ type ToolcraftExportArtifactInspection,
18
+ } from "./export-artifact-helpers";
19
+
20
+ declare const toolcraftUnavailableImageResourceFixtureBrand: unique symbol;
21
+
22
+ export type ToolcraftUnavailableImageResourceObservation = Readonly<{
23
+ assetId?: string;
24
+ fileName?: string;
25
+ lifecycle?: "ready" | "unavailable";
26
+ message?: string;
27
+ requestId?: string;
28
+ resolverAvailable?: boolean;
29
+ status: "active" | "cleaned" | "error" | "missing";
30
+ }>;
31
+
32
+ export type ToolcraftUnavailableImageResourceFixture = Readonly<{
33
+ readonly [toolcraftUnavailableImageResourceFixtureBrand]: true;
34
+ }>;
35
+
36
+ type ToolcraftUnavailableImageResourceFixtureRecord = Readonly<{
37
+ activate: ToolcraftBrowserAction;
38
+ cleanup: ToolcraftBrowserAction;
39
+ fileName: string;
40
+ observe: ToolcraftBrowserObservation<string | null>;
41
+ requestId: string;
42
+ target: string;
43
+ }>;
44
+
45
+ const unavailableImageResourceFixtureRecords = new WeakMap<
46
+ object,
47
+ ToolcraftUnavailableImageResourceFixtureRecord
48
+ >();
49
+ const malformedUnavailableImageResourceObservation = Object.freeze({
50
+ message: "Unavailable-resource proof published malformed state.",
51
+ status: "error" as const,
52
+ });
53
+ let unavailableImageResourceFixtureSequence = 0;
54
+
55
+ function requireUnavailableImageResourceFixture(
56
+ fixture: ToolcraftUnavailableImageResourceFixture,
57
+ ): ToolcraftUnavailableImageResourceFixtureRecord {
58
+ const record =
59
+ typeof fixture === "object" && fixture !== null
60
+ ? unavailableImageResourceFixtureRecords.get(fixture)
61
+ : undefined;
62
+ if (!record) {
63
+ throw new Error(
64
+ "Unavailable-image export evidence requires the protected Toolcraft unavailable-resource fixture facade.",
65
+ );
66
+ }
67
+ return record;
68
+ }
69
+
70
+ function parseUnavailableImageResourceObservation(
71
+ raw: string | null,
72
+ ): ToolcraftUnavailableImageResourceObservation {
73
+ if (!raw) return { status: "missing" };
74
+ let value: unknown;
75
+ try {
76
+ value = JSON.parse(raw);
77
+ } catch {
78
+ return malformedUnavailableImageResourceObservation;
79
+ }
80
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
81
+ return malformedUnavailableImageResourceObservation;
82
+ }
83
+ const observation = value as Record<string, unknown>;
84
+ const optionalStringsAreValid = [
85
+ "assetId",
86
+ "fileName",
87
+ "message",
88
+ "requestId",
89
+ ].every(
90
+ (field) =>
91
+ observation[field] === undefined ||
92
+ typeof observation[field] === "string",
93
+ );
94
+ const valid =
95
+ typeof observation.status === "string" &&
96
+ ["active", "cleaned", "error", "missing"].includes(observation.status) &&
97
+ optionalStringsAreValid &&
98
+ (
99
+ observation.resolverAvailable === undefined ||
100
+ typeof observation.resolverAvailable === "boolean"
101
+ ) &&
102
+ (
103
+ observation.lifecycle === undefined ||
104
+ observation.lifecycle === "ready" ||
105
+ observation.lifecycle === "unavailable"
106
+ );
107
+ return valid
108
+ ? observation as ToolcraftUnavailableImageResourceObservation
109
+ : malformedUnavailableImageResourceObservation;
110
+ }
111
+
112
+ async function readUnavailableImageResourceObservation(
113
+ observation: ToolcraftBrowserObservation<string | null>,
114
+ ): Promise<ToolcraftUnavailableImageResourceObservation> {
115
+ const raw = await readToolcraftBrowserObservation(observation);
116
+ return parseUnavailableImageResourceObservation(raw);
117
+ }
118
+
119
+ function validateUnavailableImageResourceObservation(
120
+ observation: ToolcraftUnavailableImageResourceObservation,
121
+ expected: Readonly<{
122
+ fileName: string;
123
+ requestId: string;
124
+ status: "active" | "cleaned";
125
+ }>,
126
+ ): void {
127
+ if (observation.status === "error") {
128
+ throw new Error(
129
+ observation.message ??
130
+ "The Toolcraft unavailable-resource proof fixture failed.",
131
+ );
132
+ }
133
+ expect(
134
+ observation,
135
+ `Unavailable-resource proof "${expected.requestId}" must publish its exact typed state.`,
136
+ ).toMatchObject(
137
+ expected.status === "active"
138
+ ? {
139
+ fileName: expected.fileName,
140
+ lifecycle: "unavailable",
141
+ requestId: expected.requestId,
142
+ resolverAvailable: false,
143
+ status: "active",
144
+ }
145
+ : {
146
+ fileName: expected.fileName,
147
+ lifecycle: "ready",
148
+ requestId: expected.requestId,
149
+ resolverAvailable: true,
150
+ status: "cleaned",
151
+ },
152
+ );
153
+ expect(
154
+ observation.assetId?.trim(),
155
+ "Unavailable-resource proof must identify the exact affected runtime asset.",
156
+ ).toBeTruthy();
157
+ }
158
+
159
+ export function createToolcraftUnavailableImageResourceFixture(
160
+ session: ToolcraftBrowserProofSession,
161
+ {
162
+ fileName,
163
+ sourceTarget,
164
+ target,
165
+ }: Readonly<{
166
+ fileName: string;
167
+ sourceTarget?: string;
168
+ target: string;
169
+ }>,
170
+ ): ToolcraftUnavailableImageResourceFixture {
171
+ const normalizedFileName = fileName.trim();
172
+ const normalizedSourceTarget = sourceTarget?.trim();
173
+ const normalizedTarget = target.trim();
174
+ expect(
175
+ normalizedFileName,
176
+ "Unavailable-resource proof requires an exact non-empty image file name.",
177
+ ).not.toBe("");
178
+ expect(
179
+ normalizedTarget,
180
+ "Unavailable-resource proof requires an exact semantic acceptance target.",
181
+ ).not.toBe("");
182
+ if (sourceTarget !== undefined) {
183
+ expect(
184
+ normalizedSourceTarget,
185
+ "Unavailable-resource proof sourceTarget must be non-empty when provided.",
186
+ ).not.toBe("");
187
+ }
188
+
189
+ const requestId =
190
+ `unavailable-image-${++unavailableImageResourceFixtureSequence}`;
191
+ const request = {
192
+ fileName: normalizedFileName,
193
+ requestId,
194
+ ...(normalizedSourceTarget
195
+ ? { sourceTarget: normalizedSourceTarget }
196
+ : {}),
197
+ };
198
+ const runRequest = (
199
+ action: "activate" | "cleanup",
200
+ expectedStatus: "active" | "cleaned",
201
+ ) =>
202
+ session.targetAction(normalizedTarget, async (page) => {
203
+ await page.evaluate(
204
+ ({ detail, eventName }) => {
205
+ document.dispatchEvent(
206
+ new CustomEvent(eventName, {
207
+ detail,
208
+ }),
209
+ );
210
+ },
211
+ {
212
+ detail: { ...request, action },
213
+ eventName:
214
+ "toolcraft.browser-proof.unavailable-resource-request",
215
+ },
216
+ );
217
+ await expect
218
+ .poll(
219
+ async () => {
220
+ const raw = await page
221
+ .locator('[data-slot="toolcraft-runtime-app"]')
222
+ .getAttribute(
223
+ "data-toolcraft-unavailable-resource-evidence",
224
+ );
225
+ const observation =
226
+ parseUnavailableImageResourceObservation(raw);
227
+ if (
228
+ observation.status === "error" &&
229
+ (
230
+ observation.requestId === undefined ||
231
+ observation.requestId === requestId
232
+ )
233
+ ) {
234
+ throw new Error(
235
+ observation.message ??
236
+ "The Toolcraft unavailable-resource proof fixture failed.",
237
+ );
238
+ }
239
+ return observation.requestId === requestId
240
+ ? observation.status
241
+ : "other-request";
242
+ },
243
+ {
244
+ message:
245
+ `Unavailable-resource proof "${requestId}" should reach ${expectedStatus}.`,
246
+ },
247
+ )
248
+ .toBe(expectedStatus);
249
+ });
250
+ const activate = runRequest("activate", "active");
251
+ const cleanup = runRequest("cleanup", "cleaned");
252
+ const observe = session.observe((root) =>
253
+ root.getAttribute("data-toolcraft-unavailable-resource-evidence"),
254
+ );
255
+ const fixture = Object.freeze({}) as ToolcraftUnavailableImageResourceFixture;
256
+ unavailableImageResourceFixtureRecords.set(fixture, {
257
+ activate,
258
+ cleanup,
259
+ fileName: normalizedFileName,
260
+ observe,
261
+ requestId,
262
+ target: normalizedTarget,
263
+ });
264
+ return fixture;
265
+ }
266
+
267
+ export async function expectToolcraftInfinityCanvasUnavailableImageExportEvidence<
268
+ TArtifact,
269
+ >(
270
+ fixture: ToolcraftUnavailableImageResourceFixture,
271
+ produceArtifact: ToolcraftBrowserAction<"interaction", TArtifact>,
272
+ inspectArtifact: (
273
+ artifact: TArtifact,
274
+ ) => Promise<ToolcraftExportArtifactInspection> | ToolcraftExportArtifactInspection,
275
+ {
276
+ expectedSize,
277
+ requirementId,
278
+ }: Readonly<{
279
+ expectedSize: Readonly<{ height: number; width: number }>;
280
+ requirementId: string;
281
+ }>,
282
+ ): Promise<ToolcraftExportArtifactInspection> {
283
+ const record = requireUnavailableImageResourceFixture(fixture);
284
+ assertToolcraftBrowserProofSession(
285
+ record.observe,
286
+ record.activate,
287
+ record.cleanup,
288
+ produceArtifact,
289
+ );
290
+ const exportTarget = getToolcraftBrowserActionTarget(produceArtifact)?.trim();
291
+ expect(
292
+ exportTarget,
293
+ "Unavailable-image export evidence requires a target-scoped artifact action.",
294
+ ).toBe(record.target);
295
+
296
+ let activationAttempted = false;
297
+ let cleanupSucceeded = false;
298
+ let inspection: ToolcraftExportArtifactInspection | null = null;
299
+ let outputVerified = false;
300
+ try {
301
+ activationAttempted = true;
302
+ await runToolcraftBrowserAction(record.activate);
303
+ validateUnavailableImageResourceObservation(
304
+ await readUnavailableImageResourceObservation(record.observe),
305
+ {
306
+ fileName: record.fileName,
307
+ requestId: record.requestId,
308
+ status: "active",
309
+ },
310
+ );
311
+
312
+ const artifact = await runToolcraftBrowserValueAction(produceArtifact);
313
+ assertToolcraftProducedArtifact(artifact, requirementId);
314
+ inspection = await inspectArtifact(artifact);
315
+ validateToolcraftExportArtifactInspection(inspection, requirementId);
316
+ expect(
317
+ inspection,
318
+ `Unavailable-image export "${requirementId}" must decode to the expected visible scene bounds.`,
319
+ ).toMatchObject(expectedSize);
320
+ validateUnavailableImageResourceObservation(
321
+ await readUnavailableImageResourceObservation(record.observe),
322
+ {
323
+ fileName: record.fileName,
324
+ requestId: record.requestId,
325
+ status: "active",
326
+ },
327
+ );
328
+ outputVerified = true;
329
+ } finally {
330
+ const current = activationAttempted
331
+ ? await readUnavailableImageResourceObservation(record.observe)
332
+ : null;
333
+ if (
334
+ current?.requestId === record.requestId &&
335
+ current.status === "active"
336
+ ) {
337
+ await runToolcraftBrowserAction(record.cleanup);
338
+ validateUnavailableImageResourceObservation(
339
+ await readUnavailableImageResourceObservation(record.observe),
340
+ {
341
+ fileName: record.fileName,
342
+ requestId: record.requestId,
343
+ status: "cleaned",
344
+ },
345
+ );
346
+ cleanupSucceeded = true;
347
+ } else if (outputVerified) {
348
+ throw new Error(
349
+ "Unavailable-image export evidence requires the exact active fixture to remain available for deterministic cleanup.",
350
+ );
351
+ }
352
+ }
353
+
354
+ if (!cleanupSucceeded || !inspection) {
355
+ throw new Error(
356
+ "Unavailable-image export evidence requires successful fixture cleanup before publication.",
357
+ );
358
+ }
359
+ await attachToolcraftBrowserRuntimeEvidence({
360
+ evidenceType: "exported-artifact",
361
+ requirementId,
362
+ target: exportTarget,
363
+ });
364
+ await attachToolcraftBrowserRuntimeEvidence({
365
+ evidenceType: "infinity-scene-bounds-image-export",
366
+ requirementId,
367
+ target: exportTarget,
368
+ });
369
+ return inspection;
370
+ }
@@ -0,0 +1,77 @@
1
+ import fs from "node:fs/promises";
2
+
3
+ import { expect, test, type Download, type Page } from "@playwright/test";
4
+
5
+ import { expectToolcraftImageExportArtifact } from "./browser-media-export-evidence";
6
+
7
+ async function createImageFixture(
8
+ page: Page,
9
+ ): Promise<Buffer> {
10
+ const base64 = await page.evaluate(() => {
11
+ const canvas = document.createElement("canvas");
12
+ canvas.width = 64;
13
+ canvas.height = 64;
14
+ const context = canvas.getContext("2d");
15
+ if (!context) throw new Error("Missing test canvas context.");
16
+ context.fillStyle = "#000000";
17
+ context.fillRect(0, 0, 64, 64);
18
+ context.fillStyle = "#FF0000";
19
+ context.fillRect(16, 16, 32, 32);
20
+ return canvas.toDataURL("image/png").split(",")[1]!;
21
+ });
22
+ return Buffer.from(base64, "base64");
23
+ }
24
+
25
+ test("protected image evidence attaches only after decoded semantics pass", async ({
26
+ page,
27
+ }, testInfo) => {
28
+ await page.goto("/");
29
+ const artifactPath = testInfo.outputPath("protected-image.png");
30
+ await fs.writeFile(artifactPath, await createImageFixture(page));
31
+ const download = { path: async () => artifactPath } as Download;
32
+
33
+ await expectToolcraftImageExportArtifact(async () => download, {
34
+ backgroundRgba: [0, 0, 0, 255],
35
+ expectedBounds: { height: 0.5, width: 0.5, x: 0.25, y: 0.25 },
36
+ expectedHeight: 64,
37
+ expectedMediaType: "image/png",
38
+ expectedPixels: [
39
+ { rgba: [0, 0, 0, 255], xRatio: 0.1, yRatio: 0.1 },
40
+ { rgba: [255, 0, 0, 255], xRatio: 0.5, yRatio: 0.5 },
41
+ ],
42
+ expectedWidth: 64,
43
+ page,
44
+ requirementId: "fixture.image",
45
+ });
46
+
47
+ expect(
48
+ test.info().attachments.some(
49
+ ({ name }) => name === "toolcraft.browser-runtime-evidence",
50
+ ),
51
+ ).toBe(true);
52
+ });
53
+
54
+ test("protected image evidence rejects empty product expectations before download", async ({
55
+ page,
56
+ }) => {
57
+ let called = false;
58
+ await expect(
59
+ expectToolcraftImageExportArtifact(
60
+ async () => {
61
+ called = true;
62
+ throw new Error("must not run");
63
+ },
64
+ {
65
+ backgroundRgba: [0, 0, 0, 255],
66
+ expectedBounds: { height: 0.5, width: 0.5, x: 0.25, y: 0.25 },
67
+ expectedHeight: 64,
68
+ expectedMediaType: "image/png",
69
+ expectedPixels: [],
70
+ expectedWidth: 64,
71
+ page,
72
+ requirementId: "fixture.empty",
73
+ },
74
+ ),
75
+ ).rejects.toThrow("at least one exact pixel sample");
76
+ expect(called).toBe(false);
77
+ });
@@ -0,0 +1,208 @@
1
+ import { expect, type Download, type Page } from "@playwright/test";
2
+
3
+ import {
4
+ assertToolcraftExpectedDecodedPixels,
5
+ getToolcraftRgbaDistance,
6
+ TOOLCRAFT_BACKGROUND_PIXEL_DISTANCE_THRESHOLD,
7
+ validateToolcraftExpectedDecodedPixels,
8
+ type ToolcraftExpectedDecodedPixel,
9
+ type ToolcraftExpectedDecodedVideoSample,
10
+ } from "./decoded-pixel-observation";
11
+ import type { ToolcraftNormalizedPixelBounds } from "./export-artifact-helpers";
12
+ import { inspectToolcraftImageDownload } from "./image-artifact-inspection";
13
+ import { attachToolcraftBrowserRuntimeEvidence } from "./browser-runtime-evidence";
14
+ import {
15
+ assertToolcraftVideoPacketSchedule,
16
+ inspectToolcraftVideoDownload,
17
+ type ToolcraftInspectedVideoArtifact,
18
+ type ToolcraftScheduledVideoFrame,
19
+ } from "./video-artifact-inspection";
20
+ import {
21
+ getToolcraftBrowserActionTarget,
22
+ isToolcraftBrowserAction,
23
+ runToolcraftBrowserValueAction,
24
+ type ToolcraftBrowserAction,
25
+ } from "./browser-proof-session";
26
+
27
+ type AdditionalArtifactRequirement = Readonly<{
28
+ requirementId: string;
29
+ target: string;
30
+ }>;
31
+
32
+ async function attachAdditionalArtifactRequirements(
33
+ requirements: readonly AdditionalArtifactRequirement[] | undefined,
34
+ ): Promise<void> {
35
+ for (const requirement of requirements ?? []) {
36
+ await attachToolcraftBrowserRuntimeEvidence({
37
+ evidenceType: "exported-artifact",
38
+ requirementId: requirement.requirementId,
39
+ target: requirement.target,
40
+ });
41
+ }
42
+ }
43
+
44
+ const normalizedBoundsTolerance = 2 / 64;
45
+
46
+ function assertContainsProductExpectation(
47
+ expectedPixels: readonly ToolcraftExpectedDecodedPixel[],
48
+ backgroundRgba: readonly [number, number, number, number],
49
+ ): void {
50
+ validateToolcraftExpectedDecodedPixels(expectedPixels);
51
+ if (
52
+ !expectedPixels.some(
53
+ ({ rgba }) =>
54
+ getToolcraftRgbaDistance(rgba, backgroundRgba) >
55
+ TOOLCRAFT_BACKGROUND_PIXEL_DISTANCE_THRESHOLD,
56
+ )
57
+ ) {
58
+ throw new Error(
59
+ "Media export evidence requires at least one non-background product pixel expectation.",
60
+ );
61
+ }
62
+ }
63
+
64
+ function assertExpectedBounds(
65
+ actual: ToolcraftNormalizedPixelBounds | null,
66
+ expected: ToolcraftNormalizedPixelBounds,
67
+ ): void {
68
+ expect(actual, "Decoded export must contain product pixels.").not.toBeNull();
69
+ if (!actual) return;
70
+ for (const field of ["height", "width", "x", "y"] as const) {
71
+ expect(
72
+ Math.abs(actual[field] - expected[field]),
73
+ `Decoded product ${field} must match the expected normalized bounds.`,
74
+ ).toBeLessThanOrEqual(normalizedBoundsTolerance);
75
+ }
76
+ }
77
+
78
+ export async function expectToolcraftImageExportArtifact(
79
+ produceDownload:
80
+ | (() => Promise<Download>)
81
+ | ToolcraftBrowserAction<"interaction", Download>,
82
+ options: Readonly<{
83
+ additionalArtifactRequirements?: readonly AdditionalArtifactRequirement[];
84
+ backgroundRgba: readonly [number, number, number, number];
85
+ expectedBounds: ToolcraftNormalizedPixelBounds;
86
+ expectedHeight: number;
87
+ expectedMediaType: "image/jpeg" | "image/png";
88
+ expectedPixels: readonly ToolcraftExpectedDecodedPixel[];
89
+ expectedWidth: number;
90
+ page: Page;
91
+ requirementId: string;
92
+ }>,
93
+ ): Promise<void> {
94
+ assertContainsProductExpectation(options.expectedPixels, options.backgroundRgba);
95
+ const target = isToolcraftBrowserAction(produceDownload)
96
+ ? getToolcraftBrowserActionTarget(produceDownload)
97
+ : undefined;
98
+ const download = isToolcraftBrowserAction(produceDownload)
99
+ ? await runToolcraftBrowserValueAction(produceDownload)
100
+ : await produceDownload();
101
+ const inspected = await inspectToolcraftImageDownload({
102
+ backgroundRgba: options.backgroundRgba,
103
+ download,
104
+ page: options.page,
105
+ });
106
+ expect(inspected.inspection.byteLength).toBeGreaterThan(0);
107
+ expect(inspected.inspection.mediaType).toBe(options.expectedMediaType);
108
+ expect(inspected.inspection.width).toBe(options.expectedWidth);
109
+ expect(inspected.inspection.height).toBe(options.expectedHeight);
110
+ expect(inspected.inspection.decodedPixelHash).not.toHaveLength(0);
111
+ assertExpectedBounds(
112
+ inspected.inspection.nonBackgroundBounds,
113
+ options.expectedBounds,
114
+ );
115
+ assertToolcraftExpectedDecodedPixels({
116
+ expectedPixels: options.expectedPixels,
117
+ mediaType: inspected.inspection.mediaType,
118
+ observation: inspected.observation,
119
+ });
120
+ await attachAdditionalArtifactRequirements(
121
+ options.additionalArtifactRequirements,
122
+ );
123
+ await attachToolcraftBrowserRuntimeEvidence({
124
+ evidenceType: "image-export-artifact",
125
+ requirementId: options.requirementId,
126
+ target,
127
+ });
128
+ }
129
+
130
+ export async function expectToolcraftVideoExportArtifact(
131
+ produceDownload:
132
+ | (() => Promise<Download>)
133
+ | ToolcraftBrowserAction<"interaction", Download>,
134
+ options: Readonly<{
135
+ additionalArtifactRequirements?: readonly AdditionalArtifactRequirement[];
136
+ animated: boolean;
137
+ backgroundRgba: readonly [number, number, number, number];
138
+ expectedDurationSeconds: number;
139
+ expectedHeight: number;
140
+ expectedMediaType: "video/mp4" | "video/webm";
141
+ expectedSamples: readonly ToolcraftExpectedDecodedVideoSample[];
142
+ expectedWidth: number;
143
+ page: Page;
144
+ requirementId: string;
145
+ schedule: readonly ToolcraftScheduledVideoFrame[];
146
+ }>,
147
+ ): Promise<ToolcraftInspectedVideoArtifact> {
148
+ if (options.schedule.length === 0) {
149
+ throw new Error("Video export evidence requires the runtime frame schedule.");
150
+ }
151
+ for (const sample of options.expectedSamples) {
152
+ assertContainsProductExpectation(sample.pixels, options.backgroundRgba);
153
+ }
154
+ const target = isToolcraftBrowserAction(produceDownload)
155
+ ? getToolcraftBrowserActionTarget(produceDownload)
156
+ : undefined;
157
+ const download = isToolcraftBrowserAction(produceDownload)
158
+ ? await runToolcraftBrowserValueAction(produceDownload)
159
+ : await produceDownload();
160
+ const inspected = await inspectToolcraftVideoDownload({
161
+ backgroundRgba: options.backgroundRgba,
162
+ download,
163
+ page: options.page,
164
+ schedule: options.schedule,
165
+ });
166
+ const toleranceSeconds = 1 / inspected.timeResolution;
167
+ expect(inspected.inspection.byteLength).toBeGreaterThan(0);
168
+ expect(inspected.inspection.mediaType).toBe(options.expectedMediaType);
169
+ expect(inspected.inspection.width).toBe(options.expectedWidth);
170
+ expect(inspected.inspection.height).toBe(options.expectedHeight);
171
+ expect(inspected.inspection.frameCount).toBe(options.schedule.length);
172
+ expect(
173
+ Math.abs(
174
+ inspected.inspection.durationMs - options.expectedDurationSeconds * 1000,
175
+ ),
176
+ ).toBeLessThanOrEqual(toleranceSeconds * 1000);
177
+ assertToolcraftVideoPacketSchedule({
178
+ packetTimings: inspected.inspection.packetTimings,
179
+ schedule: options.schedule,
180
+ timeResolution: inspected.timeResolution,
181
+ });
182
+ expect(inspected.sampleTimes).toEqual(
183
+ options.expectedSamples.map(({ timeSeconds }) => timeSeconds),
184
+ );
185
+ expect(inspected.observations).toHaveLength(options.expectedSamples.length);
186
+ for (const [index, observation] of inspected.observations.entries()) {
187
+ const expected = options.expectedSamples[index];
188
+ if (!expected) throw new Error(`Missing expected decoded sample ${index}.`);
189
+ expect(observation.nonBackgroundBounds).not.toBeNull();
190
+ assertToolcraftExpectedDecodedPixels({
191
+ expectedPixels: expected.pixels,
192
+ mediaType: inspected.inspection.mediaType,
193
+ observation,
194
+ });
195
+ }
196
+ if (options.animated) {
197
+ expect(new Set(inspected.inspection.samplePixelHashes).size).toBeGreaterThan(1);
198
+ }
199
+ await attachAdditionalArtifactRequirements(
200
+ options.additionalArtifactRequirements,
201
+ );
202
+ await attachToolcraftBrowserRuntimeEvidence({
203
+ evidenceType: "video-export-artifact",
204
+ requirementId: options.requirementId,
205
+ target,
206
+ });
207
+ return inspected;
208
+ }
@@ -26,6 +26,9 @@ import {
26
26
  getToolcraftOrientationAxisPosition,
27
27
  type ToolcraftOrientationAxis,
28
28
  } from "./browser-orientation-gizmo-actions";
29
+ import {
30
+ expectToolcraftOrientationLiveDragPreconditions,
31
+ } from "./browser-orientation-gizmo-live-preconditions";
29
32
 
30
33
  export type ToolcraftOrientationBrowserObservation = {
31
34
  outputSignature: string;
@@ -222,6 +225,7 @@ export async function expectToolcraftOrientationAxisDrag(
222
225
  message,
223
226
  options,
224
227
  );
228
+ await expectToolcraftOrientationLiveDragPreconditions(session);
225
229
  const before = snapshotToolcraftOutcome(await observe(), message);
226
230
 
227
231
  validateObservation(before, options);