@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,6 +1,7 @@
1
1
  import type { Page } from "@playwright/test";
2
2
 
3
3
  import {
4
+ TOOLCRAFT_CANVAS_RENDER_SCALE,
4
5
  compileToolcraftPerformanceFixturePlan,
5
6
  deriveToolcraftPerformancePaths,
6
7
  type ResolvedToolcraftAppSchema,
@@ -9,14 +10,11 @@ import {
9
10
  } from "@repo/toolcraft-runtime";
10
11
 
11
12
  import {
12
- assertToolcraftRenderScaleBackingProofConfig,
13
+ expectToolcraftCanvasBackingPixelsForRenderScale,
13
14
  expectToolcraftPerformanceRenderScaleBackingEvidence,
14
- isToolcraftRenderScalePerformancePath,
15
15
  } from "./browser-render-scale-evidence";
16
16
  import { attachToolcraftBrowserRuntimeEvidence } from "./browser-runtime-evidence";
17
- import {
18
- expectToolcraftPerformanceBudget,
19
- } from "./performance-budget-helpers";
17
+ import { expectToolcraftPerformanceBudget } from "./performance-budget-helpers";
20
18
  import { applyToolcraftPerformancePathCompiledFixture } from "./performance-fixture-helpers";
21
19
  import {
22
20
  readToolcraftPerformanceFixtureResolutionMode,
@@ -27,11 +25,15 @@ import {
27
25
  measureToolcraftClipboardActionByLabel,
28
26
  measureToolcraftDownloadActionByLabel,
29
27
  } from "./performance-output-action-helpers";
30
- import type { ToolcraftPerformancePathAdapter } from "./performance-path-adapter-contract";
31
- import { expectToolcraftPipelineInvariant } from "./performance-pipeline-evidence";
32
28
  import {
33
- getToolcraftPerformancePathBudget,
34
- } from "./performance-profile-helpers";
29
+ createToolcraftPerformanceCanvasQualityGuard,
30
+ } from "./performance-canvas-quality-guard";
31
+ import type {
32
+ ToolcraftPerformanceCanvasBacking,
33
+ ToolcraftPerformancePathAdapter,
34
+ } from "./performance-path-adapter-contract";
35
+ import { expectToolcraftPipelineInvariant } from "./performance-pipeline-evidence";
36
+ import { getToolcraftPerformancePathBudget } from "./performance-profile-helpers";
35
37
  import {
36
38
  measureToolcraftAnimationFrames,
37
39
  measureToolcraftInteraction,
@@ -39,10 +41,81 @@ import {
39
41
 
40
42
  export type ToolcraftCompiledPerformancePathAdapter = Readonly<{
41
43
  adapter: ToolcraftPerformancePathAdapter;
44
+ canvasBacking: ToolcraftPerformanceCanvasBacking | undefined;
42
45
  path: ToolcraftPerformancePath;
43
46
  testName: string;
44
47
  }>;
45
48
 
49
+ type ToolcraftPerformancePathAdapterMatrixInput = Readonly<{
50
+ adapters: readonly ToolcraftPerformancePathAdapter[];
51
+ canvasBacking: ToolcraftPerformanceCanvasBacking | undefined;
52
+ paths: readonly ToolcraftPerformancePath[];
53
+ schema: ResolvedToolcraftAppSchema;
54
+ }>;
55
+
56
+ function compileToolcraftPerformanceCanvasBackingConfig(
57
+ canvasBacking: unknown,
58
+ ): ToolcraftPerformanceCanvasBacking {
59
+ if (
60
+ typeof canvasBacking !== "object" ||
61
+ canvasBacking === null ||
62
+ Array.isArray(canvasBacking)
63
+ ) {
64
+ throw new Error(
65
+ "Toolcraft performance canvas backing must be a non-null object.",
66
+ );
67
+ }
68
+ const keys = Reflect.ownKeys(canvasBacking);
69
+ if (keys.length !== 1 || keys[0] !== "canvasSelector") {
70
+ throw new Error(
71
+ 'Toolcraft performance canvas backing must contain exactly one "canvasSelector" field.',
72
+ );
73
+ }
74
+ const descriptor = Object.getOwnPropertyDescriptor(
75
+ canvasBacking,
76
+ "canvasSelector",
77
+ );
78
+ if (!descriptor || !("value" in descriptor)) {
79
+ throw new Error(
80
+ "Toolcraft performance canvas backing canvasSelector must be a data property.",
81
+ );
82
+ }
83
+ const canvasSelector = descriptor.value;
84
+ if (
85
+ typeof canvasSelector !== "string" ||
86
+ canvasSelector.length === 0 ||
87
+ canvasSelector.trim() !== canvasSelector
88
+ ) {
89
+ throw new Error(
90
+ "Toolcraft performance canvas backing canvasSelector must be a non-empty trimmed string.",
91
+ );
92
+ }
93
+ return Object.freeze({ canvasSelector });
94
+ }
95
+
96
+ function compilePerformanceCanvasBackingBoundary({
97
+ canvasBacking,
98
+ schema,
99
+ }: Pick<
100
+ ToolcraftPerformancePathAdapterMatrixInput,
101
+ "canvasBacking" | "schema"
102
+ >): ToolcraftPerformanceCanvasBacking | undefined {
103
+ if (canvasBacking === undefined) {
104
+ if (!schema.canvas.renderScale.enabled) {
105
+ return undefined;
106
+ }
107
+ throw new Error(
108
+ "Toolcraft render-scale-enabled performance catalogs require one product canvas selector for 2x backing proof.",
109
+ );
110
+ }
111
+ if (!schema.canvas.renderScale.enabled) {
112
+ throw new Error(
113
+ "Toolcraft performance canvas backing proof must be omitted when canvas render scale is disabled.",
114
+ );
115
+ }
116
+ return compileToolcraftPerformanceCanvasBackingConfig(canvasBacking);
117
+ }
118
+
46
119
  function compareCodeUnits(left: string, right: string): number {
47
120
  return left < right ? -1 : left > right ? 1 : 0;
48
121
  }
@@ -57,13 +130,17 @@ export function getToolcraftPerformancePathSettleFrames(
57
130
  path: Pick<ToolcraftPerformancePath, "interaction">,
58
131
  ): number | undefined {
59
132
  // A p95 distribution needs at least 20 samples to remain distinct from max.
60
- return path.interaction === "initial-render" ? 20 : undefined;
133
+ return path.interaction === "animation-frame" ||
134
+ path.interaction === "export"
135
+ ? undefined
136
+ : 20;
61
137
  }
62
138
 
63
139
  export function compileToolcraftPerformancePathAdapterMatrix(
64
- paths: readonly ToolcraftPerformancePath[],
65
- adapters: readonly ToolcraftPerformancePathAdapter[],
140
+ input: ToolcraftPerformancePathAdapterMatrixInput,
66
141
  ): readonly ToolcraftCompiledPerformancePathAdapter[] {
142
+ const compiledCanvasBacking = compilePerformanceCanvasBackingBoundary(input);
143
+ const { adapters, paths } = input;
67
144
  const canonicalPaths = [...paths].sort((left, right) =>
68
145
  compareCodeUnits(left.id, right.id),
69
146
  );
@@ -100,17 +177,6 @@ export function compileToolcraftPerformancePathAdapterMatrix(
100
177
 
101
178
  for (const path of canonicalPaths) {
102
179
  const adapter = adaptersByPathId.get(path.id)!;
103
- if (
104
- isToolcraftRenderScalePerformancePath(path) &&
105
- !adapter.renderScaleBacking
106
- ) {
107
- throw new Error(
108
- `Toolcraft render-scale performance path "${path.id}" requires real backing-pixel proof configuration.`,
109
- );
110
- }
111
- if (adapter.renderScaleBacking) {
112
- assertToolcraftRenderScaleBackingProofConfig(adapter.renderScaleBacking);
113
- }
114
180
  if (path.interaction === "export" && !adapter.output) {
115
181
  throw new Error(
116
182
  `Toolcraft export performance path "${path.id}" requires a protected output completion adapter.`,
@@ -135,6 +201,7 @@ export function compileToolcraftPerformancePathAdapterMatrix(
135
201
  canonicalPaths.map((path) =>
136
202
  Object.freeze({
137
203
  adapter: adaptersByPathId.get(path.id)!,
204
+ canvasBacking: compiledCanvasBacking,
138
205
  path,
139
206
  testName: getToolcraftPerformancePathTestName(path),
140
207
  }),
@@ -179,7 +246,7 @@ export async function runToolcraftPerformancePath(
179
246
  ): Promise<void> {
180
247
  const fixtureResolutionMode =
181
248
  readToolcraftPerformanceFixtureResolutionMode();
182
- const { adapter, path } = entry;
249
+ const { adapter, canvasBacking, path } = entry;
183
250
  const canonicalPath = deriveToolcraftPerformancePaths(schema, config).find(
184
251
  (candidate) => candidate.id === path.id,
185
252
  );
@@ -216,76 +283,128 @@ export async function runToolcraftPerformancePath(
216
283
  );
217
284
  }
218
285
 
286
+ const renderScaleEnabled = schema.canvas.renderScale.enabled;
287
+ if (renderScaleEnabled !== (canvasBacking !== undefined)) {
288
+ throw new Error(
289
+ "Toolcraft performance canvas backing presence must match the resolved render-scale schema.",
290
+ );
291
+ }
292
+ const baselineCssSize = renderScaleEnabled
293
+ ? await expectToolcraftCanvasBackingPixelsForRenderScale(
294
+ page,
295
+ canvasBacking!.canvasSelector,
296
+ TOOLCRAFT_CANVAS_RENDER_SCALE.defaultValue,
297
+ { state: "performance-baseline" },
298
+ )
299
+ : undefined;
300
+ const canvasQualityGuard = baselineCssSize
301
+ ? await createToolcraftPerformanceCanvasQualityGuard(page, {
302
+ baselineCssSize,
303
+ canvasSelector: canvasBacking!.canvasSelector,
304
+ cssSizePolicy:
305
+ path.interaction === "viewport-zoom" ? "viewport-driven" : "fixed",
306
+ pathId: path.id,
307
+ })
308
+ : undefined;
309
+
219
310
  const budget = getToolcraftPerformancePathBudget(path, config.scenarios);
220
311
  const target = getPathTarget(path);
221
- await expectToolcraftPipelineInvariant(
222
- page,
223
- config,
224
- path.id,
225
- async (phase) => {
226
- const context = Object.freeze({ page, path, phase });
227
- let result;
228
- if (adapter.output?.kind === "download") {
229
- const measured = await measureToolcraftDownloadActionByLabel(
230
- page,
231
- adapter.output.label,
232
- { pathId: path.id, phase, profile: path.profile, target },
233
- );
234
- result = measured.result;
235
- await adapter.output.verify(measured.completion, page);
236
- } else if (adapter.output?.kind === "clipboard") {
237
- const measured = await measureToolcraftClipboardActionByLabel(
238
- page,
239
- adapter.output.label,
240
- { pathId: path.id, phase, profile: path.profile, target },
241
- );
242
- result = measured.result;
243
- await adapter.output.verify(measured.completion, page);
244
- } else if (path.interaction === "animation-frame") {
245
- await adapter.action(context);
246
- result = await measureToolcraftAnimationFrames(page, 120, {
247
- pathId: path.id,
248
- phase,
249
- profile: path.profile,
250
- target,
251
- });
252
- await adapter.verifyOutcome?.(context);
253
- } else {
254
- result = await measureToolcraftInteraction(
255
- page,
256
- () => adapter.action(context),
257
- {
258
- ...(adapter.observeOutcome
259
- ? { observeOutcome: () => adapter.observeOutcome!(context) }
260
- : {}),
312
+ let hasPrimaryFailure = false;
313
+ try {
314
+ await expectToolcraftPipelineInvariant(
315
+ page,
316
+ config,
317
+ path.id,
318
+ async (phase) => {
319
+ const context = Object.freeze({ page, path, phase });
320
+ const measurePhase = async () => {
321
+ if (adapter.output?.kind === "download") {
322
+ const measured = await measureToolcraftDownloadActionByLabel(
323
+ page,
324
+ adapter.output.label,
325
+ { pathId: path.id, phase, profile: path.profile, target },
326
+ );
327
+ await adapter.output.verify(measured.completion, page);
328
+ return measured.result;
329
+ }
330
+ if (adapter.output?.kind === "clipboard") {
331
+ const measured = await measureToolcraftClipboardActionByLabel(
332
+ page,
333
+ adapter.output.label,
334
+ { pathId: path.id, phase, profile: path.profile, target },
335
+ );
336
+ await adapter.output.verify(measured.completion, page);
337
+ return measured.result;
338
+ }
339
+ if (path.interaction === "animation-frame") {
340
+ await adapter.action(context);
341
+ const result = await measureToolcraftAnimationFrames(page, 120, {
342
+ pathId: path.id,
343
+ phase,
344
+ profile: path.profile,
345
+ target,
346
+ });
347
+ await adapter.verifyOutcome?.(context);
348
+ return result;
349
+ }
350
+ const result = await measureToolcraftInteraction(
351
+ page,
352
+ () => adapter.action(context),
353
+ {
354
+ ...(adapter.observeOutcome
355
+ ? { observeOutcome: () => adapter.observeOutcome!(context) }
356
+ : {}),
357
+ pathId: path.id,
358
+ phase,
359
+ profile: path.profile,
360
+ settleFrames: getToolcraftPerformancePathSettleFrames(path),
361
+ target,
362
+ },
363
+ );
364
+ await adapter.verifyOutcome?.(context);
365
+ return result;
366
+ };
367
+ const result = canvasQualityGuard
368
+ ? await canvasQualityGuard.runPhase(phase, measurePhase)
369
+ : await measurePhase();
370
+ expectToolcraftPerformanceBudget(result, budget);
371
+ await attachPathEvidence("performance-budget", path);
372
+
373
+ if (
374
+ path.interaction === "control-drag" ||
375
+ path.interaction === "mask-drag"
376
+ ) {
377
+ await attachPathEvidence("performance-control-drag", path);
378
+ }
379
+ if (
380
+ path.interaction === "viewport-drag" ||
381
+ path.interaction === "viewport-zoom"
382
+ ) {
383
+ await attachPathEvidence("performance-viewport", path);
384
+ }
385
+ if (canvasBacking) {
386
+ await expectToolcraftPerformanceRenderScaleBackingEvidence(page, {
387
+ baselineCssSize: baselineCssSize!,
388
+ canvasSelector: canvasBacking.canvasSelector,
389
+ cssSizePolicy:
390
+ path.interaction === "viewport-zoom"
391
+ ? "viewport-driven"
392
+ : "fixed",
261
393
  pathId: path.id,
262
394
  phase,
263
- profile: path.profile,
264
- settleFrames: getToolcraftPerformancePathSettleFrames(path),
265
395
  target,
266
- },
267
- );
268
- await adapter.verifyOutcome?.(context);
269
- }
270
- expectToolcraftPerformanceBudget(result, budget);
271
- await attachPathEvidence("performance-budget", path);
272
-
273
- if (path.interaction === "control-drag" || path.interaction === "mask-drag") {
274
- await attachPathEvidence("performance-control-drag", path);
275
- }
276
- if (
277
- path.interaction === "viewport-drag" ||
278
- path.interaction === "viewport-zoom"
279
- ) {
280
- await attachPathEvidence("performance-viewport", path);
281
- }
282
- if (isToolcraftRenderScalePerformancePath(path)) {
283
- await expectToolcraftPerformanceRenderScaleBackingEvidence(page, {
284
- ...adapter.renderScaleBacking!,
285
- pathId: path.id,
286
- target,
287
- });
288
- }
289
- },
290
- );
396
+ });
397
+ }
398
+ },
399
+ );
400
+ } catch (error) {
401
+ hasPrimaryFailure = true;
402
+ throw error;
403
+ } finally {
404
+ try {
405
+ await canvasQualityGuard?.dispose();
406
+ } catch (error) {
407
+ if (!hasPrimaryFailure) throw error;
408
+ }
409
+ }
291
410
  }
@@ -3,6 +3,7 @@ import { expect, test } from "@playwright/test";
3
3
  import {
4
4
  activePipelineAttachments,
5
5
  activePipelinePath,
6
+ animationPipelinePath,
6
7
  evaluatePipelineEvidence,
7
8
  initialPipelineAttachments,
8
9
  initialPipelinePath,
@@ -76,6 +77,81 @@ test("ordinary paths require exact cross-phase continuity", () => {
76
77
  );
77
78
  });
78
79
 
80
+ test("autonomous animation permits only its invalidated passes to advance between phases", () => {
81
+ const observation = (
82
+ phase: "cold" | "warm" | "sustained",
83
+ beforeExecutions: number,
84
+ afterExecutions: number,
85
+ ) =>
86
+ pipelineEvidenceAttachment(
87
+ pipelineEvidence(
88
+ phase,
89
+ noCacheActivitySnapshot(beforeExecutions),
90
+ noCacheActivitySnapshot(afterExecutions),
91
+ animationPipelinePath,
92
+ ),
93
+ );
94
+ const valid = evaluatePipelineEvidence(
95
+ [
96
+ observation("cold", 0, 2),
97
+ observation("warm", 5, 7),
98
+ observation("sustained", 10, 12),
99
+ ],
100
+ [animationPipelinePath.id],
101
+ );
102
+ const decodeAdvanced = pipelineSnapshot({
103
+ composite: {
104
+ cacheMisses: 5,
105
+ durationMax: 1,
106
+ durationTotal: 5,
107
+ executions: 5,
108
+ },
109
+ decode: {
110
+ activeResources: 1,
111
+ cacheMisses: 1,
112
+ durationMax: 1,
113
+ durationTotal: 1,
114
+ executions: 1,
115
+ resourceCreations: 1,
116
+ },
117
+ });
118
+ const invalid = evaluatePipelineEvidence(
119
+ [
120
+ observation("cold", 0, 2),
121
+ pipelineEvidenceAttachment(
122
+ pipelineEvidence(
123
+ "warm",
124
+ decodeAdvanced,
125
+ pipelineSnapshot({
126
+ composite: {
127
+ cacheMisses: 7,
128
+ durationMax: 1,
129
+ durationTotal: 7,
130
+ executions: 7,
131
+ },
132
+ decode: {
133
+ activeResources: 1,
134
+ cacheMisses: 1,
135
+ durationMax: 1,
136
+ durationTotal: 1,
137
+ executions: 1,
138
+ resourceCreations: 1,
139
+ },
140
+ }),
141
+ animationPipelinePath,
142
+ ),
143
+ ),
144
+ observation("sustained", 10, 12),
145
+ ],
146
+ [animationPipelinePath.id],
147
+ );
148
+
149
+ expect(valid.errors).toEqual([]);
150
+ expect(invalid.errors).toContainEqual(
151
+ expect.stringMatching(/cold\.after to warm\.before/i),
152
+ );
153
+ });
154
+
79
155
  test("initial-render uses fresh canonical zero lifecycle samples without continuity", () => {
80
156
  const valid = evaluatePipelineEvidence(initialPipelineAttachments(), [
81
157
  initialPipelinePath.id,
@@ -67,6 +67,12 @@ export const pipelineEvidenceRegistration =
67
67
  mustNotInvalidate: ["decode"],
68
68
  targets: ["canvas.initial-render"],
69
69
  },
70
+ {
71
+ interaction: "animation-frame",
72
+ invalidates: ["composite"],
73
+ mustNotInvalidate: ["decode"],
74
+ targets: ["runtime.animation-frame"],
75
+ },
70
76
  {
71
77
  interaction: "control-drag",
72
78
  invalidates: ["composite"],
@@ -116,8 +122,8 @@ export const pipelineEvidenceRegistration =
116
122
  relationship: "constant",
117
123
  },
118
124
  id: "composite",
119
- inputs: ["decode", "effect.strength"],
120
- invalidatedBy: ["effect.strength"],
125
+ inputs: ["decode", "effect.strength", "runtime.animation-frame"],
126
+ invalidatedBy: ["effect.strength", "runtime.animation-frame"],
121
127
  kind: "composite",
122
128
  output: "preview",
123
129
  quality: "full",
@@ -142,6 +148,9 @@ const performancePaths = deriveToolcraftPerformancePaths(
142
148
  export const activePipelinePath = performancePaths.find(
143
149
  (path) => path.interaction === "control-drag",
144
150
  )!;
151
+ export const animationPipelinePath = performancePaths.find(
152
+ (path) => path.interaction === "animation-frame",
153
+ )!;
145
154
  export const initialPipelinePath = performancePaths.find(
146
155
  (path) => path.interaction === "initial-render",
147
156
  )!;
@@ -151,6 +151,35 @@ function snapshotsMatch(
151
151
  });
152
152
  }
153
153
 
154
+ function snapshotsPreserveContinuousAnimation(
155
+ left: ToolcraftPerformancePipelineSnapshot,
156
+ right: ToolcraftPerformancePipelineSnapshot,
157
+ invalidatedPassIds: ReadonlySet<string>,
158
+ ): boolean {
159
+ if (
160
+ left.disposed !== right.disposed ||
161
+ left.runtimeId !== right.runtimeId ||
162
+ left.passes.length !== right.passes.length
163
+ ) {
164
+ return false;
165
+ }
166
+ const rightByPass = pipelinePassMap(right);
167
+ return left.passes.every((leftPass) => {
168
+ const rightPass = rightByPass.get(leftPass.passId);
169
+ if (!rightPass) return false;
170
+ if (!invalidatedPassIds.has(leftPass.passId)) {
171
+ return TOOLCRAFT_PERFORMANCE_PIPELINE_METRIC_KEYS.every(
172
+ (key) => leftPass[key] === rightPass[key],
173
+ );
174
+ }
175
+ return TOOLCRAFT_PERFORMANCE_PIPELINE_METRIC_KEYS.every((key) =>
176
+ key === "activeResources"
177
+ ? leftPass[key] === rightPass[key]
178
+ : leftPass[key] <= rightPass[key],
179
+ );
180
+ });
181
+ }
182
+
154
183
  export function getPipelinePhaseContinuityErrors({
155
184
  cold,
156
185
  path,
@@ -164,15 +193,29 @@ export function getPipelinePhaseContinuityErrors({
164
193
  }): string[] {
165
194
  if (path.interaction === "initial-render") return [];
166
195
 
196
+ const continuousAnimation = path.interaction === "animation-frame";
197
+ const invalidatedPassIds = new Set(path.invalidates);
198
+ const snapshotsPreservePhaseBoundary =
199
+ continuousAnimation
200
+ ? (left: ToolcraftPerformancePipelineSnapshot, right: ToolcraftPerformancePipelineSnapshot) =>
201
+ snapshotsPreserveContinuousAnimation(
202
+ left,
203
+ right,
204
+ invalidatedPassIds,
205
+ )
206
+ : snapshotsMatch;
207
+ const continuityRequirement = continuousAnimation
208
+ ? "monotonic invalidated-pass and exact non-invalidated-pass"
209
+ : "exact";
167
210
  const errors: string[] = [];
168
- if (!snapshotsMatch(cold.after, warm.before)) {
211
+ if (!snapshotsPreservePhaseBoundary(cold.after, warm.before)) {
169
212
  errors.push(
170
- `Path "${path.id}" must preserve exact pipeline counters from cold.after to warm.before.`,
213
+ `Path "${path.id}" must preserve ${continuityRequirement} pipeline counters from cold.after to warm.before.`,
171
214
  );
172
215
  }
173
- if (!snapshotsMatch(warm.after, sustained.before)) {
216
+ if (!snapshotsPreservePhaseBoundary(warm.after, sustained.before)) {
174
217
  errors.push(
175
- `Path "${path.id}" must preserve exact pipeline counters from warm.after to sustained.before.`,
218
+ `Path "${path.id}" must preserve ${continuityRequirement} pipeline counters from warm.after to sustained.before.`,
176
219
  );
177
220
  }
178
221
  return errors;