@pixel-point/toolcraft 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/package.json +1 -1
  2. package/src/generate-performance-iteration-execution.test.mjs +2 -2
  3. package/src/generate-test-demand-only-lifecycle.mjs +14 -3
  4. package/src/generate-test-dependency-sandbox.mjs +42 -5
  5. package/src/generate-test-doc-assertions.mjs +109 -69
  6. package/src/generate-test-doc-assertions.test.mjs +65 -0
  7. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  8. package/src/generate-test-file-assertions.mjs +36 -118
  9. package/src/generate-test-output-assertions.mjs +95 -0
  10. package/src/generate-test-package-assertions.mjs +6 -0
  11. package/src/generate-test-performance-boundary-assertions.mjs +45 -0
  12. package/src/generate-test-product-fixture.mjs +1 -0
  13. package/src/generate-test-product-fixture.test.mjs +18 -0
  14. package/src/generate-test-product-iteration.mjs +1 -0
  15. package/src/generate-test-product-iteration.test.mjs +18 -0
  16. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  17. package/src/generate-test-standalone-assertions.mjs +10 -0
  18. package/src/generate-test-standalone-delivery-stages.mjs +14 -6
  19. package/src/generate.test.mjs +14 -0
  20. package/templates/runtime/contracts/component-contracts.choices.test.ts +59 -12
  21. package/templates/runtime/contracts/component-contracts.choices.ts +58 -12
  22. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  23. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  24. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  25. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  26. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +21 -6
  27. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  28. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +4 -1
  29. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +4 -1
  30. package/templates/runtime/contracts/component-contracts.runtime.ts +15 -10
  31. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  32. package/templates/runtime/contracts/decision-contracts.test.ts +43 -4
  33. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  34. package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
  35. package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
  36. package/templates/runtime/export/artifact-download.test.ts +71 -0
  37. package/templates/runtime/export/artifact-download.ts +80 -0
  38. package/templates/runtime/export/artifact-export-request.ts +23 -0
  39. package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
  40. package/templates/runtime/export/artifact-export-settings.ts +96 -0
  41. package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
  42. package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
  43. package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
  44. package/templates/runtime/export/artifact-frame-state.ts +44 -0
  45. package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
  46. package/templates/runtime/export/artifact-scene-frame.ts +118 -0
  47. package/templates/runtime/export/export-background.ts +46 -0
  48. package/templates/runtime/export/export-error.ts +43 -0
  49. package/templates/runtime/export/export.test.ts +49 -226
  50. package/templates/runtime/export/image-artifact-export.test.ts +111 -0
  51. package/templates/runtime/export/image-artifact-export.ts +145 -0
  52. package/templates/runtime/export/index.ts +44 -3
  53. package/templates/runtime/export/product-export-renderer.test.ts +77 -0
  54. package/templates/runtime/export/product-export-renderer.ts +63 -0
  55. package/templates/runtime/export/video-artifact-export.test.ts +166 -0
  56. package/templates/runtime/export/video-artifact-export.ts +164 -0
  57. package/templates/runtime/export/video-encoding-backend.ts +124 -0
  58. package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
  59. package/templates/runtime/export/video-encoding-policy.ts +86 -0
  60. package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
  61. package/templates/runtime/export/video-frame-schedule.ts +42 -0
  62. package/templates/runtime/index.ts +3 -0
  63. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  64. package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
  65. package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
  66. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +15 -304
  67. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +41 -27
  68. package/templates/runtime/react/app-shell/toolcraft-app.tsx +39 -11
  69. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
  70. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
  71. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
  72. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
  73. package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
  74. package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
  75. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
  76. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
  77. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  78. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  79. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  80. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  81. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  82. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  83. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  84. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  85. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  86. package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
  87. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +18 -84
  88. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  89. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  90. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +43 -87
  91. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +68 -0
  92. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  93. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  94. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  95. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  96. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  97. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  98. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  99. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  100. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  101. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  102. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  103. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  104. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  105. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  106. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  107. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  108. package/templates/runtime/react/index.ts +4 -0
  109. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  110. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  111. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  112. package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
  113. package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
  114. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  115. package/templates/runtime/scene/scene-bounds.ts +32 -19
  116. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  117. package/templates/runtime/schema/canvas-render-scale.ts +62 -0
  118. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  119. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  120. package/templates/runtime/schema/control-applicability.ts +433 -0
  121. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  122. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  123. package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
  124. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  125. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +62 -3
  126. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  127. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  128. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  129. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  130. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  131. package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
  132. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  133. package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
  134. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  135. package/templates/runtime/schema/define-toolcraft.ts +1 -1
  136. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  137. package/templates/runtime/schema/schema-resolvers.ts +0 -56
  138. package/templates/runtime/schema/types.ts +72 -17
  139. package/templates/runtime/state/canvas-background-state.ts +6 -0
  140. package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
  141. package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
  142. package/templates/starter/AGENTS.md +18 -15
  143. package/templates/starter/NOTICE.md +6 -0
  144. package/templates/starter/docs/toolcraft/README.md +2 -1
  145. package/templates/starter/docs/toolcraft/acceptance-testing.md +23 -35
  146. package/templates/starter/docs/toolcraft/agent-worklog.md +18 -3
  147. package/templates/starter/docs/toolcraft/assembly-workflow.md +13 -5
  148. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  149. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  150. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  151. package/templates/starter/docs/toolcraft/core/media-upload.md +4 -2
  152. package/templates/starter/docs/toolcraft/core/performance.md +14 -3
  153. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +16 -3
  154. package/templates/starter/docs/toolcraft/core/setup-export.md +49 -19
  155. package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
  156. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  157. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  158. package/templates/starter/docs/toolcraft/performance.md +20 -6
  159. package/templates/starter/docs/toolcraft/renderer-technique.md +5 -1
  160. package/templates/starter/docs/toolcraft/schema-reference.md +23 -25
  161. package/templates/starter/docs/toolcraft/workflow.md +7 -2
  162. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  163. package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
  164. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +50 -3
  165. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  166. package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
  167. package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
  168. package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
  169. package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
  170. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +5 -34
  171. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -16
  172. package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
  173. package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
  174. package/templates/starter/e2e/app-performance.spec.ts +16 -9
  175. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  176. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  177. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +29 -341
  178. package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
  179. package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
  180. package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
  181. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  182. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  183. package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
  184. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  185. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +13 -3
  186. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +72 -35
  187. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  188. package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
  189. package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
  190. package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
  191. package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
  192. package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
  193. package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
  194. package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
  195. package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
  196. package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
  197. package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
  198. package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
  199. package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
  200. package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
  201. package/templates/starter/e2e/performance-path-helpers.ts +210 -91
  202. package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
  203. package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
  204. package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
  205. package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
  206. package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
  207. package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
  208. package/templates/starter/gitignore +1 -0
  209. package/templates/starter/package.json +1 -0
  210. package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
  211. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  212. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
  213. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
  214. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
  215. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +95 -103
  216. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  217. package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
  218. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -26
  219. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  220. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  221. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  222. package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
  223. package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
  224. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +157 -0
  225. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  226. package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
  227. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  228. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  229. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
  230. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +157 -206
  231. package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
  232. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
  233. package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
  234. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  235. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  236. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  237. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  238. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  239. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  240. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  241. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  242. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  243. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  244. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  245. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  246. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  247. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  248. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  249. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  250. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  251. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  252. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  253. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
  254. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  255. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  256. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  257. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  258. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  259. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  260. package/templates/starter/src/app/acceptance/output-export.ts +35 -12
  261. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  262. package/templates/starter/src/app/acceptance/types.ts +30 -8
  263. package/templates/starter/src/app/acceptance/validate-coverage.ts +20 -2
  264. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  265. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  266. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  267. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  268. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  269. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  270. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  271. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  272. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  273. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  274. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  275. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
  276. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  277. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  278. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  279. package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
  280. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  281. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  282. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  283. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +46 -1
  284. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  285. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  286. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  287. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  288. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  289. package/templates/starter/src/app/starter-acceptance.ts +13 -2
  290. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  291. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  292. package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
  293. package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
  294. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
  295. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +4 -2
  296. package/templates/starter/src/routes/index.tsx +2 -0
  297. package/templates/ui/components/composites/tabs.tsx +2 -2
  298. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  299. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  300. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  301. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  302. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  303. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  304. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  305. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  306. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  307. package/templates/ui/components/controls/index.ts +3 -0
  308. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  309. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  310. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  311. package/templates/runtime/export/export.ts +0 -166
  312. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  313. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
@@ -1,12 +1,15 @@
1
1
  import { expect, test, type Page, type TestInfo } from "@playwright/test";
2
2
  import {
3
+ defineToolcraft,
3
4
  defineToolcraftPerformance,
4
5
  deriveToolcraftPerformancePaths,
5
6
  registerToolcraftRendererPipeline,
6
7
  type ToolcraftRendererPipelinePassContract,
7
8
  } from "@repo/toolcraft-runtime";
9
+ import type {
10
+ ToolcraftPerformancePipelinePhase,
11
+ } from "../src/app/test-evidence/browser-performance-contract";
8
12
 
9
- import { starterSchema } from "../src/app/starter-schema";
10
13
  import { attachedEvidenceTypes } from "./browser-semantic-evidence-test-helpers";
11
14
  import {
12
15
  compileToolcraftPerformancePathAdapterMatrix,
@@ -18,6 +21,54 @@ import {
18
21
  } from "./performance-fixture-selection";
19
22
 
20
23
  const canvasSelector = "[data-render-scale-canvas]";
24
+ const originalFixtureResolutionMode =
25
+ process.env[TOOLCRAFT_PERFORMANCE_FIXTURE_RESOLUTION_MODE_ENV];
26
+ const originalRequestAuthorityHash =
27
+ process.env[TOOLCRAFT_PERFORMANCE_REQUEST_AUTHORITY_HASH_ENV];
28
+
29
+ test.beforeEach(() => {
30
+ process.env[TOOLCRAFT_PERFORMANCE_FIXTURE_RESOLUTION_MODE_ENV] =
31
+ "full-certification";
32
+ delete process.env[TOOLCRAFT_PERFORMANCE_REQUEST_AUTHORITY_HASH_ENV];
33
+ });
34
+
35
+ test.afterEach(() => {
36
+ if (originalFixtureResolutionMode === undefined) {
37
+ delete process.env[TOOLCRAFT_PERFORMANCE_FIXTURE_RESOLUTION_MODE_ENV];
38
+ } else {
39
+ process.env[TOOLCRAFT_PERFORMANCE_FIXTURE_RESOLUTION_MODE_ENV] =
40
+ originalFixtureResolutionMode;
41
+ }
42
+ if (originalRequestAuthorityHash === undefined) {
43
+ delete process.env[TOOLCRAFT_PERFORMANCE_REQUEST_AUTHORITY_HASH_ENV];
44
+ } else {
45
+ process.env[TOOLCRAFT_PERFORMANCE_REQUEST_AUTHORITY_HASH_ENV] =
46
+ originalRequestAuthorityHash;
47
+ }
48
+ });
49
+
50
+ const renderScaleSchema = defineToolcraft({
51
+ canvas: { enabled: true, renderScale: true },
52
+ panels: {
53
+ controls: {
54
+ sections: [
55
+ {
56
+ controls: {
57
+ color: {
58
+ defaultValue: "#ffffff",
59
+ label: "Color",
60
+ performanceRole: "responsiveness",
61
+ target: "appearance.color",
62
+ type: "color",
63
+ },
64
+ },
65
+ title: "Appearance",
66
+ },
67
+ ],
68
+ title: "Controls",
69
+ },
70
+ },
71
+ });
21
72
 
22
73
  type RenderScalePipelinePasses = {
23
74
  composite: ToolcraftRendererPipelinePassContract<void>;
@@ -27,10 +78,10 @@ const renderScaleRegistration =
27
78
  registerToolcraftRendererPipeline<RenderScalePipelinePasses>()({
28
79
  interactionInvalidation: [
29
80
  {
30
- interaction: "control-change",
81
+ interaction: "control-drag",
31
82
  invalidates: ["composite"],
32
83
  mustNotInvalidate: [],
33
- targets: ["canvas.renderScale"],
84
+ targets: ["appearance.color"],
34
85
  },
35
86
  ],
36
87
  passes: [
@@ -41,8 +92,8 @@ const renderScaleRegistration =
41
92
  relationship: "constant",
42
93
  },
43
94
  id: "composite",
44
- inputs: ["canvas.renderScale"],
45
- invalidatedBy: ["canvas.renderScale"],
95
+ inputs: ["appearance.color"],
96
+ invalidatedBy: ["appearance.color"],
46
97
  kind: "composite",
47
98
  output: "preview",
48
99
  quality: "full",
@@ -62,7 +113,7 @@ const renderScalePerformance = defineToolcraftPerformance({
62
113
  });
63
114
 
64
115
  const [renderScalePath] = deriveToolcraftPerformancePaths(
65
- starterSchema,
116
+ renderScaleSchema,
66
117
  renderScalePerformance,
67
118
  );
68
119
 
@@ -121,15 +172,17 @@ async function installFixture(page: Page): Promise<void> {
121
172
  }, renderScaleRegistration.runtimeId);
122
173
  }
123
174
 
175
+ type PhaseScale = Readonly<Record<ToolcraftPerformancePipelinePhase, number>>;
176
+
124
177
  function compileRunner(
125
- backingScaleAfterAction: number,
178
+ backingScaleByPhase: PhaseScale,
126
179
  prepare: () => Promise<void> = async () => undefined,
180
+ afterAction?: (page: Page) => Promise<void>,
127
181
  ) {
128
- return compileToolcraftPerformancePathAdapterMatrix(
129
- [renderScalePath!],
130
- [
182
+ return compileToolcraftPerformancePathAdapterMatrix({
183
+ adapters: [
131
184
  {
132
- action: async ({ page }) => {
185
+ action: async ({ page, phase }) => {
133
186
  await page.evaluate(({ scale, selector }) => {
134
187
  const state = Reflect.get(
135
188
  globalThis,
@@ -164,7 +217,8 @@ function compileRunner(
164
217
  canvas.height = Math.round(
165
218
  rect.height * window.devicePixelRatio * scale,
166
219
  );
167
- }, { scale: backingScaleAfterAction, selector: canvasSelector });
220
+ }, { scale: backingScaleByPhase[phase], selector: canvasSelector });
221
+ await afterAction?.(page);
168
222
  },
169
223
  observeOutcome: ({ page }) =>
170
224
  page.evaluate(
@@ -178,15 +232,54 @@ function compileRunner(
178
232
  ),
179
233
  pathId: renderScalePath!.id,
180
234
  prepare,
181
- renderScaleBacking: {
182
- canvasSelector,
183
- selectedScale: 2,
184
- },
185
235
  },
186
236
  ],
187
- )[0]!;
237
+ canvasBacking: { canvasSelector },
238
+ paths: [renderScalePath!],
239
+ schema: renderScaleSchema,
240
+ })[0]!;
188
241
  }
189
242
 
243
+ test("runner preserves an undefined phase rejection when guard disposal fails", async ({
244
+ page,
245
+ }) => {
246
+ await installFixture(page);
247
+ const outcome = await runToolcraftPerformancePath(
248
+ page,
249
+ renderScaleSchema,
250
+ renderScalePerformance,
251
+ compileRunner(
252
+ { cold: 2, sustained: 2, warm: 2 },
253
+ async () => undefined,
254
+ async (currentPage) => {
255
+ await currentPage.evaluate(() => {
256
+ const stateKey = Object.getOwnPropertyNames(globalThis).find((key) =>
257
+ key.startsWith("__toolcraftCanvasQualityObserver"),
258
+ );
259
+ if (!stateKey) {
260
+ throw new Error("Quality guard fixture could not find observer state.");
261
+ }
262
+ const state = Reflect.get(globalThis, stateKey) as {
263
+ flush: () => Promise<void>;
264
+ };
265
+ Reflect.set(globalThis, stateKey, {
266
+ dispose: () => {
267
+ throw new Error("forced guard disposal failure");
268
+ },
269
+ flush: state.flush,
270
+ });
271
+ });
272
+ return Promise.reject(undefined);
273
+ },
274
+ ),
275
+ ).then(
276
+ () => ({ status: "resolved" as const }),
277
+ (reason: unknown) => ({ reason, status: "rejected" as const }),
278
+ );
279
+
280
+ expect(outcome).toEqual({ reason: undefined, status: "rejected" });
281
+ });
282
+
190
283
  test("requestless performance runner rejects before adapter operations", async ({
191
284
  page,
192
285
  }) => {
@@ -202,9 +295,9 @@ test("requestless performance runner rejects before adapter operations", async (
202
295
  await expect(
203
296
  runToolcraftPerformancePath(
204
297
  page,
205
- starterSchema,
298
+ renderScaleSchema,
206
299
  renderScalePerformance,
207
- compileRunner(2, async () => {
300
+ compileRunner({ cold: 2, sustained: 2, warm: 2 }, async () => {
208
301
  prepareCount += 1;
209
302
  }),
210
303
  ),
@@ -234,9 +327,9 @@ test("browser perf: runner proves real render-scale backing", async ({
234
327
 
235
328
  await runToolcraftPerformancePath(
236
329
  page,
237
- starterSchema,
330
+ renderScaleSchema,
238
331
  renderScalePerformance,
239
- compileRunner(2),
332
+ compileRunner({ cold: 2, sustained: 2, warm: 2 }),
240
333
  );
241
334
 
242
335
  expect(
@@ -246,21 +339,30 @@ test("browser perf: runner proves real render-scale backing", async ({
246
339
  ).toHaveLength(3);
247
340
  });
248
341
 
249
- test("browser perf: runner rejects a render-scale quality clamp", async ({
250
- page,
251
- }, testInfo: TestInfo) => {
252
- await installFixture(page);
253
- const attachmentCount = testInfo.attachments.length;
342
+ for (const failedPhase of ["cold", "warm", "sustained"] as const) {
343
+ test(`browser perf: runner rejects a ${failedPhase} render-scale quality clamp`, async ({
344
+ page,
345
+ }, testInfo: TestInfo) => {
346
+ await installFixture(page);
347
+ const attachmentCount = testInfo.attachments.length;
348
+ const scaleByPhase: PhaseScale = { cold: 2, sustained: 2, warm: 2 };
254
349
 
255
- await expect(
256
- runToolcraftPerformancePath(
257
- page,
258
- starterSchema,
259
- renderScalePerformance,
260
- compileRunner(1),
261
- ),
262
- ).rejects.toThrow(/backing width.*Resolution scale 2/iu);
263
- expect(attachedEvidenceTypes(testInfo, attachmentCount)).not.toContain(
264
- "performance-render-scale",
265
- );
266
- });
350
+ await expect(
351
+ runToolcraftPerformancePath(
352
+ page,
353
+ renderScaleSchema,
354
+ renderScalePerformance,
355
+ compileRunner({ ...scaleByPhase, [failedPhase]: 1 }),
356
+ ),
357
+ ).rejects.toThrow(new RegExp(`${failedPhase}.*backing width`, "iu"));
358
+ const attachedQuality = testInfo.attachments
359
+ .slice(attachmentCount)
360
+ .filter((attachment) =>
361
+ attachment.body?.toString().includes("performance-render-scale"),
362
+ )
363
+ .map((attachment) => attachment.body?.toString() ?? "");
364
+ expect(attachedQuality).not.toContainEqual(
365
+ expect.stringContaining(`${renderScalePath!.id}#${failedPhase}`),
366
+ );
367
+ });
368
+ }
@@ -0,0 +1,64 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ import { assertToolcraftVideoPacketSchedule } from "./video-artifact-inspection";
4
+
5
+ const schedule = Object.freeze([
6
+ Object.freeze({ durationSeconds: 1 / 30, index: 0, timeSeconds: 0 }),
7
+ Object.freeze({ durationSeconds: 1 / 30, index: 1, timeSeconds: 1 / 30 }),
8
+ ]);
9
+
10
+ test("accepts real packet timings that match the runtime schedule", () => {
11
+ expect(() =>
12
+ assertToolcraftVideoPacketSchedule({
13
+ packetTimings: schedule,
14
+ schedule,
15
+ timeResolution: 90_000,
16
+ }),
17
+ ).not.toThrow();
18
+ });
19
+
20
+ test("rejects synthetic or dropped packet counts", () => {
21
+ expect(() =>
22
+ assertToolcraftVideoPacketSchedule({
23
+ packetTimings: schedule.slice(0, 1),
24
+ schedule,
25
+ timeResolution: 90_000,
26
+ }),
27
+ ).toThrow("contains 1 packets; runtime scheduled 2");
28
+ });
29
+
30
+ test("rejects packet timestamps that do not match runtime time", () => {
31
+ expect(() =>
32
+ assertToolcraftVideoPacketSchedule({
33
+ packetTimings: [
34
+ schedule[0]!,
35
+ { durationSeconds: 1 / 30, timeSeconds: 2 / 30 },
36
+ ],
37
+ schedule,
38
+ timeResolution: 90_000,
39
+ }),
40
+ ).toThrow("packet 1 timestamp");
41
+ });
42
+
43
+ test("rejects packet durations that do not match runtime cadence", () => {
44
+ expect(() =>
45
+ assertToolcraftVideoPacketSchedule({
46
+ packetTimings: [
47
+ schedule[0]!,
48
+ { durationSeconds: 2 / 30, timeSeconds: 1 / 30 },
49
+ ],
50
+ schedule,
51
+ timeResolution: 90_000,
52
+ }),
53
+ ).toThrow("packet 1 duration");
54
+ });
55
+
56
+ test("rejects missing timing authority", () => {
57
+ expect(() =>
58
+ assertToolcraftVideoPacketSchedule({
59
+ packetTimings: schedule,
60
+ schedule,
61
+ timeResolution: 0,
62
+ }),
63
+ ).toThrow("positive time resolution");
64
+ });
@@ -0,0 +1,281 @@
1
+ import fs from "node:fs/promises";
2
+
3
+ import type { Download, Page } from "@playwright/test";
4
+ import {
5
+ ALL_FORMATS,
6
+ BlobSource,
7
+ EncodedPacketSink,
8
+ Input,
9
+ } from "mediabunny";
10
+
11
+ import {
12
+ observeToolcraftDecodedPixels,
13
+ type ToolcraftDecodedPixelObservation,
14
+ } from "./decoded-pixel-observation";
15
+ import type {
16
+ ToolcraftVideoArtifactInspection,
17
+ ToolcraftVideoPacketTiming,
18
+ } from "./export-artifact-helpers";
19
+
20
+ export type ToolcraftScheduledVideoFrame = Readonly<{
21
+ durationSeconds: number;
22
+ index: number;
23
+ timeSeconds: number;
24
+ }>;
25
+
26
+ export function assertToolcraftVideoPacketSchedule({
27
+ packetTimings,
28
+ schedule,
29
+ timeResolution,
30
+ }: Readonly<{
31
+ packetTimings: readonly ToolcraftVideoPacketTiming[];
32
+ schedule: readonly ToolcraftScheduledVideoFrame[];
33
+ timeResolution: number;
34
+ }>): void {
35
+ if (!Number.isFinite(timeResolution) || timeResolution <= 0) {
36
+ throw new Error("Video packet proof requires a positive time resolution.");
37
+ }
38
+ if (packetTimings.length !== schedule.length) {
39
+ throw new Error(
40
+ `Encoded video contains ${packetTimings.length} packets; runtime scheduled ${schedule.length}.`,
41
+ );
42
+ }
43
+
44
+ const toleranceSeconds = 1 / timeResolution;
45
+ for (const [index, actual] of packetTimings.entries()) {
46
+ const expected = schedule[index];
47
+ if (!expected) {
48
+ throw new Error(`Runtime schedule is missing frame ${index}.`);
49
+ }
50
+ if (
51
+ !Number.isFinite(actual.timeSeconds) ||
52
+ actual.timeSeconds < 0 ||
53
+ !Number.isFinite(actual.durationSeconds) ||
54
+ actual.durationSeconds <= 0
55
+ ) {
56
+ throw new Error(`Encoded packet ${index} has invalid timing metadata.`);
57
+ }
58
+ if (
59
+ Math.abs(actual.timeSeconds - expected.timeSeconds) > toleranceSeconds
60
+ ) {
61
+ throw new Error(
62
+ `Encoded packet ${index} timestamp does not match the runtime schedule.`,
63
+ );
64
+ }
65
+ if (
66
+ Math.abs(actual.durationSeconds - expected.durationSeconds) >
67
+ toleranceSeconds
68
+ ) {
69
+ throw new Error(
70
+ `Encoded packet ${index} duration does not match the runtime schedule.`,
71
+ );
72
+ }
73
+ }
74
+ }
75
+
76
+ export type ToolcraftInspectedVideoArtifact = Readonly<{
77
+ inspection: ToolcraftVideoArtifactInspection;
78
+ observations: readonly ToolcraftDecodedPixelObservation[];
79
+ sampleTimes: readonly number[];
80
+ timeResolution: number;
81
+ }>;
82
+
83
+ function detectVideoMediaType(bytes: Uint8Array): "video/mp4" | "video/webm" {
84
+ if (
85
+ bytes.length >= 8 &&
86
+ String.fromCharCode(...bytes.subarray(4, 8)) === "ftyp"
87
+ ) {
88
+ return "video/mp4";
89
+ }
90
+ if (
91
+ bytes.length >= 4 &&
92
+ bytes[0] === 0x1a &&
93
+ bytes[1] === 0x45 &&
94
+ bytes[2] === 0xdf &&
95
+ bytes[3] === 0xa3
96
+ ) {
97
+ return "video/webm";
98
+ }
99
+ throw new Error("Downloaded artifact is not a supported MP4 or WebM video.");
100
+ }
101
+
102
+ function getSampleTimes(
103
+ schedule: readonly ToolcraftScheduledVideoFrame[],
104
+ ): readonly number[] {
105
+ if (schedule.length === 0) {
106
+ throw new Error("Video inspection requires a non-empty runtime frame schedule.");
107
+ }
108
+ const indices = [...new Set([0, Math.floor(schedule.length / 2), schedule.length - 1])];
109
+ return Object.freeze(indices.map((index) => schedule[index]!.timeSeconds));
110
+ }
111
+
112
+ async function decodeSamplesInPage({
113
+ bytes,
114
+ mediaType,
115
+ page,
116
+ sampleTimes,
117
+ }: Readonly<{
118
+ bytes: Uint8Array;
119
+ mediaType: "video/mp4" | "video/webm";
120
+ page: Page;
121
+ sampleTimes: readonly number[];
122
+ }>): Promise<readonly Uint8ClampedArray[]> {
123
+ const samples = await page.evaluate(
124
+ async ({ base64, mediaType: decodedMediaType, times }) => {
125
+ const binary = atob(base64);
126
+ const data = Uint8Array.from(binary, (character) =>
127
+ character.charCodeAt(0),
128
+ );
129
+ const video = document.createElement("video");
130
+ video.muted = true;
131
+ video.playsInline = true;
132
+ video.preload = "auto";
133
+ const objectUrl = URL.createObjectURL(
134
+ new Blob([data], { type: decodedMediaType }),
135
+ );
136
+ video.src = objectUrl;
137
+ try {
138
+ await new Promise<void>((resolve, reject) => {
139
+ video.addEventListener("loadeddata", () => resolve(), { once: true });
140
+ video.addEventListener(
141
+ "error",
142
+ () => reject(new Error("Browser could not decode exported video.")),
143
+ { once: true },
144
+ );
145
+ video.load();
146
+ });
147
+ const canvas = document.createElement("canvas");
148
+ canvas.width = 64;
149
+ canvas.height = 64;
150
+ const context = canvas.getContext("2d", { willReadFrequently: true });
151
+ if (!context) throw new Error("Unable to inspect exported video pixels.");
152
+ const decodedSamples: number[][] = [];
153
+ for (const timeSeconds of times) {
154
+ if (Math.abs(video.currentTime - timeSeconds) > 1e-6) {
155
+ await new Promise<void>((resolve, reject) => {
156
+ video.addEventListener("seeked", () => resolve(), { once: true });
157
+ video.addEventListener(
158
+ "error",
159
+ () => reject(new Error("Browser could not seek exported video.")),
160
+ { once: true },
161
+ );
162
+ video.currentTime = timeSeconds;
163
+ });
164
+ }
165
+ context.clearRect(0, 0, 64, 64);
166
+ context.drawImage(video, 0, 0, 64, 64);
167
+ decodedSamples.push(
168
+ Array.from(context.getImageData(0, 0, 64, 64).data),
169
+ );
170
+ }
171
+ return decodedSamples;
172
+ } finally {
173
+ video.removeAttribute("src");
174
+ video.load();
175
+ URL.revokeObjectURL(objectUrl);
176
+ }
177
+ },
178
+ {
179
+ base64: Buffer.from(bytes).toString("base64"),
180
+ mediaType,
181
+ times: sampleTimes,
182
+ },
183
+ );
184
+ return Object.freeze(samples.map((sample) => Uint8ClampedArray.from(sample)));
185
+ }
186
+
187
+ export async function inspectToolcraftVideoDownload({
188
+ backgroundRgba,
189
+ download,
190
+ page,
191
+ schedule,
192
+ }: Readonly<{
193
+ backgroundRgba: readonly [number, number, number, number];
194
+ download: Download;
195
+ page: Page;
196
+ schedule: readonly ToolcraftScheduledVideoFrame[];
197
+ }>): Promise<ToolcraftInspectedVideoArtifact> {
198
+ const downloadPath = await download.path();
199
+ if (!downloadPath) {
200
+ throw new Error("Playwright did not expose the downloaded video path.");
201
+ }
202
+ const bytes = await fs.readFile(downloadPath);
203
+ if (bytes.byteLength === 0) {
204
+ throw new Error("Downloaded video artifact is empty.");
205
+ }
206
+ const mediaType = detectVideoMediaType(bytes);
207
+ const input = new Input({
208
+ formats: ALL_FORMATS,
209
+ source: new BlobSource(new Blob([bytes], { type: mediaType })),
210
+ });
211
+
212
+ let durationSeconds: number;
213
+ let height: number;
214
+ let packetTimings: ToolcraftVideoPacketTiming[];
215
+ let timeResolution: number;
216
+ let width: number;
217
+ try {
218
+ const tracks = await input.getVideoTracks();
219
+ const track = tracks[0];
220
+ if (!track) throw new Error("Exported video does not contain a video track.");
221
+ [durationSeconds, height, timeResolution, width] = await Promise.all([
222
+ input.computeDuration([track]),
223
+ track.getCodedHeight(),
224
+ track.getTimeResolution(),
225
+ track.getCodedWidth(),
226
+ ]);
227
+ packetTimings = [];
228
+ const sink = new EncodedPacketSink(track);
229
+ for await (const packet of sink.packets(undefined, undefined, {
230
+ metadataOnly: true,
231
+ })) {
232
+ packetTimings.push(
233
+ Object.freeze({
234
+ durationSeconds: packet.duration,
235
+ timeSeconds: packet.timestamp,
236
+ }),
237
+ );
238
+ }
239
+ } finally {
240
+ input.dispose();
241
+ }
242
+ packetTimings.sort((left, right) => left.timeSeconds - right.timeSeconds);
243
+
244
+ const sampleTimes = getSampleTimes(schedule);
245
+ const decodedSamples = await decodeSamplesInPage({
246
+ bytes,
247
+ mediaType,
248
+ page,
249
+ sampleTimes,
250
+ });
251
+ const observations = await Promise.all(
252
+ decodedSamples.map((pixels) =>
253
+ observeToolcraftDecodedPixels({
254
+ backgroundRgba,
255
+ pixels,
256
+ sourceHeight: 64,
257
+ sourceWidth: 64,
258
+ }),
259
+ ),
260
+ );
261
+ const inspection: ToolcraftVideoArtifactInspection = Object.freeze({
262
+ byteLength: bytes.byteLength,
263
+ durationMs: durationSeconds * 1000,
264
+ frameCount: packetTimings.length,
265
+ height,
266
+ kind: "video",
267
+ mediaType,
268
+ packetTimings: Object.freeze(packetTimings),
269
+ samplePixelHashes: Object.freeze(
270
+ observations.map(({ decodedPixelHash }) => decodedPixelHash),
271
+ ),
272
+ width,
273
+ });
274
+
275
+ return Object.freeze({
276
+ inspection,
277
+ observations: Object.freeze(observations),
278
+ sampleTimes,
279
+ timeResolution,
280
+ });
281
+ }
@@ -14,6 +14,7 @@ dist-ssr
14
14
  coverage
15
15
  test-results
16
16
  playwright-report
17
+ .playwright-mcp
17
18
 
18
19
  # Local environment
19
20
  .env
@@ -38,6 +38,7 @@
38
38
  "cross-spawn": "^7.0.6",
39
39
  "fflate": "0.8.3",
40
40
  "lucide-react": "^0.511.0",
41
+ "mediabunny": "1.52.2",
41
42
  "meshoptimizer": "1.2.0",
42
43
  "motion": "^11.16.3",
43
44
  "react": "^19.2.0",
@@ -78,6 +78,15 @@ const forbiddenTextPatterns = [
78
78
  const forbiddenModelContractPatterns = [
79
79
  /\bgeometry-only (?:3D )?model(?:s| bundle| bundles| app| apps| default)?\b/iu,
80
80
  ];
81
+ const forbiddenPerformanceRoutingPatterns = [
82
+ /for a performance complaint only,\s+an exact request quote and the canonical affected performance path ids/iu,
83
+ /(?:\*\*)?performance complaint(?::\*\*|\.\*\*)[^\n]*(?:canonical affected path ids|targeted iteration)/iu,
84
+ /\ba performance complaint records[^\n]*(?:canonical affected path ids|targeted iteration)/iu,
85
+ /unresolved\s+`?needs-agent-judgment`?[^\n]*(?:creates neither|records neither|no `?performance intent)/iu,
86
+ /for unresolved\s+`?needs-agent-judgment`?[^\n]*no `?performance intent/iu,
87
+ /direct reports that the app lags[^\n]*select one performance iteration/iu,
88
+ /independent complaint still selects an iteration/iu,
89
+ ];
81
90
 
82
91
  async function readText(relativePath) {
83
92
  return fs.readFile(path.join(projectRoot, relativePath), "utf8");
@@ -172,6 +181,14 @@ for (const pattern of forbiddenModelContractPatterns) {
172
181
  }
173
182
  }
174
183
 
184
+ for (const pattern of forbiddenPerformanceRoutingPatterns) {
185
+ if (pattern.test(combinedSource)) {
186
+ failures.push(
187
+ `local Toolcraft docs contain unconditional complaint routing ${pattern}`,
188
+ );
189
+ }
190
+ }
191
+
175
192
  for (const term of [
176
193
  "bounded ZIP",
177
194
  "Blender-compatible fallback",
@@ -76,6 +76,7 @@
76
76
  { "type": "checkbox", "visualComponent": "Checkbox" },
77
77
  { "type": "actions", "visualComponent": "Actions" },
78
78
  { "type": "collectionActions", "visualComponent": "CollectionActions" },
79
+ { "type": "sourceCollection", "visualComponent": "ControlsPanelCollectionItems" },
79
80
  { "type": "panelActions", "visualComponent": "PanelActions" },
80
81
  { "type": "colorOpacity", "visualComponent": "ColorOpacity" },
81
82
  { "type": "palette", "visualComponent": "Palette" },