@pixel-point/toolcraft 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/package.json +1 -1
  2. package/src/generate-performance-iteration-execution.test.mjs +2 -2
  3. package/src/generate-test-demand-only-lifecycle.mjs +14 -3
  4. package/src/generate-test-dependency-sandbox.mjs +42 -5
  5. package/src/generate-test-doc-assertions.mjs +109 -69
  6. package/src/generate-test-doc-assertions.test.mjs +65 -0
  7. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  8. package/src/generate-test-file-assertions.mjs +36 -118
  9. package/src/generate-test-output-assertions.mjs +95 -0
  10. package/src/generate-test-package-assertions.mjs +6 -0
  11. package/src/generate-test-performance-boundary-assertions.mjs +45 -0
  12. package/src/generate-test-product-fixture.mjs +1 -0
  13. package/src/generate-test-product-fixture.test.mjs +18 -0
  14. package/src/generate-test-product-iteration.mjs +1 -0
  15. package/src/generate-test-product-iteration.test.mjs +18 -0
  16. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  17. package/src/generate-test-standalone-assertions.mjs +10 -0
  18. package/src/generate-test-standalone-delivery-stages.mjs +14 -6
  19. package/src/generate.test.mjs +14 -0
  20. package/templates/runtime/contracts/component-contracts.choices.test.ts +59 -12
  21. package/templates/runtime/contracts/component-contracts.choices.ts +58 -12
  22. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  23. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  24. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  25. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  26. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +21 -6
  27. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  28. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +4 -1
  29. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +4 -1
  30. package/templates/runtime/contracts/component-contracts.runtime.ts +15 -10
  31. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  32. package/templates/runtime/contracts/decision-contracts.test.ts +43 -4
  33. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  34. package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
  35. package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
  36. package/templates/runtime/export/artifact-download.test.ts +71 -0
  37. package/templates/runtime/export/artifact-download.ts +80 -0
  38. package/templates/runtime/export/artifact-export-request.ts +23 -0
  39. package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
  40. package/templates/runtime/export/artifact-export-settings.ts +96 -0
  41. package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
  42. package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
  43. package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
  44. package/templates/runtime/export/artifact-frame-state.ts +44 -0
  45. package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
  46. package/templates/runtime/export/artifact-scene-frame.ts +118 -0
  47. package/templates/runtime/export/export-background.ts +46 -0
  48. package/templates/runtime/export/export-error.ts +43 -0
  49. package/templates/runtime/export/export.test.ts +49 -226
  50. package/templates/runtime/export/image-artifact-export.test.ts +111 -0
  51. package/templates/runtime/export/image-artifact-export.ts +145 -0
  52. package/templates/runtime/export/index.ts +44 -3
  53. package/templates/runtime/export/product-export-renderer.test.ts +77 -0
  54. package/templates/runtime/export/product-export-renderer.ts +63 -0
  55. package/templates/runtime/export/video-artifact-export.test.ts +166 -0
  56. package/templates/runtime/export/video-artifact-export.ts +164 -0
  57. package/templates/runtime/export/video-encoding-backend.ts +124 -0
  58. package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
  59. package/templates/runtime/export/video-encoding-policy.ts +86 -0
  60. package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
  61. package/templates/runtime/export/video-frame-schedule.ts +42 -0
  62. package/templates/runtime/index.ts +3 -0
  63. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  64. package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
  65. package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
  66. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +15 -304
  67. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +41 -27
  68. package/templates/runtime/react/app-shell/toolcraft-app.tsx +39 -11
  69. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
  70. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
  71. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
  72. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
  73. package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
  74. package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
  75. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
  76. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
  77. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  78. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  79. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  80. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  81. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  82. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  83. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  84. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  85. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  86. package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
  87. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +18 -84
  88. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  89. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  90. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +43 -87
  91. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +68 -0
  92. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  93. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  94. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  95. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  96. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  97. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  98. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  99. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  100. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  101. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  102. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  103. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  104. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  105. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  106. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  107. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  108. package/templates/runtime/react/index.ts +4 -0
  109. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  110. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  111. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  112. package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
  113. package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
  114. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  115. package/templates/runtime/scene/scene-bounds.ts +32 -19
  116. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  117. package/templates/runtime/schema/canvas-render-scale.ts +62 -0
  118. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  119. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  120. package/templates/runtime/schema/control-applicability.ts +433 -0
  121. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  122. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  123. package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
  124. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  125. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +62 -3
  126. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  127. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  128. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  129. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  130. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  131. package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
  132. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  133. package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
  134. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  135. package/templates/runtime/schema/define-toolcraft.ts +1 -1
  136. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  137. package/templates/runtime/schema/schema-resolvers.ts +0 -56
  138. package/templates/runtime/schema/types.ts +72 -17
  139. package/templates/runtime/state/canvas-background-state.ts +6 -0
  140. package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
  141. package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
  142. package/templates/starter/AGENTS.md +18 -15
  143. package/templates/starter/NOTICE.md +6 -0
  144. package/templates/starter/docs/toolcraft/README.md +2 -1
  145. package/templates/starter/docs/toolcraft/acceptance-testing.md +23 -35
  146. package/templates/starter/docs/toolcraft/agent-worklog.md +18 -3
  147. package/templates/starter/docs/toolcraft/assembly-workflow.md +13 -5
  148. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  149. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  150. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  151. package/templates/starter/docs/toolcraft/core/media-upload.md +4 -2
  152. package/templates/starter/docs/toolcraft/core/performance.md +14 -3
  153. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +16 -3
  154. package/templates/starter/docs/toolcraft/core/setup-export.md +49 -19
  155. package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
  156. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  157. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  158. package/templates/starter/docs/toolcraft/performance.md +20 -6
  159. package/templates/starter/docs/toolcraft/renderer-technique.md +5 -1
  160. package/templates/starter/docs/toolcraft/schema-reference.md +23 -25
  161. package/templates/starter/docs/toolcraft/workflow.md +7 -2
  162. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  163. package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
  164. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +50 -3
  165. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  166. package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
  167. package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
  168. package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
  169. package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
  170. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +5 -34
  171. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -16
  172. package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
  173. package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
  174. package/templates/starter/e2e/app-performance.spec.ts +16 -9
  175. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  176. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  177. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +29 -341
  178. package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
  179. package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
  180. package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
  181. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  182. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  183. package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
  184. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  185. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +13 -3
  186. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +72 -35
  187. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  188. package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
  189. package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
  190. package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
  191. package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
  192. package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
  193. package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
  194. package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
  195. package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
  196. package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
  197. package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
  198. package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
  199. package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
  200. package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
  201. package/templates/starter/e2e/performance-path-helpers.ts +210 -91
  202. package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
  203. package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
  204. package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
  205. package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
  206. package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
  207. package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
  208. package/templates/starter/gitignore +1 -0
  209. package/templates/starter/package.json +1 -0
  210. package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
  211. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  212. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
  213. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
  214. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
  215. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +95 -103
  216. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  217. package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
  218. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -26
  219. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  220. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  221. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  222. package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
  223. package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
  224. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +157 -0
  225. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  226. package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
  227. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  228. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  229. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
  230. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +157 -206
  231. package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
  232. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
  233. package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
  234. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  235. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  236. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  237. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  238. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  239. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  240. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  241. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  242. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  243. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  244. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  245. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  246. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  247. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  248. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  249. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  250. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  251. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  252. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  253. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
  254. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  255. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  256. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  257. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  258. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  259. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  260. package/templates/starter/src/app/acceptance/output-export.ts +35 -12
  261. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  262. package/templates/starter/src/app/acceptance/types.ts +30 -8
  263. package/templates/starter/src/app/acceptance/validate-coverage.ts +20 -2
  264. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  265. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  266. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  267. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  268. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  269. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  270. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  271. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  272. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  273. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  274. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  275. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
  276. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  277. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  278. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  279. package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
  280. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  281. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  282. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  283. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +46 -1
  284. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  285. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  286. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  287. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  288. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  289. package/templates/starter/src/app/starter-acceptance.ts +13 -2
  290. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  291. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  292. package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
  293. package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
  294. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
  295. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +4 -2
  296. package/templates/starter/src/routes/index.tsx +2 -0
  297. package/templates/ui/components/composites/tabs.tsx +2 -2
  298. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  299. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  300. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  301. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  302. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  303. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  304. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  305. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  306. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  307. package/templates/ui/components/controls/index.ts +3 -0
  308. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  309. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  310. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  311. package/templates/runtime/export/export.ts +0 -166
  312. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  313. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
@@ -0,0 +1,51 @@
1
+ import { vi } from "vitest";
2
+
3
+ import { defineToolcraft } from "../../schema/define-toolcraft";
4
+ import type { ToolcraftSourceAssetCoordinator } from "../../source-assets/source-asset-coordinator";
5
+ import { createToolcraftState } from "../../state/create-template-state";
6
+ import { createToolcraftExternalStore } from "../../state/toolcraft-external-store";
7
+ import { useToolcraftSourceAssetCoordinator } from "./toolcraft-source-asset-context";
8
+
9
+ export function ToolcraftSourceAssetCoordinatorProbe({
10
+ capture,
11
+ }: {
12
+ capture: (coordinator: ToolcraftSourceAssetCoordinator) => void;
13
+ }): null {
14
+ capture(useToolcraftSourceAssetCoordinator());
15
+ return null;
16
+ }
17
+
18
+ export function createTestSourceAssetCoordinator(
19
+ overrides: Partial<ToolcraftSourceAssetCoordinator> = {},
20
+ ): ToolcraftSourceAssetCoordinator {
21
+ const idleOperation = Object.freeze({
22
+ phase: "idle" as const,
23
+ target: "source.test",
24
+ });
25
+
26
+ return {
27
+ cancelTarget: vi.fn(),
28
+ clearPresentationFeedback: vi.fn(),
29
+ dispose: vi.fn(async () => undefined),
30
+ getOperation: () => idleOperation,
31
+ getPresentation: () => null,
32
+ hydrateBinaryMedia: vi.fn(async () => undefined),
33
+ hydrateModels: vi.fn(async () => undefined),
34
+ importBatch: vi.fn(async () => ({
35
+ assetIds: [] as const,
36
+ kind: "committed" as const,
37
+ })),
38
+ repairModel: vi.fn(async () => ({ kind: "cancelled" as const })),
39
+ reportPresentationFeedback: vi.fn(),
40
+ subscribe: () => () => undefined,
41
+ ...overrides,
42
+ };
43
+ }
44
+
45
+ export function createTestSourceAssetStore() {
46
+ return createToolcraftExternalStore(
47
+ createToolcraftState(
48
+ defineToolcraft({ canvas: { enabled: true, upload: true }, panels: {} }),
49
+ ),
50
+ );
51
+ }
@@ -0,0 +1,138 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import { defineToolcraft } from "../../schema/define-toolcraft";
4
+ import { createToolcraftState } from "../../state/create-template-state";
5
+ import {
6
+ createToolcraftExternalStore,
7
+ type ToolcraftExternalStore,
8
+ } from "../../state/toolcraft-external-store";
9
+ import type { ToolcraftCommand } from "../../state/types";
10
+ import { createTestSourceAssetCoordinator } from "./toolcraft-source-asset-test-support";
11
+ import { decorateToolcraftUnavailableResourceProofCoordinator } from "./toolcraft-unavailable-resource-proof";
12
+
13
+ const PROOF_REQUEST = "toolcraft.browser-proof.unavailable-resource-request";
14
+ const RESOURCE_REF = `media:image:sha256:${"d".repeat(64)}`;
15
+
16
+ function createDeferred<T>() {
17
+ let resolve!: (value: T) => void;
18
+ const promise = new Promise<T>((resolvePromise) => {
19
+ resolve = resolvePromise;
20
+ });
21
+ return { promise, resolve };
22
+ }
23
+
24
+ function createReadyImageStore(): ToolcraftExternalStore {
25
+ return createToolcraftExternalStore(
26
+ createToolcraftState(
27
+ defineToolcraft({ canvas: { enabled: true }, panels: {} }),
28
+ {
29
+ mediaAssets: [{
30
+ assetKind: "image",
31
+ fileName: "fixture.png",
32
+ id: "fixture-image",
33
+ layerId: "fixture-layer",
34
+ lifecycle: "ready",
35
+ mimeType: "image/png",
36
+ position: { x: 0, y: 0 },
37
+ resourceRef: RESOURCE_REF,
38
+ size: { height: 20, unit: "px", width: 20 },
39
+ sourceTarget: "source.images",
40
+ }],
41
+ },
42
+ ),
43
+ );
44
+ }
45
+
46
+ function dispatchProofRequest(action: "activate" | "cleanup"): void {
47
+ document.dispatchEvent(new CustomEvent(PROOF_REQUEST, {
48
+ detail: {
49
+ action,
50
+ fileName: "fixture.png",
51
+ requestId: "proof-request",
52
+ sourceTarget: "source.images",
53
+ },
54
+ }));
55
+ }
56
+
57
+ describe("Toolcraft unavailable-resource proof coordinator", () => {
58
+ it("settles an aborted pending request without a post-dispose transition", async () => {
59
+ const deferredBytes = createDeferred<Uint8Array>();
60
+ const baseDispose = vi.fn(async () => undefined);
61
+ const resolveResource = vi.fn(
62
+ (
63
+ _ref: string,
64
+ _options: Readonly<{ signal: AbortSignal }>,
65
+ ) => deferredBytes.promise,
66
+ );
67
+ const backingStore = createReadyImageStore();
68
+ const dispatch = vi.fn(backingStore.dispatch);
69
+ const store: ToolcraftExternalStore = { ...backingStore, dispatch };
70
+ const coordinator = decorateToolcraftUnavailableResourceProofCoordinator(
71
+ createTestSourceAssetCoordinator({
72
+ dispose: baseDispose,
73
+ resolveResource,
74
+ }),
75
+ store,
76
+ );
77
+
78
+ dispatchProofRequest("activate");
79
+ await vi.waitFor(() => expect(resolveResource).toHaveBeenCalledTimes(1));
80
+
81
+ const disposal = coordinator.dispose();
82
+ deferredBytes.resolve(new Uint8Array([1, 2, 3]));
83
+ await disposal;
84
+ await Promise.resolve();
85
+ await Promise.resolve();
86
+ await Promise.resolve();
87
+
88
+ expect(store.getCommittedState().mediaAssets[0]).toMatchObject({
89
+ lifecycle: "ready",
90
+ });
91
+ expect(resolveResource.mock.calls[0]?.[1].signal.aborted).toBe(true);
92
+ expect(dispatch).not.toHaveBeenCalled();
93
+ expect(baseDispose).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it("attempts base disposal when proof restoration dispatch fails", async () => {
97
+ const dispatchFailure = new Error("proof restoration dispatch failed");
98
+ const baseFailure = new Error("base coordinator disposal failed");
99
+ const baseDispose = vi.fn(async () => Promise.reject(baseFailure));
100
+ const backingStore = createReadyImageStore();
101
+ const store: ToolcraftExternalStore = {
102
+ ...backingStore,
103
+ dispatch: vi.fn((command: ToolcraftCommand) => {
104
+ if (
105
+ command.type === "media.setBinaryResourceState" &&
106
+ command.lifecycle === "ready"
107
+ ) {
108
+ throw dispatchFailure;
109
+ }
110
+ backingStore.dispatch(command);
111
+ }),
112
+ };
113
+ const coordinator = decorateToolcraftUnavailableResourceProofCoordinator(
114
+ createTestSourceAssetCoordinator({
115
+ dispose: baseDispose,
116
+ resolveResource: vi.fn(async () => new Uint8Array([1, 2, 3])),
117
+ }),
118
+ store,
119
+ );
120
+
121
+ dispatchProofRequest("activate");
122
+ await vi.waitFor(() =>
123
+ expect(store.getCommittedState().mediaAssets[0]).toMatchObject({
124
+ lifecycle: "unavailable",
125
+ })
126
+ );
127
+
128
+ const disposal = coordinator.dispose();
129
+ const repeatedDisposal = coordinator.dispose();
130
+
131
+ await expect(disposal).rejects.toMatchObject({
132
+ errors: [dispatchFailure, baseFailure],
133
+ });
134
+ expect(repeatedDisposal).toBe(disposal);
135
+ await expect(repeatedDisposal).rejects.toBeInstanceOf(AggregateError);
136
+ expect(baseDispose).toHaveBeenCalledTimes(1);
137
+ });
138
+ });
@@ -0,0 +1,378 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ import type { ToolcraftSourceAssetCoordinator } from "../../source-assets/source-asset-coordinator";
4
+ import type { ToolcraftExternalStore } from "../../state/toolcraft-external-store";
5
+ import type { ToolcraftImageAsset } from "../../state/types";
6
+
7
+ const TOOLCRAFT_UNAVAILABLE_RESOURCE_PROOF_REQUEST =
8
+ "toolcraft.browser-proof.unavailable-resource-request";
9
+ const TOOLCRAFT_UNAVAILABLE_RESOURCE_PROOF_ATTRIBUTE =
10
+ "data-toolcraft-unavailable-resource-evidence";
11
+ const toolcraftUnavailableResourceProofEnabled =
12
+ import.meta.env?.MODE === "test" ||
13
+ import.meta.env?.VITE_TOOLCRAFT_BROWSER_PROOF_FIXTURES ===
14
+ "unavailable-resource";
15
+
16
+ type ToolcraftUnavailableResourceProofRequest = Readonly<{
17
+ action: "activate" | "cleanup";
18
+ fileName: string;
19
+ requestId: string;
20
+ sourceTarget?: string;
21
+ }>;
22
+
23
+ type ActiveUnavailableResourceProof = Readonly<{
24
+ assetId: string;
25
+ requestId: string;
26
+ resourceRef: string;
27
+ }>;
28
+
29
+ type PendingUnavailableResourceProofRequest = Readonly<{
30
+ abortController: AbortController;
31
+ promise: Promise<void>;
32
+ requestId: string;
33
+ }>;
34
+
35
+ const cancelledProofRequest = new Error(
36
+ "Unavailable-resource proof request was cancelled.",
37
+ );
38
+
39
+ function readUnavailableResourceProofRequest(
40
+ event: Event,
41
+ ): ToolcraftUnavailableResourceProofRequest | null {
42
+ if (!(event instanceof CustomEvent)) return null;
43
+ const detail: unknown = event.detail;
44
+ if (typeof detail !== "object" || detail === null || Array.isArray(detail)) {
45
+ return null;
46
+ }
47
+
48
+ const request = detail as Partial<ToolcraftUnavailableResourceProofRequest>;
49
+ if (
50
+ (request.action !== "activate" && request.action !== "cleanup") ||
51
+ typeof request.fileName !== "string" ||
52
+ request.fileName.trim() !== request.fileName ||
53
+ request.fileName.length === 0 ||
54
+ typeof request.requestId !== "string" ||
55
+ request.requestId.trim() !== request.requestId ||
56
+ request.requestId.length === 0 ||
57
+ (
58
+ request.sourceTarget !== undefined &&
59
+ (
60
+ typeof request.sourceTarget !== "string" ||
61
+ request.sourceTarget.trim() !== request.sourceTarget ||
62
+ request.sourceTarget.length === 0
63
+ )
64
+ )
65
+ ) {
66
+ return null;
67
+ }
68
+
69
+ return request as ToolcraftUnavailableResourceProofRequest;
70
+ }
71
+
72
+ export function decorateToolcraftUnavailableResourceProofCoordinator(
73
+ coordinator: ToolcraftSourceAssetCoordinator,
74
+ store: ToolcraftExternalStore,
75
+ ): ToolcraftSourceAssetCoordinator {
76
+ if (!toolcraftUnavailableResourceProofEnabled) return coordinator;
77
+
78
+ const resolveResource = coordinator.resolveResource;
79
+ if (!resolveResource) return coordinator;
80
+ const disposeCoordinator = coordinator.dispose;
81
+
82
+ const unavailableResourceRefs = new Set<string>();
83
+ let active: ActiveUnavailableResourceProof | null = null;
84
+ let disposed = false;
85
+ let disposePromise: Promise<void> | null = null;
86
+ let pendingRequest: PendingUnavailableResourceProofRequest | null = null;
87
+
88
+ const proofResolveResource = async (
89
+ ref: string,
90
+ options: Readonly<{ signal: AbortSignal }>,
91
+ ): Promise<Uint8Array | null> =>
92
+ unavailableResourceRefs.has(ref)
93
+ ? null
94
+ : resolveResource(ref, options);
95
+
96
+ const publish = (
97
+ requestId: string,
98
+ observation: Readonly<Record<string, boolean | string>>,
99
+ ): void => {
100
+ const root = document.querySelector<HTMLElement>(
101
+ '[data-slot="toolcraft-runtime-app"]',
102
+ );
103
+ if (!root) return;
104
+ root.setAttribute(
105
+ TOOLCRAFT_UNAVAILABLE_RESOURCE_PROOF_ATTRIBUTE,
106
+ JSON.stringify({ requestId, ...observation }),
107
+ );
108
+ };
109
+
110
+ const handleRequest = (event: Event): void => {
111
+ const request = readUnavailableResourceProofRequest(event);
112
+ if (!request || disposed) return;
113
+
114
+ if (pendingRequest) {
115
+ publish(request.requestId, {
116
+ message: `Unavailable-resource proof request "${pendingRequest.requestId}" is still pending.`,
117
+ status: "error",
118
+ });
119
+ return;
120
+ }
121
+ const abortController = new AbortController();
122
+ const assertRequestActive = (): void => {
123
+ if (disposed || abortController.signal.aborted) {
124
+ throw cancelledProofRequest;
125
+ }
126
+ };
127
+ const promise = Promise.resolve().then(async () => {
128
+ assertRequestActive();
129
+ if (request.action === "cleanup") {
130
+ if (!active || active.requestId !== request.requestId) {
131
+ throw new Error(
132
+ "Unavailable-resource proof cleanup must match the active fixture request.",
133
+ );
134
+ }
135
+
136
+ const current = active;
137
+ unavailableResourceRefs.delete(current.resourceRef);
138
+ const restoredBytes = await proofResolveResource(
139
+ current.resourceRef,
140
+ { signal: abortController.signal },
141
+ );
142
+ assertRequestActive();
143
+ if (!restoredBytes?.byteLength) {
144
+ throw new Error(
145
+ "Unavailable-resource proof cleanup could not restore the real resolver.",
146
+ );
147
+ }
148
+ store.dispatch({
149
+ assetId: current.assetId,
150
+ expectedResourceRef: current.resourceRef,
151
+ lifecycle: "ready",
152
+ type: "media.setBinaryResourceState",
153
+ });
154
+ assertRequestActive();
155
+ const restored = store
156
+ .getCommittedState()
157
+ .mediaAssets.find((asset) => asset.id === current.assetId);
158
+ if (
159
+ !restored ||
160
+ restored.assetKind !== "image" ||
161
+ restored.lifecycle !== "ready"
162
+ ) {
163
+ throw new Error(
164
+ "Unavailable-resource proof cleanup did not restore the typed resource state.",
165
+ );
166
+ }
167
+
168
+ active = null;
169
+ assertRequestActive();
170
+ publish(request.requestId, {
171
+ assetId: current.assetId,
172
+ fileName: request.fileName,
173
+ lifecycle: "ready",
174
+ resolverAvailable: true,
175
+ status: "cleaned",
176
+ });
177
+ return;
178
+ }
179
+
180
+ if (active) {
181
+ throw new Error(
182
+ "Unavailable-resource proof supports exactly one active resource fixture.",
183
+ );
184
+ }
185
+ const candidates = store
186
+ .getCommittedState()
187
+ .mediaAssets.filter(
188
+ (
189
+ asset,
190
+ ): asset is ToolcraftImageAsset & { lifecycle: "ready" } =>
191
+ asset.assetKind === "image" &&
192
+ asset.lifecycle === "ready" &&
193
+ asset.fileName === request.fileName &&
194
+ (
195
+ request.sourceTarget === undefined ||
196
+ asset.sourceTarget === request.sourceTarget
197
+ ),
198
+ );
199
+ if (candidates.length !== 1) {
200
+ throw new Error(
201
+ `Unavailable-resource proof requires exactly one ready image named "${request.fileName}", received ${candidates.length}.`,
202
+ );
203
+ }
204
+ const asset = candidates[0];
205
+ if (!asset) {
206
+ throw new Error("Unavailable-resource proof image selection failed.");
207
+ }
208
+
209
+ const initialBytes = await proofResolveResource(
210
+ asset.resourceRef,
211
+ { signal: abortController.signal },
212
+ );
213
+ assertRequestActive();
214
+ if (!initialBytes?.byteLength) {
215
+ throw new Error(
216
+ "Unavailable-resource proof requires a resource available through the real resolver before fault activation.",
217
+ );
218
+ }
219
+
220
+ unavailableResourceRefs.add(asset.resourceRef);
221
+ const activatedProof: ActiveUnavailableResourceProof = {
222
+ assetId: asset.id,
223
+ requestId: request.requestId,
224
+ resourceRef: asset.resourceRef,
225
+ };
226
+ try {
227
+ const faultedBytes = await proofResolveResource(
228
+ asset.resourceRef,
229
+ { signal: abortController.signal },
230
+ );
231
+ assertRequestActive();
232
+ if (faultedBytes !== null) {
233
+ throw new Error(
234
+ "Unavailable-resource proof did not isolate the requested resolver resource.",
235
+ );
236
+ }
237
+ active = activatedProof;
238
+ store.dispatch({
239
+ assetId: asset.id,
240
+ error: {
241
+ code: "browser-proof-resource-unavailable",
242
+ message:
243
+ "The resource is intentionally unavailable for protected browser proof.",
244
+ },
245
+ expectedResourceRef: asset.resourceRef,
246
+ lifecycle: "unavailable",
247
+ type: "media.setBinaryResourceState",
248
+ });
249
+ assertRequestActive();
250
+ const unavailable = store
251
+ .getCommittedState()
252
+ .mediaAssets.find((candidate) => candidate.id === asset.id);
253
+ if (
254
+ !unavailable ||
255
+ unavailable.assetKind !== "image" ||
256
+ unavailable.lifecycle !== "unavailable"
257
+ ) {
258
+ throw new Error(
259
+ "Unavailable-resource proof did not reach the typed unavailable state.",
260
+ );
261
+ }
262
+
263
+ assertRequestActive();
264
+ publish(request.requestId, {
265
+ assetId: asset.id,
266
+ fileName: asset.fileName,
267
+ lifecycle: "unavailable",
268
+ resolverAvailable: false,
269
+ status: "active",
270
+ });
271
+ } catch (error) {
272
+ unavailableResourceRefs.delete(asset.resourceRef);
273
+ if (!disposed && active === activatedProof) active = null;
274
+ throw error;
275
+ }
276
+ });
277
+ const trackedRequest: PendingUnavailableResourceProofRequest = {
278
+ abortController,
279
+ promise,
280
+ requestId: request.requestId,
281
+ };
282
+ pendingRequest = trackedRequest;
283
+ void promise
284
+ .catch((error: unknown) => {
285
+ if (
286
+ error === cancelledProofRequest ||
287
+ disposed ||
288
+ abortController.signal.aborted
289
+ ) {
290
+ return;
291
+ }
292
+ publish(request.requestId, {
293
+ message:
294
+ error instanceof Error
295
+ ? error.message
296
+ : "Unavailable-resource proof fixture failed.",
297
+ status: "error",
298
+ });
299
+ })
300
+ .finally(() => {
301
+ if (pendingRequest === trackedRequest) pendingRequest = null;
302
+ });
303
+ };
304
+
305
+ const dispose = (): Promise<void> => {
306
+ if (!disposePromise) {
307
+ disposed = true;
308
+ document.removeEventListener(
309
+ TOOLCRAFT_UNAVAILABLE_RESOURCE_PROOF_REQUEST,
310
+ handleRequest,
311
+ );
312
+ const requestAtDisposal = pendingRequest;
313
+ disposePromise = Promise.resolve().then(async () => {
314
+ const failures: unknown[] = [];
315
+ requestAtDisposal?.abortController.abort();
316
+ if (requestAtDisposal) {
317
+ try {
318
+ await requestAtDisposal.promise;
319
+ } catch {
320
+ // Request failures are fixture observations, not disposal failures.
321
+ }
322
+ if (pendingRequest === requestAtDisposal) pendingRequest = null;
323
+ }
324
+ const activeAtDisposal = active;
325
+ if (activeAtDisposal) {
326
+ unavailableResourceRefs.delete(activeAtDisposal.resourceRef);
327
+ try {
328
+ store.dispatch({
329
+ assetId: activeAtDisposal.assetId,
330
+ expectedResourceRef: activeAtDisposal.resourceRef,
331
+ lifecycle: "ready",
332
+ type: "media.setBinaryResourceState",
333
+ });
334
+ const restored = store
335
+ .getCommittedState()
336
+ .mediaAssets.find(
337
+ (asset) => asset.id === activeAtDisposal.assetId,
338
+ );
339
+ if (
340
+ !restored ||
341
+ restored.assetKind !== "image" ||
342
+ restored.lifecycle !== "ready"
343
+ ) {
344
+ throw new Error(
345
+ "Unavailable-resource proof disposal did not restore the typed resource state.",
346
+ );
347
+ }
348
+ } catch (error) {
349
+ failures.push(error);
350
+ } finally {
351
+ active = null;
352
+ }
353
+ }
354
+ unavailableResourceRefs.clear();
355
+ try {
356
+ await disposeCoordinator.call(coordinator);
357
+ } catch (error) {
358
+ failures.push(error);
359
+ }
360
+ if (failures.length > 0) {
361
+ throw new AggregateError(
362
+ failures,
363
+ "Toolcraft unavailable-resource proof failed to dispose.",
364
+ );
365
+ }
366
+ });
367
+ }
368
+ return disposePromise;
369
+ };
370
+
371
+ coordinator.dispose = dispose;
372
+ coordinator.resolveResource = proofResolveResource;
373
+ document.addEventListener(
374
+ TOOLCRAFT_UNAVAILABLE_RESOURCE_PROOF_REQUEST,
375
+ handleRequest,
376
+ );
377
+ return coordinator;
378
+ }
@@ -8,12 +8,49 @@ import {
8
8
  createUploadDefaultSchema,
9
9
  renderCanvasShell,
10
10
  } from "./canvas-shell-test-utils";
11
+ import { useToolcraftProductSceneFrame } from "./product-scene-surface";
11
12
 
12
13
  afterEach(() => {
13
14
  cleanup();
14
15
  });
15
16
 
16
17
  describe("CanvasShell rendering", () => {
18
+ it("uses product bounds as the live infinite preview frame", () => {
19
+ function ProductFrameProbe(): React.JSX.Element {
20
+ const frame = useToolcraftProductSceneFrame();
21
+ return <output data-testid="product-frame">{JSON.stringify(frame)}</output>;
22
+ }
23
+
24
+ const { container } = renderCanvasShell({
25
+ children: <ProductFrameProbe />,
26
+ initialState: {
27
+ canvas: {
28
+ mode: "infinite",
29
+ offset: { x: 0, y: 0 },
30
+ size: { height: 200, unit: "px", width: 300 },
31
+ zoom: 100,
32
+ },
33
+ },
34
+ productSceneBoundsProvider: () => [
35
+ { height: 260, width: 420, x: -210, y: -130 },
36
+ ],
37
+ });
38
+ const productScene = container.querySelector<HTMLElement>(
39
+ "[data-toolcraft-product-scene]",
40
+ );
41
+
42
+ expect(productScene?.style.height).toBe("260px");
43
+ expect(productScene?.style.left).toBe("-210px");
44
+ expect(productScene?.style.top).toBe("-130px");
45
+ expect(productScene?.style.width).toBe("420px");
46
+ expect(screen.getByTestId("product-frame").textContent).toBe(
47
+ JSON.stringify({
48
+ kind: "infinite",
49
+ rect: { height: 260, width: 420, x: -210, y: -130 },
50
+ }),
51
+ );
52
+ });
53
+
17
54
  it("renders the editable canvas at the schema size", () => {
18
55
  const { container } = renderCanvasShell();
19
56
 
@@ -59,6 +96,45 @@ describe("CanvasShell rendering", () => {
59
96
  ).toBe("");
60
97
  });
61
98
 
99
+ it("mounts product viewport content only in Infinity behind the transformed world", () => {
100
+ const finite = renderCanvasShell({
101
+ infiniteCanvasContent: <div data-testid="infinite-viewport-content" />,
102
+ });
103
+
104
+ expect(
105
+ finite.container.querySelector("[data-toolcraft-infinite-canvas-content]"),
106
+ ).toBeNull();
107
+ finite.unmount();
108
+
109
+ const { container } = renderCanvasShell({
110
+ children: <div data-testid="bounded-product-content" />,
111
+ infiniteCanvasContent: <div data-testid="infinite-viewport-content" />,
112
+ initialState: {
113
+ canvas: {
114
+ mode: "infinite",
115
+ offset: { x: 42, y: -18 },
116
+ size: { height: 200, unit: "px", width: 300 },
117
+ zoom: 75,
118
+ },
119
+ },
120
+ });
121
+ const backdrop = container.querySelector<HTMLElement>(
122
+ "[data-toolcraft-infinite-canvas-content]",
123
+ );
124
+ const world = container.querySelector<HTMLElement>(
125
+ "[data-toolcraft-canvas-world]",
126
+ );
127
+
128
+ expect(backdrop).toBeTruthy();
129
+ expect(backdrop?.className).toContain("pointer-events-none");
130
+ expect(backdrop?.className).toContain("absolute");
131
+ expect(backdrop?.className).toContain("inset-0");
132
+ expect(backdrop?.style.transform).toBe("");
133
+ expect(backdrop?.nextElementSibling).toBe(world);
134
+ expect(screen.getByTestId("infinite-viewport-content")).toBeTruthy();
135
+ expect(screen.getByTestId("bounded-product-content")).toBeTruthy();
136
+ });
137
+
62
138
  it("fills the complete infinite viewport with the selected Background color", () => {
63
139
  const { container } = renderCanvasShell({
64
140
  initialState: {