@pixel-point/toolcraft 0.0.14 → 0.0.15

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 (577) hide show
  1. package/README.md +13 -2
  2. package/package.json +1 -1
  3. package/scripts/prepare-pack.mjs +2 -28
  4. package/src/cli-io.mjs +174 -0
  5. package/src/cli-package-manager-flow.test.mjs +206 -0
  6. package/src/cli.mjs +7 -495
  7. package/src/cli.test.mjs +34 -177
  8. package/src/create-command.mjs +152 -0
  9. package/src/create-options.mjs +194 -0
  10. package/src/generate-test-assertions.mjs +27 -0
  11. package/src/generate-test-doc-assertions.mjs +162 -0
  12. package/src/generate-test-file-assertions.mjs +251 -0
  13. package/src/generate-test-fs-utils.mjs +59 -0
  14. package/src/generate-test-integrity-assertions.mjs +30 -0
  15. package/src/generate-test-integrity-framework-assertions.mjs +222 -0
  16. package/src/generate-test-integrity-manifest-assertions.mjs +150 -0
  17. package/src/generate-test-integrity-ownership-assertions.mjs +127 -0
  18. package/src/generate-test-integrity-test-utils.mjs +102 -0
  19. package/src/generate-test-package-assertions.mjs +75 -0
  20. package/src/generate-test-product-fixture.mjs +84 -0
  21. package/src/generate-test-product-fixture.test.mjs +73 -0
  22. package/src/generate-test-standalone-assertions.mjs +209 -0
  23. package/src/generate-transactional.test.mjs +151 -0
  24. package/src/generate.mjs +182 -102
  25. package/src/generate.test.mjs +242 -338
  26. package/src/generated-integrity-manifest.mjs +127 -0
  27. package/src/generated-integrity-manifest.test.mjs +151 -0
  28. package/src/generated-source-policy.mjs +22 -0
  29. package/src/generated-source-policy.test.mjs +35 -0
  30. package/src/generated-target-policy.mjs +55 -0
  31. package/src/generation-transaction.mjs +135 -0
  32. package/src/generation-transaction.test.mjs +74 -0
  33. package/src/package-json.mjs +18 -1
  34. package/src/package-json.test.mjs +38 -5
  35. package/src/package-manager.mjs +2 -0
  36. package/src/package-manager.test.mjs +5 -1
  37. package/src/packaged-template-fallback.test.mjs +126 -0
  38. package/src/prepare-pack-templates.mjs +34 -0
  39. package/templates/runtime/contracts/component-contract-builders.ts +95 -0
  40. package/templates/runtime/contracts/component-contracts.choices.test.ts +293 -0
  41. package/templates/runtime/contracts/component-contracts.choices.ts +322 -0
  42. package/templates/runtime/contracts/component-contracts.inputs.test.ts +210 -0
  43. package/templates/runtime/contracts/component-contracts.inputs.ts +252 -0
  44. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +116 -0
  45. package/templates/runtime/contracts/component-contracts.media-custom.ts +144 -0
  46. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +229 -0
  47. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +76 -0
  48. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +135 -0
  49. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +148 -0
  50. package/templates/runtime/contracts/component-contracts.runtime.ts +361 -0
  51. package/templates/runtime/contracts/component-contracts.test.ts +39 -1458
  52. package/templates/runtime/contracts/component-contracts.ts +44 -1505
  53. package/templates/runtime/contracts/component-contracts.visual.test.ts +316 -0
  54. package/templates/runtime/contracts/component-contracts.visual.ts +385 -0
  55. package/templates/runtime/contracts/contract-manifest.test.ts +64 -0
  56. package/templates/runtime/contracts/decision-contracts.test.ts +29 -4
  57. package/templates/runtime/contracts/decision-contracts.ts +38 -1
  58. package/templates/runtime/index.ts +1 -0
  59. package/templates/runtime/react/{runtime-public-api.test.tsx → app-shell/runtime-public-api.test.tsx} +29 -7
  60. package/templates/runtime/react/{settings-transfer.test.ts → app-shell/settings-transfer.test.ts} +4 -4
  61. package/templates/runtime/react/{settings-transfer.ts → app-shell/settings-transfer.ts} +3 -3
  62. package/templates/runtime/react/{toolbar-panel.test.tsx → app-shell/toolbar-panel.test.tsx} +2 -2
  63. package/templates/runtime/react/{toolbar-panel.tsx → app-shell/toolbar-panel.tsx} +3 -3
  64. package/templates/runtime/react/{toolcraft-app.integration.test.tsx → app-shell/toolcraft-app.integration.test.tsx} +6 -6
  65. package/templates/runtime/react/{toolcraft-app.test.tsx → app-shell/toolcraft-app.test.tsx} +3 -3
  66. package/templates/runtime/react/{toolcraft-app.tsx → app-shell/toolcraft-app.tsx} +11 -8
  67. package/templates/runtime/react/{toolcraft-root.test.tsx → app-shell/toolcraft-root.test.tsx} +1 -1
  68. package/templates/runtime/react/{toolcraft-root.tsx → app-shell/toolcraft-root.tsx} +5 -5
  69. package/templates/runtime/react/{use-toolcraft.ts → app-shell/use-toolcraft.ts} +1 -1
  70. package/templates/runtime/react/canvas/canvas-default-media-layer.tsx +81 -0
  71. package/templates/runtime/react/canvas/canvas-media-transform.ts +33 -0
  72. package/templates/runtime/react/canvas/canvas-shell-interactions.test.tsx +156 -0
  73. package/templates/runtime/react/canvas/canvas-shell-media-rendering.test.tsx +87 -0
  74. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +102 -0
  75. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +108 -0
  76. package/templates/runtime/react/canvas/canvas-shell-upload.test.tsx +106 -0
  77. package/templates/runtime/react/canvas/canvas-shell.tsx +146 -0
  78. package/templates/runtime/react/canvas/canvas-upload-routing.ts +84 -0
  79. package/templates/runtime/react/{media-file.ts → canvas/media-file.ts} +1 -1
  80. package/templates/runtime/react/canvas/use-canvas-drop-import.ts +145 -0
  81. package/templates/runtime/react/canvas/use-canvas-viewport-interactions.ts +227 -0
  82. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +89 -0
  83. package/templates/runtime/react/{controls-panel-filedrop-reorder.test.tsx → controls-panel/__tests__/controls-panel-filedrop-reorder.test.tsx} +4 -4
  84. package/templates/runtime/react/controls-panel/__tests__/controls-panel.actions-layout.test.tsx +91 -0
  85. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +213 -0
  86. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-layout.test.tsx +366 -0
  87. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-picker-models.test.tsx +169 -0
  88. package/templates/runtime/react/controls-panel/__tests__/controls-panel.compound-layout.test.tsx +160 -0
  89. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +352 -0
  90. package/templates/runtime/react/controls-panel/__tests__/controls-panel.curves.test.tsx +226 -0
  91. package/templates/runtime/react/controls-panel/__tests__/controls-panel.floating-shell.test.tsx +13 -0
  92. package/templates/runtime/react/controls-panel/__tests__/controls-panel.font-picker.test.tsx +302 -0
  93. package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +401 -0
  94. package/templates/runtime/react/controls-panel/__tests__/controls-panel.gradient-control.test.tsx +139 -0
  95. package/templates/runtime/react/controls-panel/__tests__/controls-panel.help-policy.test.tsx +103 -0
  96. package/templates/runtime/react/controls-panel/__tests__/controls-panel.image-picker.test.tsx +16 -0
  97. package/templates/runtime/react/controls-panel/__tests__/controls-panel.inline-layout.test.tsx +290 -0
  98. package/templates/runtime/react/controls-panel/__tests__/controls-panel.keyframes.test.tsx +175 -0
  99. package/templates/runtime/react/controls-panel/__tests__/controls-panel.media-upload-files.test.tsx +121 -0
  100. package/templates/runtime/react/controls-panel/__tests__/controls-panel.media-upload-image.test.tsx +363 -0
  101. package/templates/runtime/react/controls-panel/__tests__/controls-panel.media-upload-multi-image.test.tsx +105 -0
  102. package/templates/runtime/react/controls-panel/__tests__/controls-panel.range-slider-editing.test.tsx +149 -0
  103. package/templates/runtime/react/controls-panel/__tests__/controls-panel.section-shell.test.tsx +138 -0
  104. package/templates/runtime/react/controls-panel/__tests__/controls-panel.select-layout.test.tsx +148 -0
  105. package/templates/runtime/react/controls-panel/__tests__/controls-panel.setup-controls.test.tsx +189 -0
  106. package/templates/runtime/react/controls-panel/__tests__/controls-panel.slider-rendering.test.tsx +317 -0
  107. package/templates/runtime/react/controls-panel/__tests__/controls-panel.state-reset.test.tsx +137 -0
  108. package/templates/runtime/react/controls-panel/__tests__/controls-panel.structure.test.tsx +66 -0
  109. package/templates/runtime/react/controls-panel/__tests__/controls-panel.text-input.test.tsx +205 -0
  110. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-interaction.test.tsx +388 -0
  111. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-layout.test.tsx +155 -0
  112. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +172 -0
  113. package/templates/runtime/react/{control-conditions.ts → controls-panel/conditions/control-conditions.ts} +26 -3
  114. package/templates/runtime/react/{control-renderers.ts → controls-panel/control-renderers.ts} +2 -2
  115. package/templates/runtime/react/controls-panel/controls-panel.tsx +250 -0
  116. package/templates/runtime/react/controls-panel/keyframes/controls-panel-keyframes.tsx +265 -0
  117. package/templates/runtime/react/controls-panel/layout/controls-panel-collapse-storage.ts +131 -0
  118. package/templates/runtime/react/controls-panel/layout/controls-panel-help.tsx +132 -0
  119. package/templates/runtime/react/controls-panel/layout/controls-panel-inline-layout.tsx +311 -0
  120. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +216 -0
  121. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +401 -0
  122. package/templates/runtime/react/controls-panel/renderers/controls-panel-action-renderer.tsx +137 -0
  123. package/templates/runtime/react/controls-panel/renderers/controls-panel-basic-renderers.tsx +421 -0
  124. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +286 -0
  125. package/templates/runtime/react/controls-panel/renderers/controls-panel-compound-renderers.tsx +366 -0
  126. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +173 -0
  127. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +23 -0
  128. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +55 -0
  129. package/templates/runtime/react/controls-panel/renderers/controls-panel-settings-transfer-renderer.tsx +45 -0
  130. package/templates/runtime/react/controls-panel/testing/controls-panel-test-utils.tsx +300 -0
  131. package/templates/runtime/react/controls-panel/values/controls-panel-aspect-ratio-values.ts +94 -0
  132. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +87 -0
  133. package/templates/runtime/react/controls-panel/values/controls-panel-color-values.ts +82 -0
  134. package/templates/runtime/react/controls-panel/values/controls-panel-font-values.ts +69 -0
  135. package/templates/runtime/react/controls-panel/values/controls-panel-spatial-values.ts +64 -0
  136. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +93 -0
  137. package/templates/runtime/react/controls-panel/values/controls-panel-value-primitives.ts +30 -0
  138. package/templates/runtime/react/controls-panel/values/controls-panel-values.ts +7 -0
  139. package/templates/runtime/react/index.ts +13 -13
  140. package/templates/runtime/react/{layer-tree.ts → layers/layer-tree.ts} +1 -1
  141. package/templates/runtime/react/layers/layers-panel-drag-drop.test.tsx +190 -0
  142. package/templates/runtime/react/layers/layers-panel-editing.test.tsx +41 -0
  143. package/templates/runtime/react/layers/layers-panel-insert-targets.test.ts +87 -0
  144. package/templates/runtime/react/layers/layers-panel-insert-targets.ts +254 -0
  145. package/templates/runtime/react/layers/layers-panel-model.ts +15 -0
  146. package/templates/runtime/react/layers/layers-panel-rendering.test.tsx +43 -0
  147. package/templates/runtime/react/layers/layers-panel-reorder.ts +148 -0
  148. package/templates/runtime/react/layers/layers-panel-row.tsx +475 -0
  149. package/templates/runtime/react/layers/layers-panel-test-utils.tsx +135 -0
  150. package/templates/runtime/react/layers/layers-panel-visibility.test.tsx +126 -0
  151. package/templates/runtime/react/layers/layers-panel.tsx +349 -0
  152. package/templates/runtime/react/layers/use-layers-panel-drag-controller.ts +327 -0
  153. package/templates/runtime/react/{panel-host-types.ts → panel-host/panel-host-types.ts} +2 -2
  154. package/templates/runtime/react/{panel-host.test.tsx → panel-host/panel-host.test.tsx} +3 -3
  155. package/templates/runtime/react/{panel-host.tsx → panel-host/panel-host.tsx} +1 -1
  156. package/templates/runtime/react/timeline/timeline-easing-editor.tsx +409 -0
  157. package/templates/runtime/react/timeline/timeline-easing-geometry.ts +42 -0
  158. package/templates/runtime/react/timeline/timeline-easing-icons.tsx +135 -0
  159. package/templates/runtime/react/timeline/timeline-easing-model.test.ts +87 -0
  160. package/templates/runtime/react/timeline/timeline-easing-model.ts +271 -0
  161. package/templates/runtime/react/timeline/timeline-easing-popover-content.tsx +227 -0
  162. package/templates/runtime/react/timeline/timeline-easing-popover-layout.ts +69 -0
  163. package/templates/runtime/react/timeline/timeline-easing-popover.tsx +60 -0
  164. package/templates/runtime/react/timeline/timeline-event-targets.ts +19 -0
  165. package/templates/runtime/react/timeline/timeline-expanded-content.tsx +299 -0
  166. package/templates/runtime/react/timeline/timeline-icon-button.tsx +51 -0
  167. package/templates/runtime/react/timeline/timeline-keyframe-row.tsx +360 -0
  168. package/templates/runtime/react/timeline/timeline-keyframes.ts +23 -0
  169. package/templates/runtime/react/timeline/timeline-panel-floating-layout.test.tsx +328 -0
  170. package/templates/runtime/react/timeline/timeline-panel-header.tsx +404 -0
  171. package/templates/runtime/react/timeline/timeline-panel-keyframes.test.tsx +90 -0
  172. package/templates/runtime/react/timeline/timeline-panel-layout.ts +30 -0
  173. package/templates/runtime/react/timeline/timeline-panel-responsive-layout.ts +182 -0
  174. package/templates/runtime/react/timeline/timeline-panel-test-utils.tsx +88 -0
  175. package/templates/runtime/react/timeline/timeline-panel-transport.test.tsx +202 -0
  176. package/templates/runtime/react/timeline/timeline-panel-upload.test.tsx +53 -0
  177. package/templates/runtime/react/timeline/timeline-panel.tsx +449 -0
  178. package/templates/runtime/react/timeline/timeline-playback-hooks.ts +254 -0
  179. package/templates/runtime/schema/assembly-contract.ts +271 -0
  180. package/templates/runtime/schema/control-schema-normalization.ts +55 -0
  181. package/templates/runtime/schema/controls-panel-actions.ts +141 -0
  182. package/templates/runtime/schema/controls-panel-layout-groups.ts +144 -0
  183. package/templates/runtime/schema/controls-panel-normalization.ts +19 -0
  184. package/templates/runtime/schema/controls-panel-section-layout.ts +140 -0
  185. package/templates/runtime/schema/controls-panel-section-titles.ts +161 -0
  186. package/templates/runtime/schema/define-toolcraft-test-utils.ts +18 -0
  187. package/templates/runtime/schema/define-toolcraft.actions.test.ts +182 -0
  188. package/templates/runtime/schema/define-toolcraft.assembly.test.ts +246 -0
  189. package/templates/runtime/schema/define-toolcraft.canvas.test.ts +150 -0
  190. package/templates/runtime/schema/define-toolcraft.control-layout.test.ts +345 -0
  191. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +304 -0
  192. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +69 -0
  193. package/templates/runtime/schema/define-toolcraft.setup-persistence.test.ts +66 -0
  194. package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +116 -0
  195. package/templates/runtime/schema/define-toolcraft.setup-settings-transfer.test.ts +291 -0
  196. package/templates/runtime/schema/define-toolcraft.ts +24 -1449
  197. package/templates/runtime/schema/panels-schema-normalization.ts +49 -0
  198. package/templates/runtime/schema/runtime-section-titles.ts +1 -0
  199. package/templates/runtime/schema/runtime-setup-section.ts +282 -0
  200. package/templates/runtime/schema/schema-resolvers.ts +168 -0
  201. package/templates/runtime/schema/types.ts +17 -0
  202. package/templates/runtime/state/canvas-reducer.ts +106 -0
  203. package/templates/runtime/state/canvas-state.ts +209 -0
  204. package/templates/runtime/state/controls-reducer.ts +277 -0
  205. package/templates/runtime/state/history-patches.ts +174 -0
  206. package/templates/runtime/state/layer-state.ts +12 -0
  207. package/templates/runtime/state/layers-reducer.ts +318 -0
  208. package/templates/runtime/state/media-reducer.ts +283 -0
  209. package/templates/runtime/state/media-state.ts +105 -0
  210. package/templates/runtime/state/panels-reducer.ts +58 -0
  211. package/templates/runtime/state/persistence-merge.ts +53 -0
  212. package/templates/runtime/state/persistence-reader-canvas.ts +27 -0
  213. package/templates/runtime/state/persistence-reader-layers.ts +73 -0
  214. package/templates/runtime/state/persistence-reader-media.ts +82 -0
  215. package/templates/runtime/state/persistence-reader-panels.ts +56 -0
  216. package/templates/runtime/state/persistence-reader-primitives.ts +27 -0
  217. package/templates/runtime/state/persistence-reader-timeline.ts +145 -0
  218. package/templates/runtime/state/persistence-reader-values.ts +40 -0
  219. package/templates/runtime/state/persistence-readers.ts +72 -0
  220. package/templates/runtime/state/persistence-shared.ts +23 -0
  221. package/templates/runtime/state/persistence-snapshot.ts +57 -0
  222. package/templates/runtime/state/persistence.ts +16 -581
  223. package/templates/runtime/state/reducer-canvas-panels.test.ts +324 -0
  224. package/templates/runtime/state/reducer-controls.test.ts +158 -0
  225. package/templates/runtime/state/reducer-layers.test.ts +306 -0
  226. package/templates/runtime/state/reducer-media-import-sizing.test.ts +197 -0
  227. package/templates/runtime/state/reducer-media-reorder-history.test.ts +116 -0
  228. package/templates/runtime/state/reducer-media-reset.test.ts +249 -0
  229. package/templates/runtime/state/reducer-media-test-utils.ts +40 -0
  230. package/templates/runtime/state/reducer-media-transform.test.ts +52 -0
  231. package/templates/runtime/state/reducer-test-utils.ts +30 -0
  232. package/templates/runtime/state/reducer-timeline.test.ts +242 -0
  233. package/templates/runtime/state/reducer.ts +57 -1785
  234. package/templates/runtime/state/timeline-reducer.ts +412 -0
  235. package/templates/runtime/state/timeline-values.test.ts +45 -0
  236. package/templates/runtime/state/timeline-values.ts +45 -0
  237. package/templates/runtime/testing/performance-browser-policy.test.ts +81 -0
  238. package/templates/runtime/testing/performance-browser-policy.ts +102 -0
  239. package/templates/runtime/testing/performance-budget-validation.ts +84 -0
  240. package/templates/runtime/testing/performance-control-classification.test.ts +106 -0
  241. package/templates/runtime/testing/performance-control-classification.ts +101 -0
  242. package/templates/runtime/testing/performance-coverage-context.ts +44 -0
  243. package/templates/runtime/testing/performance-coverage-renderer-policy.ts +221 -0
  244. package/templates/runtime/testing/performance-coverage-scenario-policy.ts +147 -0
  245. package/templates/runtime/testing/performance-coverage-target-policy.ts +121 -0
  246. package/templates/runtime/testing/performance-coverage-validator.ts +34 -0
  247. package/templates/runtime/testing/performance-export-coverage.test.ts +234 -0
  248. package/templates/runtime/testing/performance-fixture-load-profile.test.ts +360 -0
  249. package/templates/runtime/testing/performance-fixture-media.test.ts +80 -0
  250. package/templates/runtime/testing/performance-fixture-stress.test.ts +314 -0
  251. package/templates/runtime/testing/performance-fixture-validation.ts +255 -0
  252. package/templates/runtime/testing/performance-fixture-values.ts +275 -0
  253. package/templates/runtime/testing/performance-fixture-workload.test.ts +140 -0
  254. package/templates/runtime/testing/performance-load-profile-validation.ts +467 -0
  255. package/templates/runtime/testing/performance-renderer-coverage.test.ts +325 -0
  256. package/templates/runtime/testing/performance-renderer-pipeline-coverage.test.ts +115 -0
  257. package/templates/runtime/testing/performance-renderer-pipeline-interactions.test.ts +201 -0
  258. package/templates/runtime/testing/performance-renderer-pipeline-passes.test.ts +247 -0
  259. package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +360 -0
  260. package/templates/runtime/testing/performance-renderer-technique-validation.ts +329 -0
  261. package/templates/runtime/testing/performance-renderer-technique.test.ts +275 -0
  262. package/templates/runtime/testing/performance-renderer-validation.ts +2 -0
  263. package/templates/runtime/testing/performance-scenario-coverage.test.ts +303 -0
  264. package/templates/runtime/testing/performance-scenario-validation.ts +49 -0
  265. package/templates/runtime/testing/performance-schema-derived-workloads.test.ts +231 -0
  266. package/templates/runtime/testing/performance-schema-queries.ts +181 -0
  267. package/templates/runtime/testing/performance-test-fixtures/index.ts +4 -0
  268. package/templates/runtime/testing/performance-test-fixtures/renderer-pipelines.ts +322 -0
  269. package/templates/runtime/testing/performance-test-fixtures/scenarios.ts +172 -0
  270. package/templates/runtime/testing/performance-test-fixtures/schemas.ts +151 -0
  271. package/templates/runtime/testing/performance-test-fixtures/stress-fixtures.ts +133 -0
  272. package/templates/runtime/testing/performance-test-fixtures.ts +1 -0
  273. package/templates/runtime/testing/performance-types.ts +338 -0
  274. package/templates/runtime/testing/performance.ts +15 -2485
  275. package/templates/starter/AGENTS.md +41 -26
  276. package/templates/starter/LICENSE.md +18 -95
  277. package/templates/starter/NOTICE.md +3 -3
  278. package/templates/starter/docs/toolcraft/README.md +5 -5
  279. package/templates/starter/docs/toolcraft/acceptance-testing.md +36 -8
  280. package/templates/starter/docs/toolcraft/assembly-workflow.md +12 -15
  281. package/templates/starter/docs/toolcraft/component-rules.md +6 -2
  282. package/templates/starter/docs/toolcraft/core/layout.md +1 -0
  283. package/templates/starter/docs/toolcraft/core/performance.md +7 -4
  284. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +11 -4
  285. package/templates/starter/docs/toolcraft/core/timeline-animation.md +1 -0
  286. package/templates/starter/docs/toolcraft/custom-controls.md +8 -1
  287. package/templates/starter/docs/toolcraft/decision-contract.md +26 -22
  288. package/templates/starter/docs/toolcraft/performance.md +42 -16
  289. package/templates/starter/docs/toolcraft/schema-reference.md +34 -23
  290. package/templates/starter/docs/toolcraft/workflow.md +25 -17
  291. package/templates/starter/e2e/app-browser-acceptance-evidence.spec.ts +111 -0
  292. package/templates/starter/e2e/app-browser-control-coverage.spec.ts +118 -0
  293. package/templates/starter/e2e/app-browser-performance-evidence.spec.ts +217 -0
  294. package/templates/starter/e2e/app-browser-performance-probe.spec.ts +322 -0
  295. package/templates/starter/e2e/app-browser-proof-session.spec.ts +196 -0
  296. package/templates/starter/e2e/app-browser-runtime-evidence.spec.ts +9 -0
  297. package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +240 -0
  298. package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +180 -0
  299. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +310 -0
  300. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +432 -0
  301. package/templates/starter/e2e/app-browser-shell.spec.ts +171 -0
  302. package/templates/starter/e2e/app-browser-timeline-layers-reference.spec.ts +70 -0
  303. package/templates/starter/e2e/app-performance.spec.ts +0 -491
  304. package/templates/starter/e2e/browser-acceptance-outcome-helpers.ts +97 -0
  305. package/templates/starter/e2e/browser-acceptance-transition-helpers.ts +74 -0
  306. package/templates/starter/e2e/browser-conditional-output-evidence-helpers.ts +230 -0
  307. package/templates/starter/e2e/browser-control-target-helpers.ts +109 -0
  308. package/templates/starter/e2e/browser-layer-evidence-helpers.ts +229 -0
  309. package/templates/starter/e2e/browser-proof-session.ts +392 -0
  310. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +136 -0
  311. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +290 -0
  312. package/templates/starter/e2e/browser-runtime-evidence.ts +27 -0
  313. package/templates/starter/e2e/browser-state-evidence-helpers.ts +205 -0
  314. package/templates/starter/e2e/browser-timeline-evidence-helpers.ts +319 -0
  315. package/templates/starter/e2e/canvas-handle-helpers.ts +86 -4
  316. package/templates/starter/e2e/export-artifact-helpers.ts +132 -0
  317. package/templates/starter/e2e/performance-budget-helpers.ts +159 -0
  318. package/templates/starter/e2e/performance-canvas-helpers.ts +123 -0
  319. package/templates/starter/e2e/performance-control-helpers.ts +3 -0
  320. package/templates/starter/e2e/performance-control-layout-helpers.ts +161 -0
  321. package/templates/starter/e2e/performance-fixture-helpers.ts +217 -0
  322. package/templates/starter/e2e/performance-frame-probe.ts +247 -0
  323. package/templates/starter/e2e/performance-helpers.ts +6 -854
  324. package/templates/starter/e2e/performance-interaction-measurement.ts +251 -0
  325. package/templates/starter/e2e/performance-measurement-evidence.ts +72 -0
  326. package/templates/starter/e2e/performance-output-action-helpers.spec.ts +43 -0
  327. package/templates/starter/e2e/performance-output-action-helpers.ts +109 -0
  328. package/templates/starter/e2e/performance-probe-helpers.ts +12 -0
  329. package/templates/starter/e2e/performance-render-scale-helpers.ts +55 -0
  330. package/templates/starter/e2e/performance-slider-helpers.ts +271 -0
  331. package/templates/starter/e2e/product-observable-helpers.ts +72 -17
  332. package/templates/starter/e2e/stable-outcome-helpers.ts +208 -0
  333. package/templates/starter/package.json +10 -5
  334. package/templates/starter/playwright.config.ts +5 -2
  335. package/templates/starter/scripts/check-ai-skills.mjs +14 -8
  336. package/templates/starter/scripts/check-ai-skills.test.mjs +64 -0
  337. package/templates/starter/scripts/check-toolcraft-code-health.mjs +108 -0
  338. package/templates/starter/scripts/check-toolcraft-code-health.test.mjs +213 -0
  339. package/templates/starter/scripts/check-toolcraft-docs.mjs +47 -34
  340. package/templates/starter/scripts/check-toolcraft-integrity.mjs +141 -144
  341. package/templates/starter/scripts/run-browser-performance.mjs +118 -0
  342. package/templates/starter/scripts/run-browser-performance.test.mjs +53 -0
  343. package/templates/starter/scripts/toolcraft-code-health-core.mjs +297 -0
  344. package/templates/starter/scripts/toolcraft-contract-doc-integrity.mjs +77 -0
  345. package/templates/starter/scripts/toolcraft-contract-doc-integrity.test.mjs +81 -0
  346. package/templates/starter/scripts/toolcraft-contract-manifest.json +113 -0
  347. package/templates/starter/scripts/toolcraft-contract-manifest.mjs +128 -0
  348. package/templates/starter/scripts/toolcraft-contract-manifest.test.mjs +72 -0
  349. package/templates/starter/scripts/toolcraft-integrity-manifest.mjs +172 -0
  350. package/templates/starter/scripts/toolcraft-integrity-policy.mjs +60 -0
  351. package/templates/starter/scripts/toolcraft-product-boundary-ast-utils.mjs +23 -0
  352. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +264 -0
  353. package/templates/starter/scripts/toolcraft-product-boundary-test-fixtures.mjs +16 -0
  354. package/templates/starter/scripts/toolcraft-product-boundary.mjs +243 -0
  355. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +289 -0
  356. package/templates/starter/scripts/toolcraft-product-dependency-graph.mjs +176 -0
  357. package/templates/starter/scripts/toolcraft-product-dependency-graph.test.mjs +196 -0
  358. package/templates/starter/scripts/toolcraft-product-dependency-resolution.mjs +279 -0
  359. package/templates/starter/scripts/toolcraft-product-evidence-boundary-ast.mjs +91 -0
  360. package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +138 -0
  361. package/templates/starter/scripts/toolcraft-product-style-boundary.mjs +219 -0
  362. package/templates/starter/scripts/toolcraft-source-inventory.d.mts +46 -0
  363. package/templates/starter/scripts/toolcraft-source-inventory.mjs +187 -0
  364. package/templates/starter/scripts/toolcraft-source-inventory.test.mjs +132 -0
  365. package/templates/starter/scripts/toolcraft-source-ownership.mjs +167 -0
  366. package/templates/starter/scripts/toolcraft-source-ownership.test.mjs +113 -0
  367. package/templates/starter/scripts/toolcraft-static-string.mjs +104 -0
  368. package/templates/starter/scripts/toolcraft-verification-inventory.mjs +123 -0
  369. package/templates/starter/scripts/toolcraft-verification-receipt.d.mts +19 -0
  370. package/templates/starter/scripts/toolcraft-verification-receipt.mjs +284 -0
  371. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +282 -0
  372. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.d.mts +32 -0
  373. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.mjs +88 -0
  374. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.test.mjs +87 -0
  375. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.mjs +109 -0
  376. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.test.mjs +111 -0
  377. package/templates/starter/scripts/toolcraft-workflow-routes.mjs +314 -0
  378. package/templates/starter/scripts/toolcraft-workflow-routes.test.mjs +204 -0
  379. package/templates/starter/src/app/acceptance/actions.ts +30 -0
  380. package/templates/starter/src/app/acceptance/animation-intent.ts +184 -0
  381. package/templates/starter/src/app/acceptance/canvas-handle-acceptance.ts +70 -0
  382. package/templates/starter/src/app/acceptance/color-bank-labels.ts +164 -0
  383. package/templates/starter/src/app/acceptance/conditions.ts +80 -0
  384. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +70 -0
  385. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +271 -0
  386. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +149 -0
  387. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +189 -0
  388. package/templates/starter/src/app/acceptance/control-acceptance.ts +70 -0
  389. package/templates/starter/src/app/acceptance/control-component-rules.ts +277 -0
  390. package/templates/starter/src/app/acceptance/control-labels.ts +223 -0
  391. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +107 -0
  392. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +104 -0
  393. package/templates/starter/src/app/acceptance/control-layout-model.ts +159 -0
  394. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +164 -0
  395. package/templates/starter/src/app/acceptance/control-layout.ts +45 -0
  396. package/templates/starter/src/app/acceptance/control-order.ts +123 -0
  397. package/templates/starter/src/app/acceptance/control-parts.ts +47 -0
  398. package/templates/starter/src/app/acceptance/control-section-inventory.ts +183 -0
  399. package/templates/starter/src/app/acceptance/controls.ts +27 -0
  400. package/templates/starter/src/app/acceptance/coverage.ts +81 -0
  401. package/templates/starter/src/app/acceptance/custom-controls.ts +140 -0
  402. package/templates/starter/src/app/acceptance/inline-layout.ts +192 -0
  403. package/templates/starter/src/app/acceptance/media-upload.ts +55 -0
  404. package/templates/starter/src/app/acceptance/output-background-rules.ts +135 -0
  405. package/templates/starter/src/app/acceptance/output-export-actions.test.ts +76 -0
  406. package/templates/starter/src/app/acceptance/output-export-actions.ts +34 -0
  407. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +46 -0
  408. package/templates/starter/src/app/acceptance/output-export-model.ts +146 -0
  409. package/templates/starter/src/app/acceptance/output-export.ts +45 -0
  410. package/templates/starter/src/app/acceptance/output-image-export-rules.ts +87 -0
  411. package/templates/starter/src/app/acceptance/reference-runtime-coverage.ts +96 -0
  412. package/templates/starter/src/app/acceptance/reference-runtime-feature-inventory.ts +171 -0
  413. package/templates/starter/src/app/acceptance/reference-runtime-study.ts +73 -0
  414. package/templates/starter/src/app/acceptance/reference-runtime-timeline.ts +291 -0
  415. package/templates/starter/src/app/acceptance/reference-runtime.ts +99 -0
  416. package/templates/starter/src/app/acceptance/runtime-coverage.ts +290 -0
  417. package/templates/starter/src/app/acceptance/runtime-setup.ts +117 -0
  418. package/templates/starter/src/app/acceptance/section-title-rules.ts +8 -0
  419. package/templates/starter/src/app/acceptance/sections.ts +6 -0
  420. package/templates/starter/src/app/acceptance/semantic.ts +63 -0
  421. package/templates/starter/src/app/acceptance/timeline-loop.ts +21 -0
  422. package/templates/starter/src/app/acceptance/types.ts +363 -0
  423. package/templates/starter/src/app/acceptance/validate-coverage.ts +217 -0
  424. package/templates/starter/src/app/acceptance/validation-pipeline.test.ts +56 -0
  425. package/templates/starter/src/app/acceptance/validation-pipeline.ts +60 -0
  426. package/templates/starter/src/app/acceptance/video-reference-study.ts +217 -0
  427. package/templates/starter/src/app/starter-acceptance-data.ts +21 -0
  428. package/templates/starter/src/app/starter-acceptance.animation-intent.test.ts +383 -0
  429. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +349 -0
  430. package/templates/starter/src/app/starter-acceptance.base-coverage.test.ts +47 -0
  431. package/templates/starter/src/app/starter-acceptance.canvas-handles.test.ts +71 -0
  432. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +199 -0
  433. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +244 -0
  434. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +63 -0
  435. package/templates/starter/src/app/starter-acceptance.control-label-context.test.ts +172 -0
  436. package/templates/starter/src/app/starter-acceptance.control-labels.test.ts +387 -0
  437. package/templates/starter/src/app/starter-acceptance.control-naming-rules.test.ts +215 -0
  438. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +274 -0
  439. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +275 -0
  440. package/templates/starter/src/app/starter-acceptance.curves-rules.test.ts +184 -0
  441. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +200 -0
  442. package/templates/starter/src/app/starter-acceptance.custom-control-coverage.test.ts +111 -0
  443. package/templates/starter/src/app/starter-acceptance.custom-control-fit-check.test.ts +135 -0
  444. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +74 -0
  445. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +107 -0
  446. package/templates/starter/src/app/starter-acceptance.framework-boundary.test.ts +327 -0
  447. package/templates/starter/src/app/starter-acceptance.image-video-export-settings.test.ts +328 -0
  448. package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +242 -0
  449. package/templates/starter/src/app/starter-acceptance.layers.test.ts +203 -0
  450. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +110 -0
  451. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +153 -0
  452. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +97 -0
  453. package/templates/starter/src/app/starter-acceptance.persistence-coverage.test.ts +134 -0
  454. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +202 -0
  455. package/templates/starter/src/app/starter-acceptance.product-readiness-rules.test.ts +14 -0
  456. package/templates/starter/src/app/starter-acceptance.product-readiness-test-utils.ts +25 -0
  457. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +50 -0
  458. package/templates/starter/src/app/starter-acceptance.product-worklog.test.ts +27 -0
  459. package/templates/starter/src/app/starter-acceptance.range-slider-rules.test.ts +147 -0
  460. package/templates/starter/src/app/starter-acceptance.reference-custom-timeline.test.ts +197 -0
  461. package/templates/starter/src/app/starter-acceptance.reference-foundation.test.ts +304 -0
  462. package/templates/starter/src/app/starter-acceptance.reference-test-utils.ts +102 -0
  463. package/templates/starter/src/app/starter-acceptance.reference-timeline.test.ts +267 -0
  464. package/templates/starter/src/app/starter-acceptance.schema-test-utils.ts +116 -0
  465. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +373 -0
  466. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +260 -0
  467. package/templates/starter/src/app/starter-acceptance.section-titles.test.ts +180 -0
  468. package/templates/starter/src/app/starter-acceptance.setup-readiness.test.ts +205 -0
  469. package/templates/starter/src/app/starter-acceptance.slider-rules.test.ts +470 -0
  470. package/templates/starter/src/app/starter-acceptance.slider-unit-rules.test.ts +61 -0
  471. package/templates/starter/src/app/starter-acceptance.source-test-utils.ts +43 -0
  472. package/templates/starter/src/app/starter-acceptance.test-utils.ts +21 -0
  473. package/templates/starter/src/app/starter-acceptance.text-control-kind.test.ts +89 -0
  474. package/templates/starter/src/app/starter-acceptance.timeline-playback.test.ts +264 -0
  475. package/templates/starter/src/app/starter-acceptance.timeline-test-utils.ts +56 -0
  476. package/templates/starter/src/app/starter-acceptance.toggle-pair-layout.test.ts +200 -0
  477. package/templates/starter/src/app/starter-acceptance.toggle-parameter-layout.test.ts +277 -0
  478. package/templates/starter/src/app/starter-acceptance.ts +151 -4576
  479. package/templates/starter/src/app/starter-acceptance.video-export-test-utils.ts +37 -0
  480. package/templates/starter/src/app/starter-acceptance.video-reference-study.test.ts +262 -0
  481. package/templates/starter/src/app/starter-acceptance.worklog-test-utils.ts +428 -0
  482. package/templates/starter/src/app/starter-acceptance.worklog.test.ts +283 -0
  483. package/templates/starter/src/app/starter-automated-runtime-evidence.test.ts +34 -0
  484. package/templates/starter/src/app/starter-composition.tsx +7 -0
  485. package/templates/starter/src/app/starter-performance-test-utils.ts +275 -0
  486. package/templates/starter/src/app/starter-performance.budgets.test.ts +374 -0
  487. package/templates/starter/src/app/starter-performance.gates.test.ts +128 -0
  488. package/templates/starter/src/app/starter-performance.renderer-source.test.ts +136 -0
  489. package/templates/starter/src/app/starter-performance.renderer-technique.test.ts +291 -0
  490. package/templates/starter/src/app/starter-performance.scenarios.test.ts +355 -0
  491. package/templates/starter/src/app/test-evidence/browser-runtime-contract.test.ts +253 -0
  492. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +243 -0
  493. package/templates/starter/src/routes/index.tsx +11 -2
  494. package/templates/starter/vite.config.ts +8 -0
  495. package/templates/ui/components/controls/color/color-control-types.ts +38 -0
  496. package/templates/ui/components/controls/color/color-control.tsx +19 -437
  497. package/templates/ui/components/controls/color/color-footer.tsx +272 -0
  498. package/templates/ui/components/controls/color/color-model-slider.tsx +271 -0
  499. package/templates/ui/components/controls/color/color-opacity-input.tsx +94 -0
  500. package/templates/ui/components/controls/color/color-surface.tsx +194 -0
  501. package/templates/ui/components/controls/color/color-value-control.tsx +159 -0
  502. package/templates/ui/components/controls/color/color-value-utils.ts +170 -0
  503. package/templates/ui/components/controls/color/index.ts +10 -3
  504. package/templates/ui/components/controls/color/palette-control-controller.ts +407 -0
  505. package/templates/ui/components/controls/color/palette-control-layout.ts +14 -0
  506. package/templates/ui/components/controls/color/palette-control-types.ts +50 -0
  507. package/templates/ui/components/controls/color/palette-control-view.tsx +174 -0
  508. package/templates/ui/components/controls/color/palette-control.tsx +6 -544
  509. package/templates/ui/components/controls/color/style-guide-color-picker-color-utils.ts +39 -0
  510. package/templates/ui/components/controls/color/style-guide-color-picker-controller.ts +370 -0
  511. package/templates/ui/components/controls/color/style-guide-color-picker-hue-handlers.ts +66 -0
  512. package/templates/ui/components/controls/color/style-guide-color-picker-interaction-state.ts +49 -0
  513. package/templates/ui/components/controls/color/style-guide-color-picker-interactions.ts +2 -2
  514. package/templates/ui/components/controls/color/style-guide-color-picker-model.ts +125 -0
  515. package/templates/ui/components/controls/color/style-guide-color-picker-parts.tsx +10 -707
  516. package/templates/ui/components/controls/color/style-guide-color-picker-surface-drag.ts +93 -0
  517. package/templates/ui/components/controls/color/style-guide-color-picker-surface-geometry.ts +114 -0
  518. package/templates/ui/components/controls/color/style-guide-color-picker-surface-preview.ts +45 -0
  519. package/templates/ui/components/controls/color/style-guide-color-picker-types.ts +84 -0
  520. package/templates/ui/components/controls/color/style-guide-color-picker-view.tsx +69 -0
  521. package/templates/ui/components/controls/color/style-guide-color-picker.tsx +6 -495
  522. package/templates/ui/components/controls/curves/curves-control.tsx +4 -3
  523. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +39 -515
  524. package/templates/ui/components/controls/file-drop/file-drop-empty-state.tsx +35 -0
  525. package/templates/ui/components/controls/file-drop/file-drop-file-list.tsx +160 -0
  526. package/templates/ui/components/controls/file-drop/file-drop-image-grid.tsx +175 -0
  527. package/templates/ui/components/controls/file-drop/file-drop-model.tsx +170 -0
  528. package/templates/ui/components/controls/file-drop/file-drop-single-preview.tsx +63 -0
  529. package/templates/ui/components/controls/file-drop/file-drop-types.ts +48 -0
  530. package/templates/ui/components/controls/file-drop/index.ts +2 -2
  531. package/templates/ui/components/controls/font-picker/font-picker-control.tsx +133 -906
  532. package/templates/ui/components/controls/font-picker/font-picker-footer.tsx +116 -0
  533. package/templates/ui/components/controls/font-picker/font-picker-list.tsx +194 -0
  534. package/templates/ui/components/controls/font-picker/font-picker-popover-content.tsx +191 -0
  535. package/templates/ui/components/controls/font-picker/font-picker-value.ts +222 -0
  536. package/templates/ui/components/controls/font-picker/index.ts +4 -1
  537. package/templates/ui/components/controls/font-picker/use-font-picker-preview-pipeline.ts +163 -0
  538. package/templates/ui/components/controls/font-picker/use-font-picker-virtual-list.ts +293 -0
  539. package/templates/ui/components/controls/gradient/gradient-control-utils.ts +2 -25
  540. package/templates/ui/components/controls/gradient/gradient-control.tsx +4 -464
  541. package/templates/ui/components/controls/gradient/gradient-stops-controller.ts +332 -0
  542. package/templates/ui/components/controls/gradient/gradient-stops-track.tsx +159 -0
  543. package/templates/ui/components/controls/vector/vector-control-types.ts +26 -0
  544. package/templates/ui/components/controls/vector/vector-control.tsx +10 -486
  545. package/templates/ui/components/controls/vector/vector-pad-field.tsx +211 -0
  546. package/templates/ui/components/controls/vector/vector-pad-geometry.ts +61 -0
  547. package/templates/ui/components/controls/vector/vector-pad-parts.tsx +72 -0
  548. package/templates/ui/components/controls/vector/vector-pad-variants.ts +20 -0
  549. package/templates/ui/components/controls/vector/vector-size-field.tsx +104 -0
  550. package/templates/ui/components/controls/vector/vector-value.ts +46 -0
  551. package/templates/ui/components/primitives/slider/slider-discrete-state.ts +181 -0
  552. package/templates/ui/components/primitives/slider/slider-marker-policy.ts +52 -0
  553. package/templates/ui/components/primitives/slider/slider-pointer-dragging.ts +87 -0
  554. package/templates/ui/components/primitives/slider/slider-runtime.ts +109 -0
  555. package/templates/ui/components/primitives/slider/slider-types.ts +21 -0
  556. package/templates/ui/components/primitives/slider/slider.tsx +2 -403
  557. package/templates/runtime/react/canvas-shell.test.tsx +0 -554
  558. package/templates/runtime/react/canvas-shell.tsx +0 -585
  559. package/templates/runtime/react/controls-panel.test.tsx +0 -5446
  560. package/templates/runtime/react/controls-panel.tsx +0 -3220
  561. package/templates/runtime/react/layers-panel.test.tsx +0 -487
  562. package/templates/runtime/react/layers-panel.tsx +0 -1348
  563. package/templates/runtime/react/timeline-panel.test.tsx +0 -751
  564. package/templates/runtime/react/timeline-panel.tsx +0 -3078
  565. package/templates/runtime/schema/define-toolcraft.test.ts +0 -1742
  566. package/templates/runtime/state/reducer.test.ts +0 -1703
  567. package/templates/runtime/testing/performance.test.ts +0 -3081
  568. package/templates/starter/e2e/app-browser-acceptance.spec.ts +0 -785
  569. package/templates/starter/src/app/starter-acceptance.test.ts +0 -10068
  570. package/templates/starter/src/app/starter-performance.test.ts +0 -1583
  571. package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +0 -490
  572. /package/templates/runtime/react/{storage-key-migration.ts → app-shell/storage-key-migration.ts} +0 -0
  573. /package/templates/runtime/react/{theme-runtime.tsx → app-shell/theme-runtime.tsx} +0 -0
  574. /package/templates/runtime/react/{curve-geometry.test.ts → controls-panel/__tests__/curve-geometry.test.ts} +0 -0
  575. /package/templates/runtime/react/{panel-host-config.ts → panel-host/panel-host-config.ts} +0 -0
  576. /package/templates/runtime/react/{panel-host-geometry.test.ts → panel-host/panel-host-geometry.test.ts} +0 -0
  577. /package/templates/runtime/react/{panel-host-geometry.ts → panel-host/panel-host-geometry.ts} +0 -0
@@ -1,1583 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync } from "node:fs";
2
- import { basename, dirname, join } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- import { describe, expect, it } from "vitest";
6
- import {
7
- collectToolcraftPerformanceSensitiveControls,
8
- collectToolcraftUnclassifiedPerformanceControls,
9
- defineToolcraft,
10
- validateToolcraftPerformanceCoverage,
11
- } from "@repo/toolcraft-runtime";
12
-
13
- import { starterPerformance } from "./starter-performance";
14
- import { starterSchema } from "./starter-schema";
15
-
16
- const currentFileName = basename(fileURLToPath(import.meta.url));
17
- const appDir = dirname(fileURLToPath(import.meta.url));
18
- const srcDir = join(appDir, "..");
19
- const routesDir = join(appDir, "../routes");
20
- const e2eDir = join(appDir, "../../e2e");
21
- const projectDir = join(appDir, "../..");
22
-
23
- function stripJsComments(source: string): string {
24
- return source
25
- .replace(/\/\*[\s\S]*?\*\//g, "")
26
- .replace(/(^|[^:])\/\/.*$/gm, "$1");
27
- }
28
-
29
- function readFiles(rootDir: string, matcher: RegExp): string {
30
- const chunks: string[] = [];
31
-
32
- function visit(currentDir: string) {
33
- for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
34
- const filePath = join(currentDir, entry.name);
35
-
36
- if (entry.isDirectory()) {
37
- if (!["dist", "node_modules", "toolcraft"].includes(entry.name)) {
38
- visit(filePath);
39
- }
40
- continue;
41
- }
42
-
43
- if (
44
- entry.isFile() &&
45
- matcher.test(entry.name) &&
46
- !/\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name) &&
47
- !/^(?:starter-|app-)(?:acceptance|performance)\.ts$/.test(entry.name)
48
- ) {
49
- chunks.push(readFileSync(filePath, "utf8"));
50
- }
51
- }
52
- }
53
-
54
- visit(rootDir);
55
- return chunks.join("\n");
56
- }
57
-
58
- function readSiblingAppTestSources(): string {
59
- return readdirSync(appDir)
60
- .filter((fileName) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(fileName))
61
- .filter((fileName) => fileName !== currentFileName)
62
- .map((fileName) => readFileSync(join(appDir, fileName), "utf8"))
63
- .map(stripJsComments)
64
- .join("\n");
65
- }
66
-
67
- function readBrowserTestSources(): string {
68
- return readdirSync(e2eDir)
69
- .filter((fileName) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(fileName))
70
- .map((fileName) => readFileSync(join(e2eDir, fileName), "utf8"))
71
- .map(stripJsComments)
72
- .join("\n");
73
- }
74
-
75
- function readMarkdownFiles(rootDir: string): string {
76
- if (!existsSync(rootDir)) {
77
- return "";
78
- }
79
-
80
- const chunks: string[] = [];
81
-
82
- function visit(currentDir: string) {
83
- for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
84
- const filePath = join(currentDir, entry.name);
85
-
86
- if (entry.isDirectory()) {
87
- if (!["dist", "node_modules", "toolcraft"].includes(entry.name)) {
88
- visit(filePath);
89
- }
90
- continue;
91
- }
92
-
93
- if (entry.isFile() && /\.mdx?$/i.test(entry.name)) {
94
- chunks.push(readFileSync(filePath, "utf8"));
95
- }
96
- }
97
- }
98
-
99
- visit(rootDir);
100
- return chunks.join("\n");
101
- }
102
-
103
- function readProjectDecisionSources(): string {
104
- return stripJsComments(
105
- [
106
- readMarkdownFiles(join(projectDir, "docs")),
107
- readMarkdownFiles(join(projectDir, "specs")),
108
- readMarkdownFiles(join(projectDir, "plans")),
109
- ].join("\n"),
110
- );
111
- }
112
-
113
- function projectDocsIncludeRendererTechniqueDecision(): boolean {
114
- const decisionSources = readProjectDecisionSources();
115
-
116
- return [
117
- /Renderer Technique Decision Matrix/i,
118
- /sourceRepresentation/,
119
- /productRepresentation/,
120
- /previewRenderer/,
121
- /exportRenderer/,
122
- /rendererWorkload/,
123
- /rendererStrategy/,
124
- /whyNotAlternativeStrategies/,
125
- /fidelityRisks/,
126
- /performanceRisks/,
127
- ].every((pattern) => pattern.test(decisionSources));
128
- }
129
-
130
- function projectDocsIncludeRendererLayerInventory(): boolean {
131
- const decisionSources = readProjectDecisionSources();
132
-
133
- return (
134
- /Renderer Layer Inventory|rendererTechnique\.layers|layer inventory/i.test(decisionSources) &&
135
- /backgroundLayer|productForegroundLayer|editingHandlesLayer|exportComposite|product-foreground/i.test(
136
- decisionSources,
137
- )
138
- );
139
- }
140
-
141
- function projectDocsIncludeRendererPipelineInventory(): boolean {
142
- const decisionSources = readProjectDecisionSources();
143
-
144
- return (
145
- /Render Pipeline Inventory|rendererPipeline|render pipeline/i.test(decisionSources) &&
146
- /pass|passes|cacheKey|cache key|invalidat/i.test(decisionSources) &&
147
- /control-drag|viewport-zoom|viewport-drag|media-import|animation-frame|timeline-playback|interaction/i.test(
148
- decisionSources,
149
- )
150
- );
151
- }
152
-
153
- function projectDocsExplainRendererAlternatives(): boolean {
154
- const decisionSources = readProjectDecisionSources();
155
-
156
- return (
157
- /whyNotAlternativeStrategies/.test(decisionSources) &&
158
- /alternative|strategy|renderer/i.test(decisionSources) &&
159
- /text-output|vector-output|pixel-output|rendererWorkload/.test(decisionSources) &&
160
- /exportRenderer|export\/copy|product-quality/i.test(decisionSources)
161
- );
162
- }
163
-
164
- function sourceUsesCustomRenderer(): boolean {
165
- const routeSources = stripJsComments(readFiles(routesDir, /\.(ts|tsx)$/));
166
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
167
-
168
- return (
169
- /canvasContent\s*=/.test(routeSources) ||
170
- /renderDefaultCanvasMedia=\{false\}/.test(routeSources) ||
171
- /useToolcraft(Value)?\(/.test(appSources) ||
172
- /getContext\(["']2d["']\)|webgl|webgpu|OffscreenCanvas|ImageData/.test(appSources)
173
- );
174
- }
175
-
176
- function sourceUsesHardcodedOutputBackgroundColor(
177
- source = stripJsComments(readFiles(srcDir, /\.(ts|tsx|css)$/)),
178
- ): boolean {
179
- const canvasFillPattern =
180
- /(?:ctx|context|canvasContext)\.fillStyle\s*=\s*["']#[0-9a-fA-F]{3,8}["'][\s\S]{0,240}\.fillRect\s*\(/;
181
- const outputCssBackgroundPattern =
182
- /\.(?:[a-z0-9_-]*(?:canvas|renderer|preview|output|product)[a-z0-9_-]*)\s*{[^}]*background(?:-color)?\s*:\s*#[0-9a-fA-F]{3,8}/i;
183
-
184
- return canvasFillPattern.test(source) || outputCssBackgroundPattern.test(source);
185
- }
186
-
187
- function schemaHasOutputBackgroundColorControl(): boolean {
188
- return (starterSchema.panels.controls?.sections ?? []).some((section) =>
189
- Object.values(section.controls).some((control) => {
190
- if (control.type !== "color") {
191
- return false;
192
- }
193
-
194
- const searchText = [
195
- section.title,
196
- typeof control.label === "string" ? control.label : "",
197
- control.target,
198
- ].join(" ");
199
-
200
- return /\b(background|backdrop|scene|canvas)\b/i.test(searchText);
201
- }),
202
- );
203
- }
204
-
205
- function projectDocsIncludeFixedBackgroundDecision(): boolean {
206
- return /fixedBackgroundReason|fixed background|non-editable background|not user-editable background|reference-defined background|product-defined background/i.test(
207
- readProjectDecisionSources(),
208
- );
209
- }
210
-
211
- function sourceUsesCpuPixelLoop(): boolean {
212
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
213
- const cpuPixelMethodCallPattern =
214
- /(?:\.(?:createImageData|getImageData|putImageData)|\[\s*["'](?:createImageData|getImageData|putImageData)["']\s*\])\s*\(/;
215
-
216
- return (
217
- /new\s+ImageData\s*\(/.test(appSources) ||
218
- cpuPixelMethodCallPattern.test(appSources)
219
- );
220
- }
221
-
222
- function appPerformanceHasRenderPipelinePass(kind: string): boolean {
223
- return (starterPerformance.rendererPipeline?.passes ?? []).some(
224
- (pass) => pass.kind === kind,
225
- );
226
- }
227
-
228
- function appPerformanceHasInteractionInvalidation(interaction: string): boolean {
229
- return (starterPerformance.rendererPipeline?.interactionInvalidation ?? []).some(
230
- (entry) => entry.interaction === interaction,
231
- );
232
- }
233
-
234
- function sourceUsesGpuRenderer(): boolean {
235
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
236
-
237
- return /getContext\(["']webgl2?["']\)|navigator\.gpu|GPUCanvasContext/.test(appSources);
238
- }
239
-
240
- function sourceUsesWebGlLifecycleGuard(): boolean {
241
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
242
-
243
- return (
244
- /useEffect\s*\(/.test(appSources) ||
245
- /useLayoutEffect\s*\(/.test(appSources) ||
246
- /useMemo\s*\(/.test(appSources) ||
247
- /useRef\s*\(/.test(appSources) ||
248
- /class\s+\w+Renderer/.test(appSources)
249
- );
250
- }
251
-
252
- function sourceCreatesWebGlContextInComponentRender(): boolean {
253
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
254
- const componentRenderPattern =
255
- /function\s+[A-Z]\w*\s*\([^)]*\)\s*{(?![\s\S]{0,600}use(?:Layout)?Effect\s*\()[\s\S]{0,600}\.getContext\(["']webgl2?["']\)/;
256
-
257
- return componentRenderPattern.test(appSources);
258
- }
259
-
260
- function sourceMayUploadTextureFromTimelineDrivenEffect(): boolean {
261
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
262
- const timelineDrivenTextureUploadPattern =
263
- /use(?:Layout)?Effect\s*\(\s*\(\)\s*=>\s*{[\s\S]*?(?:texImage2D\s*\(|\.setImage\s*\()[\s\S]*?}\s*,\s*\[[\s\S]*?(?:settings|state\.timeline|currentTimeSeconds|keyframeGroups)[\s\S]*?\]\s*\)/;
264
-
265
- return timelineDrivenTextureUploadPattern.test(appSources);
266
- }
267
-
268
- function sourceResyncsTimelineDurationFromRuntimeDuration(): boolean {
269
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
270
- const durationResyncPattern =
271
- /use(?:Layout)?Effect\s*\(\s*\(\)\s*=>\s*{[\s\S]*?timeline\.setDuration[\s\S]*?}\s*,\s*\[[\s\S]*state\.timeline\.durationSeconds[\s\S]*\]\s*\)/;
272
-
273
- return durationResyncPattern.test(appSources);
274
- }
275
-
276
- function sourceUsesLowResolutionPreviewUpscale(source = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/))): boolean {
277
- const lowResolutionPreviewPattern =
278
- /maxPreviewPixels|previewPixelBudget|previewScale|previewRatio|lowRes|lowResolution|downsample/i;
279
- const scaledDrawImagePattern =
280
- /\.drawImage\s*\([\s\S]{0,240}(?:outputWidth|outputHeight|state\.canvas\.size|canvas\.width|canvas\.height)[\s\S]{0,240}\)/;
281
-
282
- return lowResolutionPreviewPattern.test(source) || scaledDrawImagePattern.test(source);
283
- }
284
-
285
- function browserTestsAssertNativePreviewResolution(): boolean {
286
- const browserTestSources = readBrowserTestSources();
287
-
288
- return (
289
- /previewWidth|previewHeight|clientWidth|clientHeight|getBoundingClientRect/.test(
290
- browserTestSources,
291
- ) &&
292
- /outputWidth|outputHeight|state\.canvas\.size|canvas\.size|toHaveAttribute/.test(
293
- browserTestSources,
294
- )
295
- );
296
- }
297
-
298
- function browserPerfContractRequiresRenderScaleBackingPixels(): boolean {
299
- const browserTestSources = readBrowserTestSources();
300
-
301
- return (
302
- /scenarioUsesRenderScaleFixture/.test(browserTestSources) &&
303
- /expectToolcraftCanvasBackingPixelsForRenderScale/.test(browserTestSources)
304
- );
305
- }
306
-
307
- function sourceUsesAnimationFrameWithoutCleanup(): boolean {
308
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
309
-
310
- return /requestAnimationFrame\s*\(/.test(appSources) && !/cancelAnimationFrame\s*\(/.test(appSources);
311
- }
312
-
313
- function sourceUsesDirectStorageApi(): boolean {
314
- const appSources = stripJsComments(readFiles(srcDir, /\.(ts|tsx)$/));
315
-
316
- return /\b(?:localStorage|sessionStorage)\s*\./.test(appSources);
317
- }
318
-
319
- describe("Toolcraft template app performance coverage", () => {
320
- it("publishes separate browser acceptance and performance fallback gates", () => {
321
- const packageJson = JSON.parse(readFileSync(join(projectDir, "package.json"), "utf8")) as {
322
- scripts?: Record<string, string>;
323
- };
324
- const runScriptPattern = (scriptName: string) =>
325
- `(?:pnpm ${scriptName}|npm run ${scriptName})`;
326
-
327
- expect(
328
- packageJson.scripts?.["test:browser"],
329
- "Generated apps must keep full performance scenarios out of the default browser acceptance gate.",
330
- ).toBe('playwright install chromium && playwright test --grep-invert "browser perf:"');
331
- expect(
332
- packageJson.scripts?.["test:browser:perf"],
333
- "Generated apps must expose a sequential Playwright fallback so perf budgets can be measured when no agent browser is available.",
334
- ).toBe(
335
- 'playwright install chromium && playwright test --grep "browser perf:" --workers=1 --pass-with-no-tests',
336
- );
337
- expect(packageJson.scripts?.["verify:quick"]).toMatch(
338
- new RegExp(`^${runScriptPattern("ai:check")} && ${runScriptPattern("test")}$`),
339
- );
340
- expect(packageJson.scripts?.["verify:ui"]).toMatch(
341
- new RegExp(`^${runScriptPattern("test:browser")}$`),
342
- );
343
- expect(packageJson.scripts?.["verify:perf"]).toMatch(
344
- new RegExp(`^${runScriptPattern("test:browser:perf")}$`),
345
- );
346
- expect(packageJson.scripts?.["verify:perf:playwright"]).toMatch(
347
- new RegExp(`^${runScriptPattern("test:browser:perf")}$`),
348
- );
349
- expect(packageJson.scripts?.["verify:final"]).toMatch(
350
- new RegExp(
351
- `^${runScriptPattern("ai:check")} && ${runScriptPattern("test")} && ${runScriptPattern("build")} && ${runScriptPattern("test:browser")}$`,
352
- ),
353
- );
354
- });
355
-
356
- it("keeps the Playwright performance audit inside the browser perf tag", () => {
357
- const source = readFileSync(join(e2eDir, "app-performance.spec.ts"), "utf8");
358
- const testNames = [...source.matchAll(/test\(\s*(["'`])([^"'`]+)\1/g)].map(
359
- (match) => match[2],
360
- );
361
-
362
- expect(source).not.toContain("TOOLCRAFT_PERF_CHECK");
363
- expect(testNames.length).toBeGreaterThan(0);
364
- expect(
365
- testNames.every((name) => name.includes("browser perf:")),
366
- `app-performance.spec.ts tests must all be tagged for the dedicated perf checkpoint: ${testNames.join(", ")}`,
367
- ).toBe(true);
368
- });
369
-
370
- it("declares agent browser as the preferred performance runner", () => {
371
- expect(starterPerformance.browserCheckPolicy).toEqual({
372
- fallbackRunner: "playwright",
373
- fallbackWhen: ["agent-browser-unavailable", "ci"],
374
- preferredRunner: "agent-browser",
375
- });
376
- });
377
-
378
- it("requires valid performance coverage for declared workload scenarios", () => {
379
- expect(validateToolcraftPerformanceCoverage(starterSchema, starterPerformance)).toEqual([]);
380
- });
381
-
382
- it("requires every visible control to classify its performance role", () => {
383
- const unclassifiedControls =
384
- collectToolcraftUnclassifiedPerformanceControls(starterSchema);
385
-
386
- expect(
387
- unclassifiedControls,
388
- "Every visible non-action control must declare performanceRole as workload or responsiveness so AI cannot skip the performance decision.",
389
- ).toEqual([]);
390
- });
391
-
392
- it("requires generated custom renderers to opt into the performance matrix", () => {
393
- if (sourceUsesCustomRenderer()) {
394
- expect(starterPerformance.usesCustomRenderer).toBe(true);
395
- }
396
- });
397
-
398
- it("requires custom renderer apps to document the renderer technique decision", () => {
399
- if (!starterPerformance.usesCustomRenderer) {
400
- return;
401
- }
402
-
403
- expect(
404
- projectDocsIncludeRendererTechniqueDecision(),
405
- "Custom renderer apps must write a Renderer Technique Decision Matrix in their app spec/plan docs before implementation; AGENTS.md rules alone do not count as the app decision.",
406
- ).toBe(true);
407
- });
408
-
409
- it("requires custom renderer apps to document the renderer layer inventory", () => {
410
- if (!starterPerformance.usesCustomRenderer) {
411
- return;
412
- }
413
-
414
- expect(
415
- projectDocsIncludeRendererLayerInventory(),
416
- "Custom renderer apps must write a Renderer Layer Inventory in their app spec/plan docs so dense raster backgrounds cannot silently rasterize semantic foreground output.",
417
- ).toBe(true);
418
- });
419
-
420
- it("requires custom renderer apps to document the render pipeline inventory", () => {
421
- if (!starterPerformance.usesCustomRenderer) {
422
- return;
423
- }
424
-
425
- expect(
426
- projectDocsIncludeRendererPipelineInventory(),
427
- "Custom renderer apps must write a Render Pipeline Inventory in their app spec/plan docs before implementation so expensive passes, cache keys, and invalidation rules are deliberate.",
428
- ).toBe(true);
429
- });
430
-
431
- it("requires custom renderer apps to mirror the renderer decision in typed performance config", () => {
432
- if (!starterPerformance.usesCustomRenderer) {
433
- return;
434
- }
435
-
436
- expect(
437
- starterPerformance.rendererTechnique,
438
- "Custom renderer apps must declare rendererTechnique in app-performance.ts; prose specs and plans are not enough.",
439
- ).toBeDefined();
440
- });
441
-
442
- it("requires custom renderer apps to mirror the render pipeline in typed performance config", () => {
443
- if (!starterPerformance.usesCustomRenderer) {
444
- return;
445
- }
446
-
447
- expect(
448
- starterPerformance.rendererPipeline,
449
- "Custom renderer apps must declare rendererPipeline in app-performance.ts; prose specs and plans are not enough.",
450
- ).toBeDefined();
451
- });
452
-
453
- it("requires custom renderer apps to mirror layer inventory in typed performance config", () => {
454
- if (!starterPerformance.usesCustomRenderer) {
455
- return;
456
- }
457
-
458
- expect(
459
- starterPerformance.rendererTechnique?.layers?.length ?? 0,
460
- "Custom renderer apps must mirror the Renderer Layer Inventory in app-performance.ts rendererTechnique.layers so browser tests and zoom-stress classification can verify the real visual layers.",
461
- ).toBeGreaterThan(0);
462
- });
463
-
464
- it("requires custom renderer apps to explain rejected renderer alternatives", () => {
465
- if (!starterPerformance.usesCustomRenderer) {
466
- return;
467
- }
468
-
469
- expect(
470
- projectDocsExplainRendererAlternatives(),
471
- "Custom renderer specs must explain why the chosen preview/export renderer fits the product better than alternatives, including reference preservation, workload, and product-quality export behavior.",
472
- ).toBe(true);
473
- });
474
-
475
- it("requires renderer-owned hardcoded backgrounds to be schema-controlled or explicitly fixed", () => {
476
- if (!sourceUsesHardcodedOutputBackgroundColor()) {
477
- return;
478
- }
479
-
480
- expect(
481
- schemaHasOutputBackgroundColorControl() || projectDocsIncludeFixedBackgroundDecision(),
482
- "Renderer-owned output background colors must be schema color controls. If a background is intentionally fixed, document the fixed background reason in the app spec/plan so the missing control is deliberate.",
483
- ).toBe(true);
484
- });
485
-
486
- it("requires custom renderers to declare a renderer strategy", () => {
487
- expect(
488
- validateToolcraftPerformanceCoverage(starterSchema, {
489
- rendererStrategy: "none",
490
- rendererWorkload: "simple-composition",
491
- scenarios: starterPerformance.scenarios,
492
- usesCustomRenderer: true,
493
- workloadTargets: starterPerformance.workloadTargets,
494
- }),
495
- ).toEqual(
496
- expect.arrayContaining([
497
- 'Custom renderers must declare rendererStrategy "dom", "svg", "canvas-2d", "webgl", or "webgpu".',
498
- ]),
499
- );
500
- });
501
-
502
- it("rejects renderer strategies on non-custom apps", () => {
503
- expect(
504
- validateToolcraftPerformanceCoverage(starterSchema, {
505
- rendererStrategy: "canvas-2d",
506
- rendererWorkload: "none",
507
- scenarios: starterPerformance.scenarios,
508
- usesCustomRenderer: false,
509
- workloadTargets: starterPerformance.workloadTargets,
510
- }),
511
- ).toEqual(
512
- expect.arrayContaining([
513
- 'Non-custom renderer configs must use rendererStrategy "none", received "canvas-2d".',
514
- ]),
515
- );
516
- });
517
-
518
- it("requires custom renderers to declare a non-empty renderer workload", () => {
519
- expect(
520
- validateToolcraftPerformanceCoverage(starterSchema, {
521
- rendererStrategy: "canvas-2d",
522
- rendererWorkload: "none",
523
- scenarios: starterPerformance.scenarios,
524
- usesCustomRenderer: true,
525
- workloadTargets: starterPerformance.workloadTargets,
526
- }),
527
- ).toEqual(
528
- expect.arrayContaining([
529
- 'Custom renderers must declare rendererWorkload "simple-composition", "text-output", "vector-output", or "pixel-output".',
530
- ]),
531
- );
532
- });
533
-
534
- it("rejects renderer workloads on non-custom apps", () => {
535
- expect(
536
- validateToolcraftPerformanceCoverage(starterSchema, {
537
- rendererStrategy: "none",
538
- rendererWorkload: "simple-composition",
539
- scenarios: starterPerformance.scenarios,
540
- usesCustomRenderer: false,
541
- workloadTargets: starterPerformance.workloadTargets,
542
- }),
543
- ).toEqual(
544
- expect.arrayContaining([
545
- 'Non-custom renderer configs must use rendererWorkload "none", received "simple-composition".',
546
- ]),
547
- );
548
- });
549
-
550
- it("requires pixel-output renderers to use GPU or measured CPU evidence", () => {
551
- expect(
552
- validateToolcraftPerformanceCoverage(starterSchema, {
553
- rendererStrategy: "canvas-2d",
554
- rendererWorkload: "pixel-output",
555
- scenarios: starterPerformance.scenarios,
556
- usesCustomRenderer: true,
557
- workloadTargets: starterPerformance.workloadTargets,
558
- }),
559
- ).toEqual(
560
- expect.arrayContaining([
561
- 'rendererWorkload "pixel-output" should use rendererStrategy "webgl" or "webgpu", received "canvas-2d". Keeping a CPU renderer requires rendererTechnique.measuredAlternativeEvidence for WebGL/WebGPU stress comparison.',
562
- ]),
563
- );
564
- });
565
-
566
- it("rejects text and vector product techniques that silently choose pixel output", () => {
567
- expect(
568
- validateToolcraftPerformanceCoverage(starterSchema, {
569
- rendererStrategy: "webgl",
570
- rendererTechnique: {
571
- exportRenderer: "webgl",
572
- fidelityRisks: ["raster output could blur product geometry"],
573
- performanceRisks: ["large output requires GPU-backed drawing"],
574
- previewRenderer: "webgl",
575
- productRepresentation: "vector",
576
- rendererStrategy: "webgl",
577
- rendererWorkload: "pixel-output",
578
- sourceRepresentation: "svg",
579
- whyNotAlternativeStrategies: ["svg preview was not selected"],
580
- },
581
- rendererWorkload: "pixel-output",
582
- scenarios: starterPerformance.scenarios,
583
- usesCustomRenderer: true,
584
- workloadTargets: starterPerformance.workloadTargets,
585
- }),
586
- ).toEqual(
587
- expect.arrayContaining([
588
- 'productRepresentation "vector" requires rendererWorkload "vector-output" unless intentionalRasterizationReason is provided.',
589
- ]),
590
- );
591
- });
592
-
593
- it("requires pixel-output renderers to include a stress preview or animation scenario", () => {
594
- expect(
595
- validateToolcraftPerformanceCoverage(starterSchema, {
596
- rendererStrategy: "webgl",
597
- rendererWorkload: "pixel-output",
598
- scenarios: [
599
- {
600
- automated: true,
601
- automatedTestName: "perf: preview render stays under budget",
602
- browser: true,
603
- browserTestName: "browser perf: preview render stays under budget",
604
- budget: { maxLongTaskMs: 120, maxPreviewMs: 1000 },
605
- expectedObservable: "Preview renders without freezing.",
606
- fixture: "1600x1000 output fixture",
607
- id: "preview-render",
608
- interaction: "preview-render",
609
- workload: false,
610
- },
611
- {
612
- automated: true,
613
- automatedTestName: "perf: prompt changes stay responsive",
614
- browser: true,
615
- browserTestName: "browser perf: prompt changes stay responsive",
616
- budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
617
- controlLabel: "Prompt",
618
- expectedObservable: "Prompt changes without blocking preview.",
619
- fixture: "starter prompt fixture",
620
- id: "generation-prompt-change",
621
- interaction: "control-change",
622
- target: "generation.prompt",
623
- values: {
624
- default: "Describe the effect",
625
- max: "Performance verified prompt with a longer generation request",
626
- min: "",
627
- },
628
- workload: true,
629
- },
630
- {
631
- automated: true,
632
- automatedTestName: "perf: viewport stays stable",
633
- browser: true,
634
- browserTestName: "browser perf: viewport stays stable",
635
- budget: { maxFrameGapMs: 80 },
636
- expectedObservable: "Viewport remains stable.",
637
- fixture: "1600x1000 output fixture",
638
- id: "viewport-stability",
639
- interaction: "viewport-stability",
640
- workload: false,
641
- },
642
- ],
643
- usesCustomRenderer: true,
644
- workloadTargets: ["generation.prompt"],
645
- }),
646
- ).toEqual(
647
- expect.arrayContaining([
648
- 'rendererWorkload "pixel-output" must include a stress preview-render or animation-frame scenario with stress: true for the largest product canvas and heaviest workload values.',
649
- ]),
650
- );
651
- });
652
-
653
- it("requires pixel-output renderers to budget long tasks", () => {
654
- expect(
655
- validateToolcraftPerformanceCoverage(starterSchema, {
656
- rendererStrategy: "webgl",
657
- rendererWorkload: "pixel-output",
658
- scenarios: [
659
- {
660
- automated: true,
661
- automatedTestName: "perf: preview render stays under budget",
662
- browser: true,
663
- browserTestName: "browser perf: preview render stays under budget",
664
- budget: { maxPreviewMs: 1000 },
665
- expectedObservable: "Worst-case preview renders without freezing.",
666
- fixture: "2400x1600 worst-case output fixture",
667
- id: "preview-render",
668
- interaction: "preview-render",
669
- stress: true,
670
- workload: false,
671
- },
672
- {
673
- automated: true,
674
- automatedTestName: "perf: prompt changes stay responsive",
675
- browser: true,
676
- browserTestName: "browser perf: prompt changes stay responsive",
677
- budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
678
- controlLabel: "Prompt",
679
- expectedObservable: "Prompt changes without blocking preview.",
680
- fixture: "starter prompt fixture",
681
- id: "generation-prompt-change",
682
- interaction: "control-change",
683
- target: "generation.prompt",
684
- values: {
685
- default: "Describe the effect",
686
- max: "Performance verified prompt with a longer generation request",
687
- min: "",
688
- },
689
- workload: true,
690
- },
691
- {
692
- automated: true,
693
- automatedTestName: "perf: viewport stays stable",
694
- browser: true,
695
- browserTestName: "browser perf: viewport stays stable",
696
- budget: { maxFrameGapMs: 80 },
697
- expectedObservable: "Viewport remains stable.",
698
- fixture: "1600x1000 output fixture",
699
- id: "viewport-stability",
700
- interaction: "viewport-stability",
701
- workload: false,
702
- },
703
- ],
704
- usesCustomRenderer: true,
705
- workloadTargets: ["generation.prompt"],
706
- }),
707
- ).toEqual(
708
- expect.arrayContaining([
709
- 'rendererWorkload "pixel-output" must include at least one maxLongTaskMs budget so GPU-backed previews cannot pass while freezing the main thread.',
710
- ]),
711
- );
712
- });
713
-
714
- it("detects low-resolution preview upscale code paths", () => {
715
- expect(
716
- sourceUsesLowResolutionPreviewUpscale(`
717
- const maxPreviewPixels = 1_250_000;
718
- const previewScale = Math.sqrt(maxPreviewPixels / (outputWidth * outputHeight));
719
- previewContext.drawImage(offscreenCanvas, 0, 0, outputWidth, outputHeight);
720
- `),
721
- ).toBe(true);
722
-
723
- expect(
724
- sourceUsesLowResolutionPreviewUpscale(`
725
- canvas.width = outputWidth;
726
- canvas.height = outputHeight;
727
- drawAsciiTextToCanvas({ canvas, text });
728
- `),
729
- ).toBe(false);
730
- });
731
-
732
- it("rejects low-resolution preview upscale for text and vector output renderers", () => {
733
- if (
734
- starterPerformance.rendererWorkload !== "text-output" &&
735
- starterPerformance.rendererWorkload !== "vector-output"
736
- ) {
737
- return;
738
- }
739
-
740
- expect(
741
- sourceUsesLowResolutionPreviewUpscale(),
742
- "Text/vector product previews must preserve native output fidelity. Do not render a low-resolution preview canvas/texture and upscale it to state.canvas.size; optimize layout/drawing instead.",
743
- ).toBe(false);
744
- });
745
-
746
- it("requires text and vector output browser tests to prove native preview resolution", () => {
747
- if (
748
- !starterPerformance.usesCustomRenderer ||
749
- (starterPerformance.rendererWorkload !== "text-output" &&
750
- starterPerformance.rendererWorkload !== "vector-output")
751
- ) {
752
- return;
753
- }
754
-
755
- expect(
756
- browserTestsAssertNativePreviewResolution(),
757
- "Text/vector custom renderers must have a browser test proving visible preview dimensions match product output dimensions so low-resolution upscale cannot pass unnoticed.",
758
- ).toBe(true);
759
- });
760
-
761
- it("requires render scale scenarios to prove backing canvas pixels in browser performance tests", () => {
762
- expect(
763
- browserPerfContractRequiresRenderScaleBackingPixels(),
764
- "Browser performance contract must require renderScale scenarios to assert backing canvas pixels, not only state or labels.",
765
- ).toBe(true);
766
- });
767
-
768
- it("requires procedural pixel-loop renderers to use a GPU strategy", () => {
769
- if (!sourceUsesCpuPixelLoop()) {
770
- return;
771
- }
772
-
773
- expect(
774
- starterPerformance.rendererWorkload,
775
- "Procedural ImageData/getImageData/putImageData renderers must be classified as pixel-output.",
776
- ).toBe("pixel-output");
777
- expect(
778
- starterPerformance.rendererStrategy,
779
- "Procedural ImageData/getImageData/putImageData renderers must be converted to WebGL/WebGPU or removed from the critical render path.",
780
- ).toMatch(/^(webgl|webgpu)$/);
781
- expect(
782
- sourceUsesGpuRenderer(),
783
- "Procedural pixel renderers must contain an actual WebGL/WebGPU code path, not only declare a GPU strategy.",
784
- ).toBe(true);
785
- expect(
786
- appPerformanceHasRenderPipelinePass("pixel-transform"),
787
- "Procedural pixel renderers must declare a rendererPipeline pixel-transform pass so caching and invalidation are machine-checkable.",
788
- ).toBe(true);
789
- });
790
-
791
- it("requires custom renderers to declare high-frequency viewport invalidation", () => {
792
- if (!starterPerformance.usesCustomRenderer) {
793
- return;
794
- }
795
-
796
- expect(
797
- appPerformanceHasInteractionInvalidation("viewport-zoom"),
798
- "Custom renderer apps must declare rendererPipeline interactionInvalidation for viewport-zoom so zoom can stay responsive without recomputing expensive passes.",
799
- ).toBe(true);
800
- });
801
-
802
- it("requires WebGL/WebGPU renderers to keep their pipeline lifecycle outside React render", () => {
803
- if (!sourceUsesGpuRenderer()) {
804
- return;
805
- }
806
-
807
- expect(
808
- sourceUsesWebGlLifecycleGuard(),
809
- "WebGL/WebGPU renderer setup must be guarded by refs, memoized setup, an effect, or a renderer class so control changes update uniforms/buffers instead of rebuilding the pipeline.",
810
- ).toBe(true);
811
- expect(
812
- sourceCreatesWebGlContextInComponentRender(),
813
- "Do not create a WebGL context directly in the component render path; initialize it once and update uniforms/buffers on runtime value changes.",
814
- ).toBe(false);
815
- });
816
-
817
- it("requires animation loops to clean up scheduled frames", () => {
818
- expect(
819
- sourceUsesAnimationFrameWithoutCleanup(),
820
- "Renderers that schedule requestAnimationFrame must cancelAnimationFrame on cleanup to avoid runaway loops during control changes or route unmount.",
821
- ).toBe(false);
822
- });
823
-
824
- it("rejects direct app storage writes outside the runtime persistence policy", () => {
825
- expect(
826
- sourceUsesDirectStorageApi(),
827
- "Generated apps must not read or write app state through localStorage/sessionStorage directly. Use runtime persistence policy when product persistence is required.",
828
- ).toBe(false);
829
- });
830
-
831
- it("rejects renderer effects that overwrite user-edited timeline duration", () => {
832
- expect(
833
- sourceResyncsTimelineDurationFromRuntimeDuration(),
834
- "Renderers must not watch state.timeline.durationSeconds and dispatch timeline.setDuration back to a computed local duration. Compute a default only during initialization/reset, then map renderer progress to state.timeline.durationSeconds.",
835
- ).toBe(false);
836
- });
837
-
838
- it("rejects timeline-driven texture uploads in GPU keyframe renderers", () => {
839
- if (
840
- !sourceUsesGpuRenderer() ||
841
- starterSchema.panels.timeline?.enabled !== true ||
842
- starterSchema.panels.timeline.mode !== "keyframes"
843
- ) {
844
- return;
845
- }
846
-
847
- expect(
848
- sourceMayUploadTextureFromTimelineDrivenEffect(),
849
- "GPU keyframe renderers must upload source textures only when media/resource keys change. Timeline-driven effects may update uniforms and draw, but must not call texImage2D or renderer.setImage from an effect that depends on settings/currentTime/keyframeGroups.",
850
- ).toBe(false);
851
- });
852
-
853
- it("requires workload targets for performance-sensitive schema controls", () => {
854
- const workloadTargets = new Set(starterPerformance.workloadTargets);
855
- const sensitiveControls = collectToolcraftPerformanceSensitiveControls(starterSchema);
856
-
857
- for (const { controlId, target } of sensitiveControls) {
858
- expect(
859
- workloadTargets,
860
- `${controlId} (${target}) looks performance-sensitive and must be listed in workloadTargets or deliberately removed from the app.`,
861
- ).toContain(target);
862
- }
863
- });
864
-
865
- it("requires each automated performance scenario to point at an app test", () => {
866
- const testSources = readSiblingAppTestSources();
867
-
868
- for (const scenario of starterPerformance.scenarios) {
869
- if (!scenario.automated) {
870
- continue;
871
- }
872
-
873
- expect(
874
- testSources,
875
- `${scenario.id} must be backed by an app test named "${scenario.automatedTestName}".`,
876
- ).toContain(scenario.automatedTestName);
877
- }
878
- });
879
-
880
- it("requires each browser performance scenario to point at a fallback Playwright test", () => {
881
- const browserTestSources = readBrowserTestSources();
882
-
883
- for (const scenario of starterPerformance.scenarios) {
884
- if (!scenario.browser) {
885
- continue;
886
- }
887
-
888
- expect(
889
- browserTestSources,
890
- `${scenario.id} must be backed by a fallback Playwright test named "${scenario.browserTestName}".`,
891
- ).toContain(scenario.browserTestName);
892
- }
893
- });
894
-
895
- it("rejects reused browser performance tests across scenarios", () => {
896
- expect(
897
- validateToolcraftPerformanceCoverage(starterSchema, {
898
- rendererStrategy: "none",
899
- rendererWorkload: "none",
900
- scenarios: [
901
- {
902
- automated: true,
903
- automatedTestName: "perf: prompt min change stays responsive",
904
- browser: true,
905
- browserTestName: "browser perf: prompt changes stay responsive",
906
- budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
907
- controlLabel: "Prompt",
908
- expectedObservable: "Prompt min edit updates product output.",
909
- fixture: "starter prompt fixture",
910
- id: "prompt-min-change",
911
- interaction: "control-change",
912
- target: "generation.prompt",
913
- values: { default: "Describe the effect", max: "Long prompt", min: "" },
914
- workload: true,
915
- },
916
- {
917
- automated: true,
918
- automatedTestName: "perf: prompt max change stays responsive",
919
- browser: true,
920
- browserTestName: "browser perf: prompt changes stay responsive",
921
- budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
922
- controlLabel: "Prompt",
923
- expectedObservable: "Prompt max edit updates product output.",
924
- fixture: "starter prompt fixture",
925
- id: "prompt-max-change",
926
- interaction: "control-change",
927
- target: "generation.prompt",
928
- values: { default: "Describe the effect", max: "Long prompt", min: "" },
929
- workload: true,
930
- },
931
- ],
932
- usesCustomRenderer: false,
933
- workloadTargets: ["generation.prompt"],
934
- }),
935
- ).toEqual(
936
- expect.arrayContaining([
937
- 'prompt-max-change browserTestName "browser perf: prompt changes stay responsive" is already used by prompt-min-change. Give each performance scenario its own browser test so every control is actually exercised.',
938
- ]),
939
- );
940
- });
941
-
942
- it("fails custom renderer configs that omit real workload coverage", () => {
943
- expect(
944
- validateToolcraftPerformanceCoverage(starterSchema, {
945
- rendererStrategy: "canvas-2d",
946
- rendererWorkload: "simple-composition",
947
- scenarios: [
948
- {
949
- automated: true,
950
- automatedTestName: "perf: preview render stays under budget",
951
- browser: true,
952
- browserTestName: "browser perf: preview render stays interactive",
953
- budget: { maxPreviewMs: 100 },
954
- expectedObservable: "Preview renders without freezing.",
955
- fixture: "1600x1000 gradient fixture",
956
- id: "preview-render",
957
- interaction: "preview-render",
958
- workload: false,
959
- },
960
- ],
961
- usesCustomRenderer: true,
962
- workloadTargets: ["generation.prompt"],
963
- }),
964
- ).toEqual(
965
- expect.arrayContaining([
966
- "Custom renderers must include a control-drag performance scenario.",
967
- "generation.prompt must have min/default/max workload performance coverage.",
968
- ]),
969
- );
970
- });
971
-
972
- it("requires custom renderers to cover upload, output actions, and viewport stability", () => {
973
- const outputSchema = defineToolcraft({
974
- canvas: {
975
- enabled: true,
976
- size: { height: 1080, unit: "px", width: 1920 },
977
- sizing: { mode: "editable-output" },
978
- upload: true,
979
- },
980
- panels: {
981
- controls: {
982
- sections: [
983
- {
984
- controls: {
985
- export: {
986
- actions: [{ label: "Export PNG", value: "export-png" }],
987
- target: "panel.actions",
988
- type: "panelActions",
989
- },
990
- quality: {
991
- defaultValue: 0.5,
992
- label: "Quality",
993
- max: 1,
994
- min: 0,
995
- target: "render.quality",
996
- type: "slider",
997
- },
998
- },
999
- },
1000
- ],
1001
- title: "Renderer Controls",
1002
- },
1003
- },
1004
- });
1005
-
1006
- expect(
1007
- validateToolcraftPerformanceCoverage(outputSchema, {
1008
- rendererStrategy: "canvas-2d",
1009
- rendererWorkload: "simple-composition",
1010
- scenarios: [
1011
- {
1012
- automated: true,
1013
- automatedTestName: "perf: preview render stays under budget",
1014
- browser: true,
1015
- browserTestName: "browser perf: preview render stays under budget",
1016
- budget: { maxPreviewMs: 1000 },
1017
- expectedObservable: "Preview renders without freezing.",
1018
- fixture: "1920x1080 output fixture",
1019
- id: "preview-render",
1020
- interaction: "preview-render",
1021
- workload: false,
1022
- },
1023
- {
1024
- automated: true,
1025
- automatedTestName: "perf: quality drag stays responsive",
1026
- browser: true,
1027
- browserTestName: "browser perf: quality drag stays responsive",
1028
- budget: { maxFrameGapMs: 50, maxInteractionMs: 250 },
1029
- controlLabel: "Quality",
1030
- expectedObservable: "Dragging Quality remains responsive.",
1031
- fixture: "1920x1080 output fixture",
1032
- id: "quality-drag",
1033
- interaction: "control-drag",
1034
- target: "render.quality",
1035
- values: { default: 0.5, max: 1, min: 0 },
1036
- workload: true,
1037
- },
1038
- ],
1039
- usesCustomRenderer: true,
1040
- workloadTargets: ["render.quality"],
1041
- }),
1042
- ).toEqual(
1043
- expect.arrayContaining([
1044
- "Custom renderers must include a viewport-stability performance scenario.",
1045
- "Custom renderers with canvas upload must include a media-import performance scenario.",
1046
- "Output actions must include an export-copy performance scenario.",
1047
- ]),
1048
- );
1049
- });
1050
-
1051
- it("requires keyframe custom renderers to cover viewport stability during keyframe interactions", () => {
1052
- const keyframeSchema = defineToolcraft({
1053
- canvas: {
1054
- enabled: true,
1055
- sizing: { mode: "intrinsic-media" },
1056
- },
1057
- panels: {
1058
- controls: {
1059
- sections: [
1060
- {
1061
- controls: {
1062
- intensity: {
1063
- defaultValue: 0.5,
1064
- label: "Intensity",
1065
- max: 1,
1066
- min: 0,
1067
- target: "render.intensity",
1068
- type: "slider",
1069
- },
1070
- },
1071
- title: "Render",
1072
- },
1073
- ],
1074
- title: "Render Controls",
1075
- },
1076
- timeline: { mode: "keyframes" },
1077
- },
1078
- });
1079
-
1080
- expect(
1081
- validateToolcraftPerformanceCoverage(keyframeSchema, {
1082
- rendererStrategy: "webgl",
1083
- rendererWorkload: "simple-composition",
1084
- scenarios: [
1085
- {
1086
- automated: true,
1087
- automatedTestName: "perf: preview render stays under budget",
1088
- browser: true,
1089
- browserTestName: "browser perf: preview render stays under budget",
1090
- budget: { maxPreviewMs: 1000 },
1091
- expectedObservable: "Preview renders without freezing.",
1092
- fixture: "1440x1080 shader fixture",
1093
- id: "preview-render",
1094
- interaction: "preview-render",
1095
- workload: false,
1096
- },
1097
- {
1098
- automated: true,
1099
- automatedTestName: "perf: intensity drag stays responsive",
1100
- browser: true,
1101
- browserTestName: "browser perf: intensity drag stays responsive",
1102
- budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
1103
- controlLabel: "Intensity",
1104
- expectedObservable: "Dragging Intensity remains responsive.",
1105
- fixture: "1440x1080 shader fixture",
1106
- id: "intensity-drag",
1107
- interaction: "control-drag",
1108
- target: "render.intensity",
1109
- values: { default: 0.5, max: 1, min: 0 },
1110
- workload: true,
1111
- },
1112
- {
1113
- automated: true,
1114
- automatedTestName: "perf: viewport stays stable",
1115
- browser: true,
1116
- browserTestName: "browser perf: viewport stays stable",
1117
- budget: { maxFrameGapMs: 80 },
1118
- expectedObservable: "Changing controls does not move the canvas viewport.",
1119
- fixture: "1440x1080 shader fixture",
1120
- id: "viewport-stability",
1121
- interaction: "viewport-stability",
1122
- target: "render.intensity",
1123
- workload: false,
1124
- },
1125
- ],
1126
- usesCustomRenderer: true,
1127
- workloadTargets: ["render.intensity"],
1128
- }),
1129
- ).toEqual(
1130
- expect.arrayContaining([
1131
- 'Keyframe custom renderers must include a viewport-stability performance scenario with target "timeline.keyframes" that exercises zoom/radar, expanded keyframes, keyframe creation, and playback or scrubbing.',
1132
- ]),
1133
- );
1134
- });
1135
-
1136
- it("requires layer custom renderers to cover viewport stability during layer interactions", () => {
1137
- const layerSchema = defineToolcraft({
1138
- canvas: {
1139
- enabled: true,
1140
- upload: true,
1141
- },
1142
- panels: {
1143
- controls: {
1144
- sections: [
1145
- {
1146
- controls: {
1147
- opacity: {
1148
- defaultValue: 1,
1149
- label: "Opacity",
1150
- max: 1,
1151
- min: 0,
1152
- target: "selectedLayer.opacity",
1153
- type: "slider",
1154
- },
1155
- },
1156
- title: "Layer",
1157
- },
1158
- ],
1159
- title: "Layer Controls",
1160
- },
1161
- layers: true,
1162
- },
1163
- });
1164
-
1165
- expect(
1166
- validateToolcraftPerformanceCoverage(layerSchema, {
1167
- rendererStrategy: "webgl",
1168
- rendererWorkload: "simple-composition",
1169
- scenarios: [
1170
- {
1171
- automated: true,
1172
- automatedTestName: "perf: preview render stays under budget",
1173
- browser: true,
1174
- browserTestName: "browser perf: preview render stays under budget",
1175
- budget: { maxPreviewMs: 1000 },
1176
- expectedObservable: "Preview renders without freezing.",
1177
- fixture: "two-layer image fixture",
1178
- id: "preview-render",
1179
- interaction: "preview-render",
1180
- workload: false,
1181
- },
1182
- {
1183
- automated: true,
1184
- automatedTestName: "perf: opacity drag stays responsive",
1185
- browser: true,
1186
- browserTestName: "browser perf: opacity drag stays responsive",
1187
- budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
1188
- controlLabel: "Opacity",
1189
- expectedObservable: "Dragging selected layer opacity remains responsive.",
1190
- fixture: "two-layer image fixture",
1191
- id: "opacity-drag",
1192
- interaction: "control-drag",
1193
- target: "selectedLayer.opacity",
1194
- values: { default: 1, max: 1, min: 0 },
1195
- workload: true,
1196
- },
1197
- {
1198
- automated: true,
1199
- automatedTestName: "perf: viewport stays stable",
1200
- browser: true,
1201
- browserTestName: "browser perf: viewport stays stable",
1202
- budget: { maxFrameGapMs: 80 },
1203
- expectedObservable: "Changing controls does not move the canvas viewport.",
1204
- fixture: "two-layer image fixture",
1205
- id: "viewport-stability",
1206
- interaction: "viewport-stability",
1207
- target: "selectedLayer.opacity",
1208
- workload: false,
1209
- },
1210
- ],
1211
- usesCustomRenderer: true,
1212
- workloadTargets: ["selectedLayer.opacity"],
1213
- }),
1214
- ).toEqual(
1215
- expect.arrayContaining([
1216
- 'Layer-enabled custom renderers must include a viewport-stability performance scenario with target "layers.interactions" that exercises zoom/radar, layer selection, visibility, reorder or grouping, and selected-layer output stability.',
1217
- ]),
1218
- );
1219
- });
1220
-
1221
- it("requires every visible non-action control to have performance coverage", () => {
1222
- const rendererSchema = defineToolcraft({
1223
- canvas: {
1224
- enabled: true,
1225
- size: { height: 1080, unit: "px", width: 1440 },
1226
- sizing: { mode: "editable-output" },
1227
- },
1228
- panels: {
1229
- controls: {
1230
- sections: [
1231
- {
1232
- controls: {
1233
- depth: {
1234
- defaultValue: 50,
1235
- label: "Depth",
1236
- max: 100,
1237
- min: 0,
1238
- target: "shader.depth",
1239
- type: "slider",
1240
- },
1241
- gradient: {
1242
- defaultValue: "aurora",
1243
- label: "Gradient",
1244
- options: [
1245
- { label: "Aurora", value: "aurora" },
1246
- { label: "Prism", value: "prism" },
1247
- ],
1248
- target: "shader.gradient",
1249
- type: "select",
1250
- },
1251
- mode: {
1252
- defaultValue: "soft",
1253
- label: "Mode",
1254
- options: [
1255
- { label: "Soft", value: "soft" },
1256
- { label: "Sharp", value: "sharp" },
1257
- ],
1258
- target: "shader.mode",
1259
- type: "segmented",
1260
- },
1261
- },
1262
- title: "Shader",
1263
- },
1264
- ],
1265
- title: "Shader Controls",
1266
- },
1267
- },
1268
- });
1269
-
1270
- expect(
1271
- validateToolcraftPerformanceCoverage(rendererSchema, {
1272
- rendererStrategy: "canvas-2d",
1273
- rendererWorkload: "simple-composition",
1274
- scenarios: [
1275
- {
1276
- automated: true,
1277
- automatedTestName: "perf: preview render stays under budget",
1278
- browser: true,
1279
- browserTestName: "browser perf: preview render stays under budget",
1280
- budget: { maxPreviewMs: 1000 },
1281
- expectedObservable: "Preview renders without freezing.",
1282
- fixture: "1440x1080 shader fixture",
1283
- id: "preview-render",
1284
- interaction: "preview-render",
1285
- workload: false,
1286
- },
1287
- {
1288
- automated: true,
1289
- automatedTestName: "perf: depth drag stays responsive",
1290
- browser: true,
1291
- browserTestName: "browser perf: depth drag stays responsive",
1292
- budget: { maxFrameGapMs: 50, maxInteractionMs: 250 },
1293
- controlLabel: "Depth",
1294
- expectedObservable: "Dragging Depth remains responsive.",
1295
- fixture: "1440x1080 shader fixture",
1296
- id: "depth-drag",
1297
- interaction: "control-drag",
1298
- target: "shader.depth",
1299
- values: { default: 50, max: 100, min: 0 },
1300
- workload: true,
1301
- },
1302
- {
1303
- automated: true,
1304
- automatedTestName: "perf: viewport stays stable",
1305
- browser: true,
1306
- browserTestName: "browser perf: viewport stays stable",
1307
- budget: { maxFrameGapMs: 50 },
1308
- expectedObservable: "Canvas zoom and offset do not jump.",
1309
- fixture: "1440x1080 shader fixture",
1310
- id: "viewport-stability",
1311
- interaction: "viewport-stability",
1312
- workload: false,
1313
- },
1314
- ],
1315
- usesCustomRenderer: true,
1316
- workloadTargets: ["shader.depth"],
1317
- }),
1318
- ).toEqual(
1319
- expect.arrayContaining([
1320
- "shader.gradient must have a performance scenario because every visible control can affect app responsiveness.",
1321
- "shader.mode must have a performance scenario because every visible control can affect app responsiveness.",
1322
- ]),
1323
- );
1324
- });
1325
-
1326
- it("requires visible control performance coverage even without a custom renderer", () => {
1327
- const controlsSchema = defineToolcraft({
1328
- canvas: {
1329
- enabled: true,
1330
- sizing: { mode: "intrinsic-media" },
1331
- },
1332
- panels: {
1333
- controls: {
1334
- sections: [
1335
- {
1336
- controls: {
1337
- mode: {
1338
- defaultValue: "soft",
1339
- label: "Mode",
1340
- options: [
1341
- { label: "Soft", value: "soft" },
1342
- { label: "Sharp", value: "sharp" },
1343
- ],
1344
- target: "app.mode",
1345
- type: "select",
1346
- },
1347
- },
1348
- title: "Display",
1349
- },
1350
- ],
1351
- title: "Controls",
1352
- },
1353
- },
1354
- });
1355
-
1356
- expect(
1357
- validateToolcraftPerformanceCoverage(controlsSchema, {
1358
- rendererStrategy: "none",
1359
- rendererWorkload: "none",
1360
- scenarios: [],
1361
- usesCustomRenderer: false,
1362
- workloadTargets: [],
1363
- }),
1364
- ).toEqual(
1365
- expect.arrayContaining([
1366
- "app.mode must have a performance scenario because every visible control can affect app responsiveness.",
1367
- ]),
1368
- );
1369
- });
1370
-
1371
- it("requires budgets that match each performance interaction type", () => {
1372
- expect(
1373
- validateToolcraftPerformanceCoverage(starterSchema, {
1374
- rendererStrategy: "none",
1375
- rendererWorkload: "none",
1376
- scenarios: [
1377
- {
1378
- automated: true,
1379
- automatedTestName: "perf: char size drag stays responsive",
1380
- browser: true,
1381
- browserTestName: "browser perf: char size drag stays responsive",
1382
- budget: { maxExportMs: 100 },
1383
- expectedObservable: "Dragging Char Size remains responsive.",
1384
- fixture: "1600x1000 transparent glyph fixture",
1385
- id: "char-size-drag",
1386
- interaction: "control-drag",
1387
- target: "generation.prompt",
1388
- values: { default: 12, max: 32, min: 4 },
1389
- workload: true,
1390
- },
1391
- {
1392
- automated: true,
1393
- automatedTestName: "perf: export png stays under budget",
1394
- browser: true,
1395
- browserTestName: "browser perf: export png stays under budget",
1396
- budget: { maxFrameGapMs: 50 },
1397
- expectedObservable: "Export completes without blocking the UI.",
1398
- fixture: "1920x1080 output fixture",
1399
- id: "export-png",
1400
- interaction: "export-copy",
1401
- workload: false,
1402
- },
1403
- {
1404
- automated: true,
1405
- automatedTestName: "perf: preview render stays under budget",
1406
- browser: true,
1407
- browserTestName: "browser perf: preview render stays under budget",
1408
- budget: { maxFrameGapMs: 50 },
1409
- expectedObservable: "Preview renders without freezing.",
1410
- fixture: "1920x1080 output fixture",
1411
- id: "preview-render",
1412
- interaction: "preview-render",
1413
- workload: false,
1414
- },
1415
- {
1416
- automated: true,
1417
- automatedTestName: "perf: animation frame loop stays smooth",
1418
- browser: true,
1419
- browserTestName: "browser perf: animation frame loop stays smooth",
1420
- budget: { maxFrameGapMs: 50 },
1421
- expectedObservable: "Animation advances without frame stalls.",
1422
- fixture: "animated output fixture",
1423
- id: "animation-frame-loop",
1424
- interaction: "animation-frame",
1425
- workload: false,
1426
- },
1427
- ],
1428
- usesCustomRenderer: false,
1429
- workloadTargets: ["generation.prompt"],
1430
- }),
1431
- ).toEqual(
1432
- expect.arrayContaining([
1433
- "char-size-drag control-drag scenario must declare maxInteractionMs and maxFrameGapMs.",
1434
- "export-png export-copy scenario must declare maxExportMs.",
1435
- "preview-render preview-render scenario must declare maxPreviewMs or maxRenderMs.",
1436
- "animation-frame-loop animation-frame scenario must declare maxLongTaskMs.",
1437
- ]),
1438
- );
1439
- });
1440
-
1441
- it("rejects performance budgets that are too loose to catch lag", () => {
1442
- expect(
1443
- validateToolcraftPerformanceCoverage(starterSchema, {
1444
- rendererStrategy: "none",
1445
- rendererWorkload: "none",
1446
- scenarios: [
1447
- {
1448
- automated: true,
1449
- automatedTestName: "perf: char size drag stays responsive",
1450
- browser: true,
1451
- browserTestName: "browser perf: char size drag stays responsive",
1452
- budget: { maxFrameGapMs: 1000, maxInteractionMs: 10000 },
1453
- controlLabel: "Char Size",
1454
- expectedObservable: "Dragging Char Size remains responsive.",
1455
- fixture: "1600x1000 transparent glyph fixture",
1456
- id: "char-size-drag",
1457
- interaction: "control-drag",
1458
- target: "generation.prompt",
1459
- values: { default: 12, max: 32, min: 4 },
1460
- workload: true,
1461
- },
1462
- ],
1463
- usesCustomRenderer: false,
1464
- workloadTargets: ["generation.prompt"],
1465
- }),
1466
- ).toEqual(
1467
- expect.arrayContaining([
1468
- "char-size-drag maxFrameGapMs budget must be <= 120ms, received 1000ms.",
1469
- "char-size-drag maxInteractionMs budget must be <= 2000ms, received 10000ms.",
1470
- ]),
1471
- );
1472
- });
1473
-
1474
- it("does not let load-profile smooth targets bypass required interaction budgets", () => {
1475
- const schema = defineToolcraft({
1476
- canvas: { enabled: true },
1477
- panels: {
1478
- controls: {
1479
- sections: [
1480
- {
1481
- controls: {
1482
- density: {
1483
- defaultValue: 4,
1484
- label: "Density",
1485
- max: 12,
1486
- min: 1,
1487
- performanceReason: "Density changes rendered output size.",
1488
- performanceRole: "workload",
1489
- target: "render.density",
1490
- type: "slider",
1491
- },
1492
- },
1493
- title: "Render",
1494
- },
1495
- ],
1496
- title: "Controls",
1497
- },
1498
- },
1499
- });
1500
-
1501
- expect(
1502
- validateToolcraftPerformanceCoverage(schema, {
1503
- rendererStrategy: "none",
1504
- rendererWorkload: "none",
1505
- scenarios: [
1506
- {
1507
- automated: true,
1508
- automatedTestName: "perf: density drag stays responsive",
1509
- browser: true,
1510
- browserTestName: "browser perf: density drag stays responsive",
1511
- budget: { maxFrameGapMs: 80 },
1512
- controlLabel: "Density",
1513
- expectedObservable: "Dragging Density updates product output without blocking the UI.",
1514
- fixture: "density smooth target fixture",
1515
- id: "density-drag",
1516
- interaction: "control-drag",
1517
- stressFixture: {
1518
- kind: "max-value",
1519
- loadProfile: {
1520
- degradationStepPercent: 10,
1521
- evidence: [
1522
- {
1523
- attemptedTarget: 12,
1524
- decision: "Keep 12 as experimental and guarantee smoothness through 11.",
1525
- measuredResult: "At density 12, maxFrameGapMs 148 exceeded the 80ms budget.",
1526
- optimizationAttempted:
1527
- "Cached expensive render inputs and coalesced preview work to requestAnimationFrame.",
1528
- result: "failed",
1529
- scenarioId: "density-drag",
1530
- },
1531
- ],
1532
- hardLimit: 12,
1533
- metric: "numeric-max",
1534
- smoothTarget: 11,
1535
- smoothTargetRatio: 0.9,
1536
- target: "render.density",
1537
- userFacingRange: "experimental-above-smooth",
1538
- },
1539
- reason: "Density 11 is the measured smooth target after hard-limit testing.",
1540
- value: 11,
1541
- },
1542
- target: "render.density",
1543
- values: { default: 4, max: 12, min: 1 },
1544
- workload: true,
1545
- },
1546
- ],
1547
- usesCustomRenderer: false,
1548
- workloadTargets: ["render.density"],
1549
- }),
1550
- ).toContain("density-drag control-drag scenario must declare maxInteractionMs.");
1551
- });
1552
-
1553
- it("requires real browser interaction metadata for control performance scenarios", () => {
1554
- expect(
1555
- validateToolcraftPerformanceCoverage(starterSchema, {
1556
- rendererStrategy: "none",
1557
- rendererWorkload: "none",
1558
- scenarios: [
1559
- {
1560
- automated: true,
1561
- automatedTestName: "perf: char size drag stays responsive",
1562
- browser: true,
1563
- browserTestName: "browser perf: char size drag stays responsive",
1564
- budget: { maxFrameGapMs: 50, maxInteractionMs: 250 },
1565
- expectedObservable: "Dragging Char Size remains responsive.",
1566
- fixture: "1600x1000 transparent glyph fixture",
1567
- id: "char-size-drag",
1568
- interaction: "control-drag",
1569
- target: "generation.prompt",
1570
- values: { default: 12, max: 32, min: 4 },
1571
- workload: true,
1572
- },
1573
- ],
1574
- usesCustomRenderer: false,
1575
- workloadTargets: ["generation.prompt"],
1576
- }),
1577
- ).toEqual(
1578
- expect.arrayContaining([
1579
- "char-size-drag control-drag scenario must declare controlLabel or uiSelector for its real browser interaction.",
1580
- ]),
1581
- );
1582
- });
1583
- });