@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,1446 +1,23 @@
1
- import { TOOLCRAFT_COMPONENT_CONTRACTS } from "../contracts/component-contracts";
1
+ import { createToolcraftAssemblyContract } from "./assembly-contract";
2
+ import { normalizeToolcraftPanels } from "./panels-schema-normalization";
3
+ import { resolveToolcraftSettingsTransfer } from "./runtime-setup-section";
2
4
  import {
3
- getToolcraftCanvasAspectRatioPreset,
4
- getToolcraftCanvasAspectRatioPresetBySize,
5
- } from "./canvas-aspect-ratio-presets";
6
- import { toolcraftTimelinePanelExtendedTarget } from "./runtime-targets";
5
+ defaultToolcraftCanvasSize,
6
+ resolveToolcraftCanvasRenderScale,
7
+ resolveToolcraftCanvasSizing,
8
+ resolveToolcraftExport,
9
+ resolveToolcraftMedia,
10
+ resolveToolcraftPersistence,
11
+ resolveToolcraftToolbar,
12
+ } from "./schema-resolvers";
7
13
  import type {
8
- ToolcraftAssemblyCapability,
9
- ToolcraftAssemblyCommand,
10
- ToolcraftAssemblyComponentId,
11
- ToolcraftAssemblyContract,
12
- ToolcraftAssemblyPanelContract,
13
14
  ToolcraftAppSchema,
14
- ToolcraftControlLayoutGroupSchema,
15
- ToolcraftControlSectionSchema,
16
- ToolcraftCanvasSize,
17
- ToolcraftCanvasSizingSchema,
18
- ToolcraftControlSchema,
19
- ToolcraftControlsPanelSchema,
20
- ToolcraftSettingsTransferSchema,
21
- ToolcraftTimelinePanelSchema,
22
15
  ToolcraftToolbarSchema,
23
16
  ResolvedToolcraftPanelsSchema,
24
- ResolvedToolcraftSettingsTransferSchema,
25
- ResolvedToolcraftTimelinePanelSchema,
26
17
  ResolvedToolcraftAppSchema,
27
18
  } from "./types";
28
19
 
29
- const defaultCanvasSize = {
30
- height: 1080,
31
- unit: "px",
32
- width: 1920,
33
- } satisfies ToolcraftCanvasSize;
34
-
35
- type ResolvedCanvas = ResolvedToolcraftAppSchema["canvas"];
36
- type ResolvedExport = ResolvedToolcraftAppSchema["export"];
37
- type ResolvedMedia = ResolvedToolcraftAppSchema["media"];
38
20
  type ResolvedToolbar = Required<ToolcraftToolbarSchema>;
39
- type PanelContract = {
40
- capabilities?: readonly string[];
41
- defaultPlacement: ToolcraftAssemblyPanelContract["defaultPlacement"];
42
- snapEdges: ToolcraftAssemblyPanelContract["snapEdges"];
43
- visualComponent: string;
44
- };
45
-
46
- const canvasSizeControlTargets = {
47
- height: "canvas.size.height",
48
- width: "canvas.size.width",
49
- } as const;
50
- const canvasAspectRatioTarget = "canvas.aspectRatio";
51
- const canvasRenderScaleTarget = "canvas.renderScale";
52
- const defaultTimelineDurationSeconds = 8;
53
- const minTimelineDurationSeconds = 1;
54
- const maxTimelineDurationSeconds = 60;
55
- const defaultCanvasRenderScale = {
56
- defaultValue: 2,
57
- enabled: false,
58
- max: 2,
59
- min: 1,
60
- step: 0.25,
61
- } satisfies ResolvedToolcraftAppSchema["canvas"]["renderScale"];
62
- const maxAutoInlineControlLabelLength = 18;
63
- const settingsTransferTarget = "runtime.settingsTransfer";
64
- const runtimeSetupSectionTitle = "Setup";
65
-
66
- type ToolcraftControlActionSchema = NonNullable<
67
- ToolcraftControlSchema["actions"]
68
- >[number];
69
-
70
- function unique<const Value extends string>(values: readonly Value[]): Value[] {
71
- return Array.from(new Set(values));
72
- }
73
-
74
- function assertNever(value: never): never {
75
- throw new Error(`Unsupported Toolcraft template persistence storage: ${String(value)}`);
76
- }
77
-
78
- function resolvePersistence(
79
- persistence: ToolcraftAppSchema["persistence"],
80
- ): ResolvedToolcraftAppSchema["persistence"] {
81
- switch (persistence?.storage) {
82
- case undefined:
83
- case "none":
84
- return { storage: "none" };
85
- case "localStorage":
86
- return persistence;
87
- default:
88
- return assertNever(persistence);
89
- }
90
- }
91
-
92
- function slugifySettingsTransferAppId(value: string | undefined): string {
93
- const slug = value
94
- ?.trim()
95
- .toLowerCase()
96
- .replace(/[^a-z0-9]+/g, "-")
97
- .replace(/^-+|-+$/g, "");
98
-
99
- return slug || "toolcraft-app";
100
- }
101
-
102
- function getSettingsTransferMode(
103
- settingsTransfer: ToolcraftSettingsTransferSchema | undefined,
104
- ): "auto" | boolean {
105
- if (typeof settingsTransfer === "object" && settingsTransfer !== null) {
106
- return settingsTransfer.enabled ?? "auto";
107
- }
108
-
109
- return settingsTransfer ?? "auto";
110
- }
111
-
112
- function getSettingsTransferObject(
113
- settingsTransfer: ToolcraftSettingsTransferSchema | undefined,
114
- ): Extract<ToolcraftSettingsTransferSchema, object> | undefined {
115
- return typeof settingsTransfer === "object" && settingsTransfer !== null
116
- ? settingsTransfer
117
- : undefined;
118
- }
119
-
120
- function getSettingsTransferAppId({
121
- controls,
122
- persistence,
123
- settingsTransfer,
124
- }: {
125
- controls: ToolcraftControlsPanelSchema | undefined;
126
- persistence: ResolvedToolcraftAppSchema["persistence"];
127
- settingsTransfer: ToolcraftSettingsTransferSchema | undefined;
128
- }): string {
129
- const objectSchema = getSettingsTransferObject(settingsTransfer);
130
-
131
- if (objectSchema?.appId) {
132
- return slugifySettingsTransferAppId(objectSchema.appId);
133
- }
134
-
135
- if (persistence.storage === "localStorage") {
136
- const match = /^toolcraft:(.+):state:v\d+$/u.exec(persistence.key);
137
-
138
- if (match?.[1]) {
139
- return slugifySettingsTransferAppId(match[1]);
140
- }
141
- }
142
-
143
- return slugifySettingsTransferAppId(controls?.title);
144
- }
145
-
146
- function getSettingsTransferFileName({
147
- appId,
148
- settingsTransfer,
149
- }: {
150
- appId: string;
151
- settingsTransfer: ToolcraftSettingsTransferSchema | undefined;
152
- }): string {
153
- const explicitFileName = getSettingsTransferObject(settingsTransfer)?.fileName?.trim();
154
-
155
- if (explicitFileName) {
156
- return explicitFileName.endsWith(".json") ? explicitFileName : `${explicitFileName}.json`;
157
- }
158
-
159
- return `${appId}-settings.json`;
160
- }
161
-
162
- function resolveCanvasSizing(
163
- canvas: ToolcraftAppSchema["canvas"],
164
- ): ToolcraftCanvasSizingSchema {
165
- if (canvas.sizing) {
166
- return canvas.sizing;
167
- }
168
-
169
- if (canvas.size) {
170
- return { mode: "editable-output" };
171
- }
172
-
173
- if (canvas.upload) {
174
- return { mode: "editable-output" };
175
- }
176
-
177
- return { mode: "intrinsic-media" };
178
- }
179
-
180
- function clampCanvasRenderScale(value: number | undefined, fallback: number): number {
181
- if (typeof value !== "number" || !Number.isFinite(value)) {
182
- return fallback;
183
- }
184
-
185
- return Math.max(1, Math.min(2, value));
186
- }
187
-
188
- function resolveCanvasRenderScale(
189
- renderScale: ToolcraftAppSchema["canvas"]["renderScale"],
190
- ): ResolvedToolcraftAppSchema["canvas"]["renderScale"] {
191
- if (renderScale === true) {
192
- return {
193
- ...defaultCanvasRenderScale,
194
- enabled: true,
195
- };
196
- }
197
-
198
- if (!renderScale) {
199
- return defaultCanvasRenderScale;
200
- }
201
-
202
- const min = clampCanvasRenderScale(renderScale.min, defaultCanvasRenderScale.min);
203
- const max = Math.max(
204
- min,
205
- clampCanvasRenderScale(renderScale.max, defaultCanvasRenderScale.max),
206
- );
207
- const step =
208
- typeof renderScale.step === "number" && Number.isFinite(renderScale.step)
209
- ? Math.max(0.01, Math.min(1, renderScale.step))
210
- : defaultCanvasRenderScale.step;
211
- const defaultValue = Math.max(
212
- min,
213
- Math.min(
214
- max,
215
- clampCanvasRenderScale(renderScale.defaultValue, defaultCanvasRenderScale.defaultValue),
216
- ),
217
- );
218
-
219
- return {
220
- defaultValue,
221
- enabled: renderScale.enabled ?? true,
222
- max,
223
- min,
224
- step,
225
- };
226
- }
227
-
228
- function resolveExport(
229
- exportSchema: ToolcraftAppSchema["export"],
230
- ): ResolvedExport {
231
- return {
232
- png: {
233
- background: exportSchema?.png?.background ?? "include",
234
- },
235
- };
236
- }
237
-
238
- function resolveMedia(mediaSchema: ToolcraftAppSchema["media"]): ResolvedMedia {
239
- return {
240
- defaultAssets: mediaSchema?.defaultAssets ?? [],
241
- };
242
- }
243
-
244
- function getPanelDragMode(
245
- contract: { capabilities?: readonly string[] },
246
- ): ToolcraftAssemblyPanelContract["dragMode"] {
247
- return contract.capabilities?.includes("dragMode:handle") ? "handle" : "panel";
248
- }
249
-
250
- function createPanelAssemblyContract({
251
- capabilities = [],
252
- commands = [],
253
- contract,
254
- enabled,
255
- }: {
256
- capabilities?: readonly ToolcraftAssemblyCapability[];
257
- commands?: readonly ToolcraftAssemblyCommand[];
258
- contract: PanelContract;
259
- enabled: boolean;
260
- }): ToolcraftAssemblyPanelContract {
261
- const panelCapabilities = enabled
262
- ? unique<ToolcraftAssemblyCapability>([
263
- "panels.draggable",
264
- "panels.snap",
265
- "panels.doubleClickReset",
266
- ...capabilities,
267
- ])
268
- : [];
269
- const panelCommands = enabled
270
- ? unique<ToolcraftAssemblyCommand>([
271
- "panels.setOffset",
272
- "panels.setHidden",
273
- "panels.resetOffset",
274
- ...commands,
275
- ])
276
- : [];
277
-
278
- return {
279
- capabilities: panelCapabilities,
280
- commands: panelCommands,
281
- defaultPlacement: contract.defaultPlacement,
282
- dragMode: getPanelDragMode(contract),
283
- enabled,
284
- requiredWrapper: "PanelHost",
285
- snapEdges: contract.snapEdges,
286
- visualComponent: contract.visualComponent,
287
- };
288
- }
289
-
290
- function createToolcraftAssembly({
291
- canvas,
292
- panels,
293
- toolbar,
294
- }: {
295
- canvas: ResolvedCanvas;
296
- panels: ResolvedToolcraftPanelsSchema;
297
- toolbar: ResolvedToolbar;
298
- }): ToolcraftAssemblyContract {
299
- const components: ToolcraftAssemblyComponentId[] = [];
300
- const capabilities: ToolcraftAssemblyCapability[] = [];
301
- const commands: ToolcraftAssemblyCommand[] = [];
302
- const toolbarEnabled = toolbar.history || toolbar.radar || toolbar.theme || toolbar.zoom;
303
- const canvasEditableSize = canvas.sizing.mode === "editable-output";
304
-
305
- if (canvas.enabled) {
306
- components.push("canvas");
307
-
308
- if (canvasEditableSize) {
309
- capabilities.push("canvas.editableSize");
310
- commands.push("canvas.setSize");
311
- }
312
-
313
- if (canvas.renderScale.enabled) {
314
- capabilities.push("canvas.renderScale");
315
- }
316
-
317
- if (canvas.draggable) {
318
- capabilities.push("canvas.draggable");
319
- commands.push("canvas.panBy", "canvas.setOffset", "canvas.setViewport");
320
- }
321
-
322
- if (canvas.upload) {
323
- capabilities.push("canvas.upload");
324
- commands.push("media.delete", "media.import", "media.reorder", "media.transform");
325
- }
326
- }
327
-
328
- const controlsPanel = panels.controls
329
- ? createPanelAssemblyContract({
330
- capabilities: ["controls.panel", "controls.defaults"],
331
- commands: [
332
- "controls.apply",
333
- "controls.reset",
334
- "controls.resetTargets",
335
- "controls.setValue",
336
- ],
337
- contract: TOOLCRAFT_COMPONENT_CONTRACTS.controlsPanel,
338
- enabled: true,
339
- })
340
- : undefined;
341
-
342
- if (controlsPanel) {
343
- components.push("controlsPanel");
344
- capabilities.push(...controlsPanel.capabilities);
345
- commands.push(...controlsPanel.commands);
346
- }
347
-
348
- const layersPanel = panels.layers
349
- ? createPanelAssemblyContract({
350
- capabilities: [
351
- "layers.groups",
352
- "layers.panel",
353
- "layers.selection",
354
- "layers.visibility",
355
- ],
356
- commands: [
357
- "layers.add",
358
- "layers.delete",
359
- "layers.moveToGroup",
360
- "layers.rename",
361
- "layers.reorder",
362
- "layers.select",
363
- "layers.toggleCollapsed",
364
- "layers.toggleVisibility",
365
- ],
366
- contract: TOOLCRAFT_COMPONENT_CONTRACTS.layersPanel,
367
- enabled: true,
368
- })
369
- : undefined;
370
-
371
- if (layersPanel) {
372
- components.push("layersPanel");
373
- capabilities.push(...layersPanel.capabilities);
374
- commands.push(...layersPanel.commands);
375
- }
376
-
377
- const timelineKeyframesEnabled = panels.timeline?.mode === "keyframes";
378
- const timelinePanel = panels.timeline?.enabled
379
- ? createPanelAssemblyContract({
380
- capabilities: [
381
- "timeline.duration",
382
- "timeline.panel",
383
- "timeline.playback",
384
- ...(timelineKeyframesEnabled ? (["timeline.keyframes"] as const) : []),
385
- ],
386
- commands: [
387
- "timeline.setCurrentTime",
388
- "timeline.setDuration",
389
- "timeline.setPlaying",
390
- "timeline.toggleLoop",
391
- "timeline.togglePlayback",
392
- ...(timelineKeyframesEnabled
393
- ? ([
394
- "timeline.changeKeyframeEasing",
395
- "timeline.deleteControlKeyframes",
396
- "timeline.deleteKeyframe",
397
- "timeline.moveKeyframe",
398
- "timeline.selectKeyframe",
399
- "timeline.setExpanded",
400
- "timeline.toggleControlKeyframes",
401
- "timeline.toggleExpanded",
402
- ] as const)
403
- : []),
404
- ],
405
- contract: TOOLCRAFT_COMPONENT_CONTRACTS.timelinePanel,
406
- enabled: true,
407
- })
408
- : undefined;
409
-
410
- if (timelinePanel) {
411
- components.push("timelinePanel");
412
- capabilities.push(...timelinePanel.capabilities);
413
- commands.push(...timelinePanel.commands);
414
- }
415
-
416
- const toolbarCommands: ToolcraftAssemblyCommand[] = [];
417
- const toolbarCapabilities: ToolcraftAssemblyCapability[] = [];
418
-
419
- if (toolbar.history) {
420
- toolbarCapabilities.push("history.undoRedo", "toolbar.history");
421
- toolbarCommands.push("history.redo", "history.undo");
422
- }
423
-
424
- if (toolbar.radar) {
425
- toolbarCapabilities.push("toolbar.radar");
426
- toolbarCommands.push("canvas.center");
427
- }
428
-
429
- if (toolbar.theme) {
430
- toolbarCapabilities.push("toolbar.theme");
431
- }
432
-
433
- if (toolbar.zoom) {
434
- toolbarCapabilities.push("toolbar.zoom");
435
- toolbarCommands.push("canvas.zoomIn", "canvas.zoomOut", "canvas.zoomReset");
436
- }
437
-
438
- const toolbarPanel = createPanelAssemblyContract({
439
- capabilities: toolbarCapabilities,
440
- commands: toolbarCommands,
441
- contract: TOOLCRAFT_COMPONENT_CONTRACTS.toolbar,
442
- enabled: toolbarEnabled,
443
- });
444
-
445
- if (toolbarEnabled) {
446
- components.push("toolbar");
447
- capabilities.push(...toolbarPanel.capabilities);
448
- commands.push(...toolbarPanel.commands);
449
- }
450
-
451
- return {
452
- capabilities: unique(capabilities),
453
- commands: unique(commands),
454
- components: unique(components),
455
- surfaces: {
456
- canvas: {
457
- capabilities: canvas.enabled
458
- ? unique<ToolcraftAssemblyCapability>([
459
- ...(canvasEditableSize ? (["canvas.editableSize"] as const) : []),
460
- ...(canvas.draggable ? (["canvas.draggable"] as const) : []),
461
- ...(canvas.upload ? (["canvas.upload"] as const) : []),
462
- ])
463
- : [],
464
- commands: canvas.enabled
465
- ? unique<ToolcraftAssemblyCommand>([
466
- ...(canvasEditableSize ? (["canvas.setSize"] as const) : []),
467
- ...(canvas.draggable
468
- ? (["canvas.panBy", "canvas.setOffset", "canvas.setViewport"] as const)
469
- : []),
470
- ...(canvas.upload
471
- ? ([
472
- "media.delete",
473
- "media.import",
474
- "media.reorder",
475
- "media.transform",
476
- ] as const)
477
- : []),
478
- ])
479
- : [],
480
- enabled: canvas.enabled,
481
- visualComponent: "CanvasShell",
482
- },
483
- panels: {
484
- controls: controlsPanel,
485
- layers: layersPanel,
486
- timeline: timelinePanel,
487
- toolbar: toolbarPanel,
488
- },
489
- },
490
- };
491
- }
492
-
493
- function getControlDefaultSectionLayout(
494
- control: ToolcraftControlSchema,
495
- ): "grouped" | "standalone" {
496
- const contract = (
497
- TOOLCRAFT_COMPONENT_CONTRACTS as Record<
498
- string,
499
- { defaultSectionLayout?: "grouped" | "standalone"; kind?: string } | undefined
500
- >
501
- )[control.type];
502
-
503
- return contract?.kind === "control" && contract.defaultSectionLayout
504
- ? contract.defaultSectionLayout
505
- : "grouped";
506
- }
507
-
508
- function getControlSectionLayout(
509
- control: ToolcraftControlSchema,
510
- entries: readonly [string, ToolcraftControlSchema][],
511
- ): "grouped" | "standalone" {
512
- if (isControlGatedBySameSectionControl(control, entries)) {
513
- return "grouped";
514
- }
515
-
516
- if (
517
- (control.type === "color" || control.type === "colorOpacity") &&
518
- entries.some(
519
- ([, entryControl]) =>
520
- entryControl.type !== "color" &&
521
- entryControl.type !== "colorOpacity" &&
522
- getControlDefaultSectionLayout(entryControl) === "grouped",
523
- )
524
- ) {
525
- return "grouped";
526
- }
527
-
528
- return getControlDefaultSectionLayout(control);
529
- }
530
-
531
- function isControlGatedBySameSectionControl(
532
- control: ToolcraftControlSchema,
533
- entries: readonly [string, ToolcraftControlSchema][],
534
- ): boolean {
535
- const gateTargets = [control.visibleWhen?.target, control.disabledWhen?.target].filter(
536
- (target): target is string => Boolean(target),
537
- );
538
-
539
- if (gateTargets.length === 0) {
540
- return false;
541
- }
542
-
543
- return entries.some(([, entryControl]) =>
544
- gateTargets.includes(entryControl.target) &&
545
- getControlDefaultSectionLayout(entryControl) === "grouped",
546
- );
547
- }
548
-
549
- function createControlsRecord(
550
- entries: readonly [string, ToolcraftControlSchema][],
551
- ): Record<string, ToolcraftControlSchema> {
552
- return Object.fromEntries(
553
- entries.map(([id, control]) => [id, normalizeControlSchema(control)]),
554
- );
555
- }
556
-
557
- function isSliderLikeControl(control: ToolcraftControlSchema): boolean {
558
- return control.type === "slider" || control.type === "rangeSlider";
559
- }
560
-
561
- function getStepMarkerCount(control: ToolcraftControlSchema): number | undefined {
562
- if (
563
- typeof control.step !== "number" ||
564
- typeof control.min !== "number" ||
565
- typeof control.max !== "number" ||
566
- !Number.isFinite(control.step) ||
567
- !Number.isFinite(control.min) ||
568
- !Number.isFinite(control.max) ||
569
- control.step <= 0 ||
570
- control.max <= control.min
571
- ) {
572
- return undefined;
573
- }
574
-
575
- const rawStepCount = (control.max - control.min) / control.step;
576
- const roundedStepCount = Math.round(rawStepCount);
577
- const stepCount =
578
- Math.abs(rawStepCount - roundedStepCount) < Number.EPSILON * 100
579
- ? roundedStepCount
580
- : Math.floor(rawStepCount) + 1;
581
-
582
- return Math.max(2, stepCount + 1);
583
- }
584
-
585
- function normalizeControlSchema(
586
- control: ToolcraftControlSchema,
587
- ): ToolcraftControlSchema {
588
- if (
589
- !isSliderLikeControl(control) ||
590
- typeof control.step !== "number" ||
591
- control.variant !== "discrete"
592
- ) {
593
- return control;
594
- }
595
-
596
- return {
597
- ...control,
598
- markerCount: getStepMarkerCount(control) ?? control.markerCount,
599
- variant: "discrete",
600
- };
601
- }
602
-
603
- function filterLayoutGroupsForControlIds(
604
- layoutGroups: readonly ToolcraftControlLayoutGroupSchema[] | undefined,
605
- controlIds: ReadonlySet<string>,
606
- ): ToolcraftControlLayoutGroupSchema[] {
607
- return (layoutGroups ?? [])
608
- .map((layoutGroup) => ({
609
- ...layoutGroup,
610
- controls: layoutGroup.controls.filter((controlId) => controlIds.has(controlId)),
611
- }))
612
- .filter((layoutGroup) => layoutGroup.controls.length > 1);
613
- }
614
-
615
- function hasControlEntries(
616
- entries: readonly [string, ToolcraftControlSchema][],
617
- ): boolean {
618
- return entries.length > 0;
619
- }
620
-
621
- function isPanelActionsControl(control: ToolcraftControlSchema): boolean {
622
- return control.type === "panelActions";
623
- }
624
-
625
- function resolveSettingsTransfer({
626
- controls,
627
- persistence,
628
- settingsTransfer,
629
- }: {
630
- controls: ToolcraftControlsPanelSchema | undefined;
631
- persistence: ResolvedToolcraftAppSchema["persistence"];
632
- settingsTransfer: ToolcraftSettingsTransferSchema | undefined;
633
- }): ResolvedToolcraftSettingsTransferSchema {
634
- const mode = getSettingsTransferMode(settingsTransfer);
635
- const appId = getSettingsTransferAppId({
636
- controls,
637
- persistence,
638
- settingsTransfer,
639
- });
640
-
641
- return {
642
- appId,
643
- enabled: Boolean(controls),
644
- fileName: getSettingsTransferFileName({ appId, settingsTransfer }),
645
- mode,
646
- };
647
- }
648
-
649
- function createSettingsTransferSection(
650
- settingsTransfer: ResolvedToolcraftSettingsTransferSchema,
651
- ): ToolcraftControlSectionSchema {
652
- return {
653
- controls: {
654
- settingsTransfer: {
655
- label: false,
656
- target: settingsTransferTarget,
657
- type: "settingsTransfer",
658
- },
659
- },
660
- layout: "standalone",
661
- title: runtimeSetupSectionTitle,
662
- };
663
- }
664
-
665
- function getCanvasSizeLayoutGroups(
666
- sizeControlIds: readonly string[],
667
- ): ToolcraftControlSectionSchema["layoutGroups"] {
668
- return sizeControlIds.length > 1
669
- ? [{ columns: 2, controls: [...sizeControlIds], layout: "inline" }]
670
- : undefined;
671
- }
672
-
673
- function getGreatestCommonDivisor(left: number, right: number): number {
674
- let a = Math.abs(Math.round(left));
675
- let b = Math.abs(Math.round(right));
676
-
677
- while (b !== 0) {
678
- const next = b;
679
- b = a % b;
680
- a = next;
681
- }
682
-
683
- return a || 1;
684
- }
685
-
686
- function getCanvasAspectRatioDefaultValue(size: ToolcraftCanvasSize): {
687
- height: number;
688
- mode: "custom" | "preset";
689
- value: string;
690
- width: number;
691
- } {
692
- const divisor = getGreatestCommonDivisor(size.width, size.height);
693
- const width = Math.max(1, Math.round(size.width / divisor));
694
- const height = Math.max(1, Math.round(size.height / divisor));
695
- const value = `${width}:${height}`;
696
- const preset =
697
- getToolcraftCanvasAspectRatioPreset(value) ??
698
- getToolcraftCanvasAspectRatioPresetBySize(size);
699
-
700
- return {
701
- height: preset?.ratioHeight ?? height,
702
- mode: preset ? "preset" : "custom",
703
- value: preset?.value ?? value,
704
- width: preset?.ratioWidth ?? width,
705
- };
706
- }
707
-
708
- function mergeCanvasSizeControlsIntoSettingsTransferSection({
709
- aspectRatioControl,
710
- renderScaleControl,
711
- settingsTransferSection,
712
- sizeControlIds,
713
- sizeControls,
714
- timelineExtendedControl,
715
- }: {
716
- aspectRatioControl: ToolcraftControlSchema;
717
- renderScaleControl?: ToolcraftControlSchema;
718
- settingsTransferSection: ToolcraftControlSectionSchema;
719
- sizeControlIds: readonly string[];
720
- sizeControls: ToolcraftControlSectionSchema["controls"];
721
- timelineExtendedControl?: ToolcraftControlSchema;
722
- }): ToolcraftControlSectionSchema {
723
- const canvasSizeLayoutGroups = getCanvasSizeLayoutGroups(sizeControlIds) ?? [];
724
-
725
- return {
726
- ...settingsTransferSection,
727
- controls: {
728
- ...settingsTransferSection.controls,
729
- canvasAspectRatio: aspectRatioControl,
730
- ...sizeControls,
731
- ...(renderScaleControl ? { canvasRenderScale: renderScaleControl } : {}),
732
- ...(timelineExtendedControl ? { timelineExtended: timelineExtendedControl } : {}),
733
- },
734
- layoutGroups:
735
- canvasSizeLayoutGroups.length > 0 || settingsTransferSection.layoutGroups?.length
736
- ? [
737
- ...(settingsTransferSection.layoutGroups ?? []),
738
- ...canvasSizeLayoutGroups,
739
- ]
740
- : undefined,
741
- };
742
- }
743
-
744
- function mergeCanvasRenderScaleIntoSettingsTransferSection({
745
- renderScaleControl,
746
- settingsTransferSection,
747
- timelineExtendedControl,
748
- }: {
749
- renderScaleControl: ToolcraftControlSchema;
750
- settingsTransferSection: ToolcraftControlSectionSchema;
751
- timelineExtendedControl?: ToolcraftControlSchema;
752
- }): ToolcraftControlSectionSchema {
753
- return {
754
- ...settingsTransferSection,
755
- controls: {
756
- ...settingsTransferSection.controls,
757
- canvasRenderScale: renderScaleControl,
758
- ...(timelineExtendedControl ? { timelineExtended: timelineExtendedControl } : {}),
759
- },
760
- };
761
- }
762
-
763
- function mergeTimelineExtendedIntoSettingsTransferSection({
764
- settingsTransferSection,
765
- timelineExtendedControl,
766
- }: {
767
- settingsTransferSection: ToolcraftControlSectionSchema;
768
- timelineExtendedControl: ToolcraftControlSchema;
769
- }): ToolcraftControlSectionSchema {
770
- return {
771
- ...settingsTransferSection,
772
- controls: {
773
- ...settingsTransferSection.controls,
774
- timelineExtended: timelineExtendedControl,
775
- },
776
- };
777
- }
778
-
779
- function isPrimaryPanelAction(action: ToolcraftControlActionSchema): boolean {
780
- return typeof action !== "string" && action.variant !== "outline";
781
- }
782
-
783
- function orderPanelActions(
784
- actions: readonly ToolcraftControlActionSchema[],
785
- ): ToolcraftControlActionSchema[] {
786
- if (actions.length !== 2) {
787
- return [...actions];
788
- }
789
-
790
- return [...actions].sort(
791
- (left, right) => Number(isPrimaryPanelAction(left)) - Number(isPrimaryPanelAction(right)),
792
- );
793
- }
794
-
795
- function createMergedPanelActionsControl(
796
- entries: readonly [string, ToolcraftControlSchema][],
797
- ): ToolcraftControlSchema | null {
798
- const firstControl = entries[0]?.[1];
799
-
800
- if (!firstControl) {
801
- return null;
802
- }
803
-
804
- const actions = entries.flatMap(([, control]) => [...(control.actions ?? [])]);
805
-
806
- return {
807
- ...firstControl,
808
- actions: orderPanelActions(actions),
809
- target: firstControl.target || "panel.actions",
810
- type: "panelActions",
811
- };
812
- }
813
-
814
- function splitControlsPanelActionSections(
815
- sections: readonly ToolcraftControlSectionSchema[],
816
- ): {
817
- bodySections: ToolcraftControlSectionSchema[];
818
- stickyFooterSections: ToolcraftControlSectionSchema[];
819
- } {
820
- const bodySections: ToolcraftControlSectionSchema[] = [];
821
- const stickyFooterSections: ToolcraftControlSectionSchema[] = [];
822
- const stickyFooterActionEntries: [string, ToolcraftControlSchema][] = [];
823
-
824
- for (const section of sections) {
825
- if (section.actionGroup) {
826
- const entries = Object.entries(section.controls);
827
- const actionEntries = entries.filter(([, control]) => isPanelActionsControl(control));
828
- const passthroughEntries = entries.filter(([, control]) => !isPanelActionsControl(control));
829
-
830
- stickyFooterActionEntries.push(...actionEntries);
831
-
832
- if (hasControlEntries(passthroughEntries)) {
833
- stickyFooterSections.push({
834
- ...section,
835
- controls: createControlsRecord(passthroughEntries),
836
- });
837
- }
838
-
839
- continue;
840
- }
841
-
842
- const bodyEntries: [string, ToolcraftControlSchema][] = [];
843
- const actionEntries: [string, ToolcraftControlSchema][] = [];
844
-
845
- for (const entry of Object.entries(section.controls)) {
846
- const [, control] = entry;
847
-
848
- if (isPanelActionsControl(control)) {
849
- actionEntries.push(entry);
850
- } else {
851
- bodyEntries.push(entry);
852
- }
853
- }
854
-
855
- if (hasControlEntries(bodyEntries)) {
856
- const controlIds = new Set(bodyEntries.map(([id]) => id));
857
- const layoutGroups = filterLayoutGroupsForControlIds(section.layoutGroups, controlIds);
858
- const title = getBodySectionTitleAfterActionSplit(section.title);
859
-
860
- bodySections.push({
861
- ...section,
862
- controls: createControlsRecord(bodyEntries),
863
- layoutGroups: layoutGroups.length > 0 ? layoutGroups : undefined,
864
- title,
865
- });
866
- }
867
-
868
- if (hasControlEntries(actionEntries)) {
869
- stickyFooterActionEntries.push(...actionEntries);
870
- }
871
- }
872
-
873
- const mergedActionsControl = createMergedPanelActionsControl(stickyFooterActionEntries);
874
-
875
- if (mergedActionsControl) {
876
- stickyFooterSections.unshift({
877
- actionGroup: "secondary",
878
- controls: { footer: mergedActionsControl },
879
- layout: "standalone",
880
- title: "Export",
881
- });
882
- }
883
-
884
- return { bodySections, stickyFooterSections };
885
- }
886
-
887
- function getBodySectionTitleAfterActionSplit(
888
- title: ToolcraftControlSectionSchema["title"],
889
- ): ToolcraftControlSectionSchema["title"] {
890
- if (!title) {
891
- return title;
892
- }
893
-
894
- return isActionOrExportSectionTitle(title) ? undefined : title;
895
- }
896
-
897
- function isActionOrExportSectionTitle(title: string): boolean {
898
- const normalizedTitle = title.trim().toLowerCase();
899
-
900
- return normalizedTitle === "action" ||
901
- normalizedTitle === "actions" ||
902
- normalizedTitle === "export" ||
903
- normalizedTitle === "exports";
904
- }
905
-
906
- function isShortControlLabel(id: string, control: ToolcraftControlSchema): boolean {
907
- const label = typeof control.label === "string" ? control.label : id;
908
-
909
- return label.length <= maxAutoInlineControlLabelLength;
910
- }
911
-
912
- function isNumericTextControl(control: ToolcraftControlSchema): boolean {
913
- if (control.type !== "text") {
914
- return false;
915
- }
916
-
917
- if (typeof control.defaultValue === "number") {
918
- return Number.isFinite(control.defaultValue);
919
- }
920
-
921
- return (
922
- typeof control.defaultValue === "string" &&
923
- /^-?\d+(?:\.\d+)?(?:px|%|s)?$/u.test(control.defaultValue.trim())
924
- );
925
- }
926
-
927
- function isColorValueControl(control: ToolcraftControlSchema): boolean {
928
- return control.type === "color" || control.type === "colorOpacity";
929
- }
930
-
931
- function hasVisibleControlLabel(control: ToolcraftControlSchema): boolean {
932
- return typeof control.label === "string" && control.label.trim().length > 0;
933
- }
934
-
935
- function shouldAutoInlineMixedFieldControls(
936
- first: [string, ToolcraftControlSchema],
937
- second: [string, ToolcraftControlSchema],
938
- ): boolean {
939
- const [firstId, firstControl] = first;
940
- const [secondId, secondControl] = second;
941
- const isNumericColorPair =
942
- (isNumericTextControl(firstControl) && isColorValueControl(secondControl)) ||
943
- (isColorValueControl(firstControl) && isNumericTextControl(secondControl));
944
-
945
- return (
946
- isNumericColorPair &&
947
- hasVisibleControlLabel(firstControl) &&
948
- hasVisibleControlLabel(secondControl) &&
949
- isShortControlLabel(firstId, firstControl) &&
950
- isShortControlLabel(secondId, secondControl)
951
- );
952
- }
953
-
954
- function shouldAutoInlineControls(
955
- first: [string, ToolcraftControlSchema],
956
- second: [string, ToolcraftControlSchema],
957
- ): boolean {
958
- const [firstId, firstControl] = first;
959
- const [secondId, secondControl] = second;
960
-
961
- if (
962
- isNumericTextControl(firstControl) &&
963
- isNumericTextControl(secondControl) &&
964
- isShortControlLabel(firstId, firstControl) &&
965
- isShortControlLabel(secondId, secondControl)
966
- ) {
967
- return true;
968
- }
969
-
970
- return shouldAutoInlineMixedFieldControls(first, second);
971
- }
972
-
973
- function addAutoLayoutGroupsToSection(
974
- section: ToolcraftControlSectionSchema,
975
- ): ToolcraftControlSectionSchema {
976
- if (section.layout === "standalone" || section.actionGroup) {
977
- return section;
978
- }
979
-
980
- const entries = Object.entries(section.controls);
981
- const explicitLayoutGroups = section.layoutGroups ?? [];
982
- const groupedControlIds = new Set<string>();
983
-
984
- for (const layoutGroup of explicitLayoutGroups) {
985
- for (const controlId of layoutGroup.controls) {
986
- groupedControlIds.add(controlId);
987
- }
988
- }
989
-
990
- const autoLayoutGroups: ToolcraftControlLayoutGroupSchema[] = [];
991
-
992
- for (let index = 0; index < entries.length - 1; index += 1) {
993
- const firstEntry = entries[index];
994
- const secondEntry = entries[index + 1];
995
-
996
- if (!firstEntry || !secondEntry) {
997
- continue;
998
- }
999
-
1000
- const [firstId] = firstEntry;
1001
- const [secondId] = secondEntry;
1002
-
1003
- if (groupedControlIds.has(firstId) || groupedControlIds.has(secondId)) {
1004
- continue;
1005
- }
1006
-
1007
- if (!shouldAutoInlineControls(firstEntry, secondEntry)) {
1008
- continue;
1009
- }
1010
-
1011
- autoLayoutGroups.push({
1012
- columns: 2,
1013
- controls: [firstId, secondId],
1014
- layout: "inline",
1015
- });
1016
- groupedControlIds.add(firstId);
1017
- groupedControlIds.add(secondId);
1018
- index += 1;
1019
- }
1020
-
1021
- const layoutGroups = [...explicitLayoutGroups, ...autoLayoutGroups];
1022
-
1023
- return layoutGroups.length > 0
1024
- ? {
1025
- ...section,
1026
- layoutGroups,
1027
- }
1028
- : section;
1029
- }
1030
-
1031
- function getImplicitStandaloneSectionTitle(
1032
- entries: readonly [string, ToolcraftControlSchema][],
1033
- ): string {
1034
- const names = entries
1035
- .map(([id, control]) => getSectionTitlePart(id, control, entries))
1036
- .filter((name): name is string => Boolean(name));
1037
-
1038
- if (names.length === 1) {
1039
- return names[0];
1040
- }
1041
-
1042
- if (names.length === 2) {
1043
- return `${names[0]} & ${names[1]}`;
1044
- }
1045
-
1046
- return getCommonTargetSectionTitle(entries) ?? "Appearance";
1047
- }
1048
-
1049
- function getSectionTitlePart(
1050
- id: string,
1051
- control: ToolcraftControlSchema,
1052
- entries: readonly [string, ToolcraftControlSchema][],
1053
- ): string | undefined {
1054
- if (typeof control.label === "string" && control.label.trim()) {
1055
- const label = control.label.trim();
1056
-
1057
- if (!isGenericSectionTitle(label)) {
1058
- return label;
1059
- }
1060
- }
1061
-
1062
- const title = titleizeControlId(id);
1063
-
1064
- if (title && !isGenericSectionTitle(title)) {
1065
- return title;
1066
- }
1067
-
1068
- if (entries.length === 1) {
1069
- return getControlTypeSectionTitle(control);
1070
- }
1071
-
1072
- return undefined;
1073
- }
1074
-
1075
- function getControlTypeSectionTitle(
1076
- control: ToolcraftControlSchema,
1077
- ): string | undefined {
1078
- switch (control.type) {
1079
- case "channelMixer":
1080
- return "Channels";
1081
- case "curves":
1082
- return "Curves";
1083
- case "fontPicker":
1084
- return "Typography";
1085
- case "gradient":
1086
- return "Gradient";
1087
- case "palette":
1088
- return "Palette";
1089
- case "settingsTransfer":
1090
- return "Settings";
1091
- default:
1092
- return undefined;
1093
- }
1094
- }
1095
-
1096
- function getCommonTargetSectionTitle(
1097
- entries: readonly [string, ToolcraftControlSchema][],
1098
- ): string | undefined {
1099
- const targetPrefixes = new Set(
1100
- entries
1101
- .map(([, control]) => control.target.split(".")[0]?.trim())
1102
- .filter((prefix): prefix is string => Boolean(prefix)),
1103
- );
1104
-
1105
- if (targetPrefixes.size !== 1) {
1106
- return undefined;
1107
- }
1108
-
1109
- const [targetPrefix] = targetPrefixes;
1110
- const title = titlePrefixToSectionTitle(targetPrefix);
1111
-
1112
- return title && !isGenericSectionTitle(title) ? title : undefined;
1113
- }
1114
-
1115
- function titlePrefixToSectionTitle(prefix: string | undefined): string | undefined {
1116
- if (!prefix) {
1117
- return undefined;
1118
- }
1119
-
1120
- switch (prefix) {
1121
- case "canvas":
1122
- return runtimeSetupSectionTitle;
1123
- case "runtime":
1124
- return "Settings";
1125
- case "style":
1126
- return "Appearance";
1127
- default:
1128
- return titleizeControlId(prefix);
1129
- }
1130
- }
1131
-
1132
- function isGenericColorSectionTitle(title: string): boolean {
1133
- const normalizedTitle = title.trim().toLowerCase();
1134
-
1135
- return normalizedTitle === "color" || normalizedTitle === "colors";
1136
- }
1137
-
1138
- function isGenericSectionTitle(title: string): boolean {
1139
- const normalizedTitle = title.trim().toLowerCase();
1140
-
1141
- return (
1142
- normalizedTitle === "control" ||
1143
- normalizedTitle === "controls" ||
1144
- normalizedTitle === "setting" ||
1145
- normalizedTitle === "settings" ||
1146
- isGenericColorSectionTitle(title)
1147
- );
1148
- }
1149
-
1150
- function isColorOnlySectionEntries(
1151
- entries: readonly [string, ToolcraftControlSchema][],
1152
- ): boolean {
1153
- return entries.length > 0 && entries.every(([, control]) => control.type === "color");
1154
- }
1155
-
1156
- function titleizeControlId(id: string): string | undefined {
1157
- const title = id
1158
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
1159
- .replace(/[_-]+/g, " ")
1160
- .trim()
1161
- .replace(/\s+/g, " ");
1162
-
1163
- if (!title) {
1164
- return undefined;
1165
- }
1166
-
1167
- return title.replace(/\b\w/g, (letter) => letter.toUpperCase());
1168
- }
1169
-
1170
- function withImplicitStandaloneSectionTitle(
1171
- section: ToolcraftControlSectionSchema,
1172
- entries: readonly [string, ToolcraftControlSchema][],
1173
- ): ToolcraftControlSectionSchema {
1174
- if (section.title) {
1175
- if (isColorOnlySectionEntries(entries) && isGenericColorSectionTitle(section.title)) {
1176
- return { ...section, title: "Appearance" };
1177
- }
1178
-
1179
- return section;
1180
- }
1181
-
1182
- const title = getImplicitStandaloneSectionTitle(entries);
1183
-
1184
- return { ...section, title };
1185
- }
1186
-
1187
- function normalizeMixedSectionLayout(
1188
- section: ToolcraftControlSectionSchema,
1189
- ): ToolcraftControlSectionSchema[] {
1190
- const entries = Object.entries(section.controls);
1191
-
1192
- if (section.layout === "standalone") {
1193
- return [
1194
- addAutoLayoutGroupsToSection(
1195
- withImplicitStandaloneSectionTitle(section, entries),
1196
- ),
1197
- ];
1198
- }
1199
-
1200
- if (entries.length <= 1) {
1201
- return [addAutoLayoutGroupsToSection(withImplicitStandaloneSectionTitle(section, entries))];
1202
- }
1203
-
1204
- const layouts = entries.map(([, control]) => getControlSectionLayout(control, entries));
1205
- const uniqueLayouts = new Set(layouts);
1206
-
1207
- if (uniqueLayouts.size <= 1) {
1208
- return [addAutoLayoutGroupsToSection(withImplicitStandaloneSectionTitle(section, entries))];
1209
- }
1210
-
1211
- const normalizedSections: ToolcraftControlSectionSchema[] = [];
1212
- let currentLayout = layouts[0];
1213
- let currentEntries: [string, ToolcraftControlSchema][] = [];
1214
-
1215
- const pushCurrentSection = (): void => {
1216
- if (!currentLayout || currentEntries.length === 0) {
1217
- return;
1218
- }
1219
-
1220
- const controlIds = new Set(currentEntries.map(([id]) => id));
1221
-
1222
- if (currentLayout === "standalone") {
1223
- normalizedSections.push(
1224
- withImplicitStandaloneSectionTitle(
1225
- {
1226
- controls: createControlsRecord(currentEntries),
1227
- layout: "standalone",
1228
- },
1229
- currentEntries,
1230
- ),
1231
- );
1232
- } else {
1233
- normalizedSections.push(
1234
- addAutoLayoutGroupsToSection({
1235
- ...section,
1236
- controls: createControlsRecord(currentEntries),
1237
- layoutGroups: filterLayoutGroupsForControlIds(section.layoutGroups, controlIds),
1238
- }),
1239
- );
1240
- }
1241
- };
1242
-
1243
- for (const [index, entry] of entries.entries()) {
1244
- const layout = layouts[index] ?? "grouped";
1245
-
1246
- if (layout !== currentLayout) {
1247
- pushCurrentSection();
1248
- currentLayout = layout;
1249
- currentEntries = [];
1250
- }
1251
-
1252
- currentEntries.push(entry);
1253
- }
1254
-
1255
- pushCurrentSection();
1256
-
1257
- return normalizedSections;
1258
- }
1259
-
1260
- function normalizeControlsPanelLayout(
1261
- controls: ToolcraftControlsPanelSchema,
1262
- ): ToolcraftControlsPanelSchema {
1263
- const { bodySections, stickyFooterSections } = splitControlsPanelActionSections(
1264
- controls.sections,
1265
- );
1266
-
1267
- return {
1268
- ...controls,
1269
- sections: [
1270
- ...bodySections.flatMap(normalizeMixedSectionLayout),
1271
- ...stickyFooterSections.flatMap(normalizeMixedSectionLayout),
1272
- ],
1273
- };
1274
- }
1275
-
1276
- function normalizePanels({
1277
- canvas,
1278
- panels,
1279
- settingsTransfer,
1280
- }: {
1281
- canvas: ResolvedCanvas;
1282
- panels: ToolcraftAppSchema["panels"];
1283
- settingsTransfer: ResolvedToolcraftSettingsTransferSchema;
1284
- }): ResolvedToolcraftPanelsSchema {
1285
- const normalizedTimeline = resolveTimelinePanel(panels.timeline);
1286
- const normalizedPanels: ResolvedToolcraftPanelsSchema = {
1287
- ...(panels.controls ? { controls: panels.controls } : {}),
1288
- ...(panels.layers ? { layers: panels.layers } : {}),
1289
- ...(normalizedTimeline ? { timeline: normalizedTimeline } : {}),
1290
- };
1291
-
1292
- if (!panels.controls) {
1293
- return normalizedPanels;
1294
- }
1295
-
1296
- const controls = { ...panels.controls };
1297
- const settingsTransferSection = createSettingsTransferSection(settingsTransfer);
1298
- const renderScaleControl: ToolcraftControlSchema | undefined =
1299
- canvas.renderScale.enabled
1300
- ? {
1301
- defaultValue: canvas.renderScale.defaultValue,
1302
- description:
1303
- "Increases raster canvas backing resolution without changing the visible output size.",
1304
- label: "Resolution scale",
1305
- markerCount:
1306
- Math.floor(
1307
- (canvas.renderScale.max - canvas.renderScale.min) / canvas.renderScale.step,
1308
- ) + 1,
1309
- max: canvas.renderScale.max,
1310
- min: canvas.renderScale.min,
1311
- performanceReason:
1312
- "Resolution scale changes raster, Canvas, WebGL, or WebGPU backing pixels.",
1313
- performanceRole: "workload",
1314
- step: canvas.renderScale.step,
1315
- target: canvasRenderScaleTarget,
1316
- type: "slider",
1317
- variant: "discrete",
1318
- }
1319
- : undefined;
1320
- const timelineExtendedControl: ToolcraftControlSchema | undefined =
1321
- normalizedTimeline?.enabled
1322
- ? {
1323
- defaultValue: false,
1324
- description:
1325
- "Shows the extended runtime timeline with scrubber, duration, loop, and keyframe controls; compact mode keeps only Play visible.",
1326
- label: "Timeline",
1327
- target: toolcraftTimelinePanelExtendedTarget,
1328
- type: "switch",
1329
- }
1330
- : undefined;
1331
-
1332
- if (!canvas.enabled || canvas.sizing.mode !== "editable-output") {
1333
- const runtimeSetupSection = renderScaleControl
1334
- ? mergeCanvasRenderScaleIntoSettingsTransferSection({
1335
- renderScaleControl,
1336
- settingsTransferSection,
1337
- timelineExtendedControl,
1338
- })
1339
- : timelineExtendedControl
1340
- ? mergeTimelineExtendedIntoSettingsTransferSection({
1341
- settingsTransferSection,
1342
- timelineExtendedControl,
1343
- })
1344
- : settingsTransferSection;
1345
-
1346
- return {
1347
- ...normalizedPanels,
1348
- controls: normalizeControlsPanelLayout({
1349
- ...controls,
1350
- sections: [
1351
- runtimeSetupSection,
1352
- ...controls.sections,
1353
- ],
1354
- }),
1355
- };
1356
- }
1357
-
1358
- const sizeControls: ToolcraftControlSectionSchema["controls"] = {};
1359
- const sizeControlIds: string[] = [];
1360
- const aspectRatioControl: ToolcraftControlSchema = {
1361
- defaultValue: getCanvasAspectRatioDefaultValue(canvas.size),
1362
- label: "Aspect ratio",
1363
- orderRole: "input",
1364
- performanceReason: "Aspect ratio changes output dimensions and renderer workload.",
1365
- performanceRole: "workload",
1366
- target: canvasAspectRatioTarget,
1367
- type: "aspectRatio",
1368
- };
1369
-
1370
- sizeControls.canvasWidth = {
1371
- defaultValue: canvas.size.width,
1372
- label: "Canvas width",
1373
- orderRole: "input",
1374
- performanceReason: "Canvas width changes output dimensions and renderer workload.",
1375
- performanceRole: "workload",
1376
- target: canvasSizeControlTargets.width,
1377
- type: "text",
1378
- };
1379
- sizeControlIds.push("canvasWidth");
1380
-
1381
- sizeControls.canvasHeight = {
1382
- defaultValue: canvas.size.height,
1383
- label: "Canvas height",
1384
- orderRole: "input",
1385
- performanceReason: "Canvas height changes output dimensions and renderer workload.",
1386
- performanceRole: "workload",
1387
- target: canvasSizeControlTargets.height,
1388
- type: "text",
1389
- };
1390
- sizeControlIds.push("canvasHeight");
1391
-
1392
- const runtimeSettingsSection = mergeCanvasSizeControlsIntoSettingsTransferSection({
1393
- aspectRatioControl,
1394
- renderScaleControl,
1395
- settingsTransferSection,
1396
- sizeControlIds,
1397
- sizeControls,
1398
- timelineExtendedControl,
1399
- });
1400
-
1401
- return {
1402
- ...normalizedPanels,
1403
- controls: {
1404
- ...normalizeControlsPanelLayout({
1405
- ...controls,
1406
- sections: [
1407
- runtimeSettingsSection,
1408
- ...controls.sections,
1409
- ],
1410
- }),
1411
- },
1412
- };
1413
- }
1414
-
1415
- function resolveTimelinePanel(
1416
- timeline: ToolcraftTimelinePanelSchema | undefined,
1417
- ): ResolvedToolcraftTimelinePanelSchema | undefined {
1418
- if (timeline === true) {
1419
- return {
1420
- defaultDurationSeconds: defaultTimelineDurationSeconds,
1421
- enabled: true,
1422
- mode: "keyframes",
1423
- };
1424
- }
1425
-
1426
- if (!timeline || timeline.enabled === false) {
1427
- return undefined;
1428
- }
1429
-
1430
- return {
1431
- defaultDurationSeconds: normalizeTimelineDurationSeconds(timeline.defaultDurationSeconds),
1432
- enabled: true,
1433
- mode: timeline.mode ?? "keyframes",
1434
- };
1435
- }
1436
-
1437
- function normalizeTimelineDurationSeconds(value: unknown): number {
1438
- if (typeof value !== "number" || !Number.isFinite(value)) {
1439
- return defaultTimelineDurationSeconds;
1440
- }
1441
-
1442
- return Math.min(maxTimelineDurationSeconds, Math.max(minTimelineDurationSeconds, value));
1443
- }
1444
21
 
1445
22
  function hasVisibleRuntimePanel({
1446
23
  panels,
@@ -1485,10 +62,10 @@ function assertPanelPersistenceContract({
1485
62
  export function defineToolcraft(schema: ToolcraftAppSchema): ResolvedToolcraftAppSchema {
1486
63
  const canvasEnabled = schema.canvas.enabled;
1487
64
  const canvasSize = schema.canvas.size;
1488
- const canvasRenderScale = resolveCanvasRenderScale(schema.canvas.renderScale);
1489
- const canvasSizing = resolveCanvasSizing(schema.canvas);
1490
- const persistence = resolvePersistence(schema.persistence);
1491
- const settingsTransfer = resolveSettingsTransfer({
65
+ const canvasRenderScale = resolveToolcraftCanvasRenderScale(schema.canvas.renderScale);
66
+ const canvasSizing = resolveToolcraftCanvasSizing(schema.canvas);
67
+ const persistence = resolveToolcraftPersistence(schema.persistence);
68
+ const settingsTransfer = resolveToolcraftSettingsTransfer({
1492
69
  controls: schema.panels.controls,
1493
70
  persistence,
1494
71
  settingsTransfer: schema.settingsTransfer,
@@ -1497,29 +74,27 @@ export function defineToolcraft(schema: ToolcraftAppSchema): ResolvedToolcraftAp
1497
74
  ...schema.canvas,
1498
75
  draggable: canvasEnabled ? (schema.canvas.draggable ?? true) : false,
1499
76
  renderScale: canvasRenderScale,
1500
- size: canvasSize ?? defaultCanvasSize,
77
+ size: canvasSize ?? defaultToolcraftCanvasSize,
1501
78
  sizeSource: canvasSize ? ("app" as const) : ("runtime-default" as const),
1502
79
  sizing: canvasSizing,
1503
80
  upload: schema.canvas.upload ?? false,
1504
81
  };
1505
- const panels = normalizePanels({
82
+ const panels = normalizeToolcraftPanels({
1506
83
  canvas,
1507
84
  panels: schema.panels,
1508
85
  settingsTransfer,
1509
86
  });
1510
- const toolbar = {
1511
- history: schema.toolbar?.history ?? canvasEnabled,
1512
- radar: schema.toolbar?.radar ?? canvasEnabled,
1513
- theme: schema.toolbar?.theme ?? true,
1514
- zoom: schema.toolbar?.zoom ?? canvasEnabled,
1515
- };
1516
- const exportSchema = resolveExport(schema.export);
1517
- const media = resolveMedia(schema.media);
87
+ const toolbar = resolveToolcraftToolbar({
88
+ canvasEnabled,
89
+ toolbar: schema.toolbar,
90
+ });
91
+ const exportSchema = resolveToolcraftExport(schema.export);
92
+ const media = resolveToolcraftMedia(schema.media);
1518
93
 
1519
94
  assertPanelPersistenceContract({ panels, persistence, toolbar });
1520
95
 
1521
96
  return {
1522
- assembly: createToolcraftAssembly({
97
+ assembly: createToolcraftAssemblyContract({
1523
98
  canvas,
1524
99
  panels,
1525
100
  toolbar,