@pixel-point/toolcraft 0.0.17 → 0.0.19

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 (503) hide show
  1. package/package.json +1 -1
  2. package/src/generate-demand-only-lifecycle.test.mjs +9 -1
  3. package/src/generate-performance-iteration-execution.test.mjs +3 -3
  4. package/src/generate-test-delivery-architecture-assertions.mjs +11 -73
  5. package/src/generate-test-demand-only-lifecycle.mjs +34 -36
  6. package/src/generate-test-dependency-sandbox.mjs +4 -1
  7. package/src/generate-test-doc-assertions.mjs +81 -89
  8. package/src/generate-test-doc-assertions.test.mjs +44 -1
  9. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  10. package/src/generate-test-export-doc-assertions.mjs +53 -0
  11. package/src/generate-test-file-assertions.mjs +77 -83
  12. package/src/generate-test-integrity-assertions.mjs +5 -2
  13. package/src/generate-test-integrity-manifest-assertions.mjs +16 -5
  14. package/src/generate-test-integrity-ownership-assertions.mjs +36 -4
  15. package/src/generate-test-package-assertions.mjs +4 -0
  16. package/src/generate-test-product-fixture.mjs +1 -1
  17. package/src/generate-test-product-fixture.test.mjs +35 -38
  18. package/src/generate-test-product-iteration.mjs +0 -1
  19. package/src/generate-test-product-iteration.test.mjs +17 -1
  20. package/src/generate-test-runtime-boundary-assertions.mjs +178 -0
  21. package/src/generate-test-standalone-assertions.mjs +7 -13
  22. package/src/generate-test-standalone-delivery-stages.mjs +2 -86
  23. package/src/generate.test.mjs +4 -0
  24. package/src/generated-integrity-manifest.test.mjs +5 -2
  25. package/src/generated-source-policy.test.mjs +0 -4
  26. package/src/package-json.mjs +5 -0
  27. package/src/package-json.test.mjs +21 -0
  28. package/src/package-manager.mjs +2 -0
  29. package/src/package-manager.test.mjs +8 -0
  30. package/src/packaged-template-fallback-helpers.mjs +254 -0
  31. package/src/packaged-template-fallback.test.mjs +121 -46
  32. package/src/packaged-template-runtime-assertions.mjs +150 -0
  33. package/templates/runtime/contracts/component-contracts.choices.test.ts +64 -5
  34. package/templates/runtime/contracts/component-contracts.choices.ts +55 -5
  35. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  36. package/templates/runtime/contracts/component-contracts.inputs.ts +8 -7
  37. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  38. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  39. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +22 -8
  40. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  41. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
  42. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +4 -1
  43. package/templates/runtime/contracts/component-contracts.runtime.ts +19 -12
  44. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  45. package/templates/runtime/contracts/component-contracts.visual.test.ts +20 -0
  46. package/templates/runtime/contracts/component-contracts.visual.ts +4 -0
  47. package/templates/runtime/contracts/decision-contracts.test.ts +35 -11
  48. package/templates/runtime/contracts/decision-contracts.ts +12 -12
  49. package/templates/runtime/contracts/performance-verification-policy.test.ts +12 -12
  50. package/templates/runtime/contracts/performance-verification-policy.ts +8 -8
  51. package/templates/runtime/export/artifact-download.ts +1 -0
  52. package/templates/runtime/export/artifact-frame-renderer.ts +2 -15
  53. package/templates/runtime/export/artifact-frame-state.ts +16 -0
  54. package/templates/runtime/export/artifact-scene-frame.test.ts +3 -3
  55. package/templates/runtime/export/artifact-scene-frame.ts +3 -3
  56. package/templates/runtime/export/export-background.ts +0 -19
  57. package/templates/runtime/export/export-error.ts +5 -0
  58. package/templates/runtime/export/export-renderer-coverage.ts +54 -0
  59. package/templates/runtime/export/export.test.ts +45 -42
  60. package/templates/runtime/export/image-artifact-export.ts +2 -2
  61. package/templates/runtime/export/index.ts +57 -16
  62. package/templates/runtime/export/product-export-renderer.test.ts +38 -4
  63. package/templates/runtime/export/product-export-renderer.ts +0 -41
  64. package/templates/runtime/export/product-svg-export-renderer.ts +21 -0
  65. package/templates/runtime/export/svg-artifact-export.test.ts +98 -0
  66. package/templates/runtime/export/svg-artifact-export.ts +120 -0
  67. package/templates/runtime/export/svg-document.test.ts +221 -0
  68. package/templates/runtime/export/svg-document.ts +400 -0
  69. package/templates/runtime/export/svg-policy.ts +47 -0
  70. package/templates/runtime/index.ts +3 -0
  71. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  72. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +21 -1
  73. package/templates/runtime/react/app-shell/settings-transfer.test.ts +21 -0
  74. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +75 -1
  75. package/templates/runtime/react/app-shell/toolcraft-app.tsx +24 -9
  76. package/templates/runtime/react/canvas/canvas-shell-interactions.test.tsx +30 -0
  77. package/templates/runtime/react/canvas/canvas-shell-pinch-interactions.test.tsx +155 -0
  78. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +77 -1
  79. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  80. package/templates/runtime/react/canvas/canvas-shell.tsx +32 -6
  81. package/templates/runtime/react/canvas/canvas-viewport-geometry.ts +133 -0
  82. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  83. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  84. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  85. package/templates/runtime/react/canvas/use-canvas-viewport-interactions.ts +285 -36
  86. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  87. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  88. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-applicability-layout.test.tsx +134 -0
  89. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-layout.test.tsx +16 -1
  90. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  91. package/templates/runtime/react/controls-panel/__tests__/controls-panel.render-isolation.test.tsx +2 -0
  92. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +40 -1
  93. package/templates/runtime/react/controls-panel/__tests__/controls-panel.setup-controls.test.tsx +3 -3
  94. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +59 -0
  95. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  96. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-interaction.test.tsx +6 -6
  97. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +4 -6
  98. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +41 -6
  99. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  100. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  101. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +41 -10
  102. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +7 -4
  103. package/templates/runtime/react/controls-panel/renderers/controls-panel-action-renderer.tsx +2 -1
  104. package/templates/runtime/react/controls-panel/renderers/controls-panel-basic-renderers.tsx +2 -52
  105. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  106. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  107. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  108. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  109. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  110. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  111. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  112. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  113. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  114. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  115. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  116. package/templates/runtime/react/controls-panel/values/controls-panel-color-values.ts +1 -9
  117. package/templates/runtime/react/controls-panel/values/controls-panel-spatial-values.ts +7 -2
  118. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  119. package/templates/runtime/react/index.ts +4 -0
  120. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  121. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-math.ts +9 -69
  122. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  123. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  124. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  125. package/templates/runtime/scene/scene-bounds.ts +31 -17
  126. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  127. package/templates/runtime/schema/artifact-export-actions.test.ts +47 -0
  128. package/templates/runtime/schema/artifact-export-actions.ts +49 -0
  129. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  130. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  131. package/templates/runtime/schema/control-applicability.ts +433 -0
  132. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  133. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  134. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  135. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
  136. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  137. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  138. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  139. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  140. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  141. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  142. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  143. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  144. package/templates/runtime/schema/types.ts +69 -13
  145. package/templates/runtime/state/canvas-background-state.ts +1 -16
  146. package/templates/runtime/state/canvas-state.ts +32 -20
  147. package/templates/runtime/state/control-value-codecs.test.ts +98 -0
  148. package/templates/runtime/state/control-value-codecs.ts +700 -0
  149. package/templates/runtime/state/control-value-normalization.test.ts +1 -1
  150. package/templates/runtime/state/control-value-normalization.ts +127 -135
  151. package/templates/runtime/state/controls-reducer.ts +26 -3
  152. package/templates/runtime/state/create-template-state.test.ts +30 -0
  153. package/templates/runtime/state/create-template-state.ts +52 -31
  154. package/templates/runtime/state/orientation-pose.ts +80 -0
  155. package/templates/runtime/state/persistence-control-values.test.ts +59 -0
  156. package/templates/runtime/state/reducer-controls.test.ts +45 -0
  157. package/templates/runtime/state/reducer-media-reset.test.ts +1 -1
  158. package/templates/runtime/state/reducer-timeline.test.ts +70 -0
  159. package/templates/runtime/state/timeline-reducer.ts +40 -2
  160. package/templates/runtime/testing/performance-schema-queries.ts +2 -2
  161. package/templates/starter/AGENTS.md +29 -28
  162. package/templates/starter/docs/toolcraft/README.md +6 -6
  163. package/templates/starter/docs/toolcraft/acceptance-testing.md +34 -48
  164. package/templates/starter/docs/toolcraft/agent-worklog.md +103 -1
  165. package/templates/starter/docs/toolcraft/assembly-workflow.md +16 -8
  166. package/templates/starter/docs/toolcraft/component-rules.md +9 -7
  167. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  168. package/templates/starter/docs/toolcraft/core/layout.md +13 -8
  169. package/templates/starter/docs/toolcraft/core/media-upload.md +3 -1
  170. package/templates/starter/docs/toolcraft/core/performance.md +8 -10
  171. package/templates/starter/docs/toolcraft/core/reference-study.md +80 -19
  172. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +15 -3
  173. package/templates/starter/docs/toolcraft/core/setup-export.md +67 -11
  174. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  175. package/templates/starter/docs/toolcraft/decision-contract.md +4 -2
  176. package/templates/starter/docs/toolcraft/performance.md +8 -10
  177. package/templates/starter/docs/toolcraft/renderer-technique.md +6 -2
  178. package/templates/starter/docs/toolcraft/schema-reference.md +24 -22
  179. package/templates/starter/docs/toolcraft/workflow.md +14 -14
  180. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  181. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +45 -2
  182. package/templates/starter/e2e/app-browser-motion-reference-requirements.spec.ts +411 -0
  183. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  184. package/templates/starter/e2e/app-browser-proof-session.spec.ts +85 -0
  185. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +63 -33
  186. package/templates/starter/e2e/app-browser-selection-scope-evidence.spec.ts +137 -0
  187. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
  188. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  189. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  190. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
  191. package/templates/starter/e2e/browser-infinity-canvas-svg-evidence.ts +42 -0
  192. package/templates/starter/e2e/browser-layer-evidence-helpers.ts +15 -36
  193. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  194. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  195. package/templates/starter/e2e/browser-product-raster-snapshot.ts +213 -0
  196. package/templates/starter/e2e/browser-proof-session.ts +43 -15
  197. package/templates/starter/e2e/browser-proof-surface-action.ts +79 -0
  198. package/templates/starter/e2e/browser-proof-types.ts +15 -0
  199. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  200. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
  201. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +51 -32
  202. package/templates/starter/e2e/browser-selection-scope-evidence.ts +269 -0
  203. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +122 -0
  204. package/templates/starter/e2e/browser-svg-export-evidence.spec.ts +79 -0
  205. package/templates/starter/e2e/browser-svg-export-evidence.ts +152 -0
  206. package/templates/starter/e2e/export-artifact-helpers.ts +70 -0
  207. package/templates/starter/e2e/product-observable-helpers.ts +8 -129
  208. package/templates/starter/e2e/svg-artifact-inspection.spec.ts +136 -0
  209. package/templates/starter/e2e/svg-artifact-inspection.ts +331 -0
  210. package/templates/starter/e2e/svg-download-test-fixtures.ts +34 -0
  211. package/templates/starter/e2e/toolcraft-delivery-catalog-reporter.ts +198 -15
  212. package/templates/starter/e2e/toolcraft-feature-verification-reporter.spec.ts +149 -0
  213. package/templates/starter/e2e/toolcraft-feature-verification-reporter.ts +223 -0
  214. package/templates/starter/package.json +2 -0
  215. package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
  216. package/templates/starter/scripts/create-toolcraft-motion-reference-study.mjs +233 -0
  217. package/templates/starter/scripts/create-toolcraft-motion-reference-study.test.mjs +459 -0
  218. package/templates/starter/scripts/playwright-test-title-selection.d.mts +45 -0
  219. package/templates/starter/scripts/run-browser-performance-test-helpers.mjs +0 -27
  220. package/templates/starter/scripts/run-browser-performance.mjs +0 -6
  221. package/templates/starter/scripts/run-browser-performance.test.mjs +5 -13
  222. package/templates/starter/scripts/run-delivery-verification-test-helpers.mjs +0 -34
  223. package/templates/starter/scripts/run-delivery-verification.test.mjs +135 -31
  224. package/templates/starter/scripts/run-feature-verification.mjs +212 -0
  225. package/templates/starter/scripts/run-feature-verification.test.mjs +226 -0
  226. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.mjs +21 -8
  227. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.test.mjs +32 -92
  228. package/templates/starter/scripts/toolcraft-checkpoint-transaction.mjs +28 -6
  229. package/templates/starter/scripts/toolcraft-checkpoint-transaction.test.mjs +8 -63
  230. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  231. package/templates/starter/scripts/toolcraft-delivery-anchor.mjs +28 -8
  232. package/templates/starter/scripts/toolcraft-delivery-anchor.test.mjs +50 -61
  233. package/templates/starter/scripts/toolcraft-delivery-architecture-inventory.mjs +0 -4
  234. package/templates/starter/scripts/toolcraft-delivery-architecture-policy.mjs +0 -1
  235. package/templates/starter/scripts/toolcraft-delivery-evidence.test.mjs +2 -3
  236. package/templates/starter/scripts/toolcraft-delivery-executor.mjs +5 -1
  237. package/templates/starter/scripts/toolcraft-delivery-executor.test.mjs +5 -12
  238. package/templates/starter/scripts/toolcraft-delivery-functional-context.d.mts +1 -48
  239. package/templates/starter/scripts/toolcraft-delivery-functional-context.mjs +2 -30
  240. package/templates/starter/scripts/toolcraft-delivery-functional-context.test.mjs +4 -166
  241. package/templates/starter/scripts/toolcraft-delivery-functional-initial.test.mjs +4 -20
  242. package/templates/starter/scripts/toolcraft-delivery-lifecycle.mjs +43 -192
  243. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-lifecycle.test.mjs +79 -440
  244. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +102 -297
  245. package/templates/starter/scripts/toolcraft-delivery-plan-construction.mjs +46 -97
  246. package/templates/starter/scripts/toolcraft-delivery-plan-test-helpers.mjs +125 -0
  247. package/templates/starter/scripts/toolcraft-delivery-plan-validation.test.mjs +58 -0
  248. package/templates/starter/scripts/toolcraft-delivery-plan.d.mts +19 -31
  249. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +254 -252
  250. package/templates/starter/scripts/toolcraft-delivery-plan.test.mjs +29 -457
  251. package/templates/starter/scripts/toolcraft-delivery-receipt-performance.test.mjs +1 -1
  252. package/templates/starter/scripts/toolcraft-delivery-receipt-strictness.test.mjs +3 -72
  253. package/templates/starter/scripts/toolcraft-delivery-receipt-test-helpers.mjs +7 -36
  254. package/templates/starter/scripts/toolcraft-delivery-receipt.d.mts +3 -5
  255. package/templates/starter/scripts/toolcraft-delivery-receipt.mjs +15 -31
  256. package/templates/starter/scripts/toolcraft-delivery-receipt.test.mjs +28 -20
  257. package/templates/starter/scripts/toolcraft-delivery-receipt.types.test.mts +22 -30
  258. package/templates/starter/scripts/toolcraft-feature-verification-plan.d.mts +20 -0
  259. package/templates/starter/scripts/toolcraft-feature-verification-plan.mjs +146 -0
  260. package/templates/starter/scripts/toolcraft-feature-verification-plan.test.mjs +96 -0
  261. package/templates/starter/scripts/toolcraft-functional-proof-model.d.mts +3 -18
  262. package/templates/starter/scripts/toolcraft-functional-proof-model.mjs +39 -127
  263. package/templates/starter/scripts/toolcraft-functional-proof-model.test.mjs +25 -362
  264. package/templates/starter/scripts/toolcraft-functional-proof-model.types.test.mts +6 -83
  265. package/templates/starter/scripts/toolcraft-functional-proof-primitives.d.mts +11 -0
  266. package/templates/starter/scripts/toolcraft-functional-proof-primitives.mjs +61 -0
  267. package/templates/starter/scripts/toolcraft-functional-proof-primitives.test.mjs +43 -0
  268. package/templates/starter/scripts/toolcraft-integrity-policy.mjs +13 -0
  269. package/templates/starter/scripts/toolcraft-integrity-test-utils.mjs +1 -1
  270. package/templates/starter/scripts/toolcraft-layer-evidence-facade.test.mjs +58 -22
  271. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.mjs +111 -0
  272. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.test.mjs +116 -0
  273. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.d.mts +37 -0
  274. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.mjs +308 -0
  275. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.test.mjs +420 -0
  276. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.types.test.mts +50 -0
  277. package/templates/starter/scripts/toolcraft-motion-reference-command-options.mjs +195 -0
  278. package/templates/starter/scripts/toolcraft-motion-reference-command-options.test.mjs +224 -0
  279. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.mjs +122 -0
  280. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.test.mjs +153 -0
  281. package/templates/starter/scripts/toolcraft-motion-reference-evidence-assembly.mjs +270 -0
  282. package/templates/starter/scripts/toolcraft-motion-reference-evidence.test.mjs +432 -0
  283. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.mjs +275 -0
  284. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.test.mjs +294 -0
  285. package/templates/starter/scripts/toolcraft-motion-reference-group-inventory.mjs +148 -0
  286. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.mjs +101 -0
  287. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.test.mjs +124 -0
  288. package/templates/starter/scripts/toolcraft-motion-reference-image-source.mjs +259 -0
  289. package/templates/starter/scripts/toolcraft-motion-reference-image-source.test.mjs +338 -0
  290. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.mjs +96 -0
  291. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.test.mjs +81 -0
  292. package/templates/starter/scripts/toolcraft-motion-reference-lock-recovery.test.mjs +144 -0
  293. package/templates/starter/scripts/toolcraft-motion-reference-media-source.mjs +165 -0
  294. package/templates/starter/scripts/toolcraft-motion-reference-media-source.test.mjs +299 -0
  295. package/templates/starter/scripts/toolcraft-motion-reference-publication-discovery.test.mjs +142 -0
  296. package/templates/starter/scripts/toolcraft-motion-reference-publication-isolation.test.mjs +234 -0
  297. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-schema.mjs +122 -0
  298. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.mjs +89 -0
  299. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.test.mjs +74 -0
  300. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.mjs +264 -0
  301. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.test.mjs +377 -0
  302. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.mjs +254 -0
  303. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.test.mjs +130 -0
  304. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.mjs +128 -0
  305. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.test.mjs +209 -0
  306. package/templates/starter/scripts/toolcraft-motion-reference-publication.fixtures.mjs +88 -0
  307. package/templates/starter/scripts/toolcraft-motion-reference-publication.mjs +249 -0
  308. package/templates/starter/scripts/toolcraft-motion-reference-publication.test.mjs +433 -0
  309. package/templates/starter/scripts/toolcraft-motion-reference-review-output.mjs +38 -0
  310. package/templates/starter/scripts/toolcraft-motion-reference-review-output.test.mjs +29 -0
  311. package/templates/starter/scripts/toolcraft-motion-reference-root-safety.test.mjs +84 -0
  312. package/templates/starter/scripts/toolcraft-motion-reference-roots.mjs +83 -0
  313. package/templates/starter/scripts/toolcraft-motion-reference-sampling.mjs +283 -0
  314. package/templates/starter/scripts/toolcraft-motion-reference-sampling.test.mjs +480 -0
  315. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.mjs +39 -0
  316. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.test.mjs +59 -0
  317. package/templates/starter/scripts/toolcraft-motion-reference-stage.mjs +71 -0
  318. package/templates/starter/scripts/toolcraft-motion-reference-stage.test.mjs +61 -0
  319. package/templates/starter/scripts/toolcraft-motion-review-numeric.mjs +74 -0
  320. package/templates/starter/scripts/toolcraft-motion-review-numeric.test.mjs +65 -0
  321. package/templates/starter/scripts/toolcraft-motion-review-partitioning.mjs +268 -0
  322. package/templates/starter/scripts/toolcraft-motion-review-partitioning.test.mjs +329 -0
  323. package/templates/starter/scripts/toolcraft-motion-review-protected-units.mjs +120 -0
  324. package/templates/starter/scripts/toolcraft-motion-review-protected-units.test.mjs +111 -0
  325. package/templates/starter/scripts/toolcraft-performance-escalation-policy.test.mjs +77 -345
  326. package/templates/starter/scripts/toolcraft-performance-receipt-validation.test.mjs +19 -21
  327. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
  328. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  329. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
  330. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  331. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  332. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  333. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +16 -18
  334. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +200 -0
  335. package/templates/starter/scripts/toolcraft-product-observable-facade.test.mjs +46 -1
  336. package/templates/starter/scripts/toolcraft-receipt-boundary.test.mjs +0 -1
  337. package/templates/starter/scripts/toolcraft-source-inventory.d.mts +5 -0
  338. package/templates/starter/scripts/toolcraft-source-inventory.mjs +25 -14
  339. package/templates/starter/scripts/toolcraft-source-inventory.test.mjs +48 -0
  340. package/templates/starter/scripts/toolcraft-source-ownership.mjs +0 -1
  341. package/templates/starter/scripts/toolcraft-source-ownership.test.mjs +30 -5
  342. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  343. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  344. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
  345. package/templates/starter/scripts/toolcraft-verification-receipt-test-helpers.mjs +5 -26
  346. package/templates/starter/scripts/toolcraft-verification-receipt.d.mts +0 -7
  347. package/templates/starter/scripts/toolcraft-verification-receipt.mjs +3 -47
  348. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +6 -87
  349. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.d.mts +19 -0
  350. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.mjs +107 -0
  351. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.test.mjs +114 -0
  352. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.mjs +13 -2
  353. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.test.mjs +110 -0
  354. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +161 -0
  355. package/templates/starter/src/app/acceptance/canvas-coverage.ts +1 -0
  356. package/templates/starter/src/app/acceptance/color-bank-labels.ts +11 -15
  357. package/templates/starter/src/app/acceptance/color-row-grouping.ts +33 -0
  358. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  359. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  360. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -31
  361. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  362. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +4 -16
  363. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  364. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  365. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  366. package/templates/starter/src/app/acceptance/control-component-rules.ts +0 -10
  367. package/templates/starter/src/app/acceptance/control-labels.ts +30 -1
  368. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  369. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  370. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  371. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +21 -10
  372. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  373. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  374. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  375. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  376. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  377. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  378. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +25 -17
  379. package/templates/starter/src/app/acceptance/feature-verification-selection.test.ts +295 -0
  380. package/templates/starter/src/app/acceptance/feature-verification-selection.ts +161 -0
  381. package/templates/starter/src/app/acceptance/infinity-canvas.ts +10 -0
  382. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  383. package/templates/starter/src/app/acceptance/interaction-ownership-intent.ts +19 -1
  384. package/templates/starter/src/app/acceptance/interaction-ownership.ts +18 -2
  385. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  386. package/templates/starter/src/app/acceptance/motion-reference-evidence-validation.mjs +65 -0
  387. package/templates/starter/src/app/acceptance/motion-reference-evidence-windows.mjs +301 -0
  388. package/templates/starter/src/app/acceptance/motion-reference-evidence.d.mts +11 -0
  389. package/templates/starter/src/app/acceptance/motion-reference-evidence.mjs +145 -0
  390. package/templates/starter/src/app/acceptance/motion-reference-study-behaviors.ts +162 -0
  391. package/templates/starter/src/app/acceptance/motion-reference-study-coverage.ts +206 -0
  392. package/templates/starter/src/app/acceptance/motion-reference-study-identity.ts +129 -0
  393. package/templates/starter/src/app/acceptance/motion-reference-study-partitions.ts +186 -0
  394. package/templates/starter/src/app/acceptance/motion-reference-study.ts +90 -0
  395. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  396. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  397. package/templates/starter/src/app/acceptance/output-export-actions.test.ts +9 -1
  398. package/templates/starter/src/app/acceptance/output-export-actions.ts +14 -22
  399. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +22 -10
  400. package/templates/starter/src/app/acceptance/output-export-model.ts +58 -11
  401. package/templates/starter/src/app/acceptance/output-export.ts +25 -10
  402. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  403. package/templates/starter/src/app/acceptance/reference-study-types.ts +167 -17
  404. package/templates/starter/src/app/acceptance/runtime-coverage.ts +6 -1
  405. package/templates/starter/src/app/acceptance/selection-scope.test.ts +200 -0
  406. package/templates/starter/src/app/acceptance/selection-scope.ts +151 -0
  407. package/templates/starter/src/app/acceptance/transfer-mode-types.ts +114 -0
  408. package/templates/starter/src/app/acceptance/types.ts +65 -125
  409. package/templates/starter/src/app/acceptance/validate-coverage.ts +33 -5
  410. package/templates/starter/src/app/starter-acceptance-data.ts +1 -0
  411. package/templates/starter/src/app/starter-acceptance.animation-intent.test.ts +3 -0
  412. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +312 -0
  413. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +35 -0
  414. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +18 -1
  415. package/templates/starter/src/app/starter-acceptance.color-row-grouping.test.ts +118 -0
  416. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +66 -1
  417. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  418. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +113 -16
  419. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +225 -0
  420. package/templates/starter/src/app/starter-acceptance.control-label-context.test.ts +1 -0
  421. package/templates/starter/src/app/starter-acceptance.control-labels.test.ts +78 -3
  422. package/templates/starter/src/app/starter-acceptance.control-naming-rules.test.ts +77 -1
  423. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  424. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  425. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  426. package/templates/starter/src/app/starter-acceptance.custom-control-fit-check.test.ts +1 -1
  427. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  428. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +74 -3
  429. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +26 -0
  430. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +39 -9
  431. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +31 -3
  432. package/templates/starter/src/app/starter-acceptance.layers.test.ts +2 -1
  433. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  434. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +56 -0
  435. package/templates/starter/src/app/starter-acceptance.motion-reference-evidence.test.ts +17 -0
  436. package/templates/starter/src/app/starter-acceptance.motion-reference-identity.test.ts +163 -0
  437. package/templates/starter/src/app/starter-acceptance.motion-reference-mapping.test.ts +133 -0
  438. package/templates/starter/src/app/starter-acceptance.motion-reference-ownership.test.ts +121 -0
  439. package/templates/starter/src/app/starter-acceptance.motion-reference-partitions.test.ts +210 -0
  440. package/templates/starter/src/app/starter-acceptance.motion-reference-test-utils.ts +313 -0
  441. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +458 -0
  442. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
  443. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +116 -104
  444. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +29 -0
  445. package/templates/starter/src/app/starter-acceptance.product-worklog.test.ts +123 -2
  446. package/templates/starter/src/app/starter-acceptance.reference-custom-timeline.test.ts +2 -0
  447. package/templates/starter/src/app/starter-acceptance.reference-foundation.test.ts +8 -0
  448. package/templates/starter/src/app/starter-acceptance.reference-timeline.test.ts +7 -0
  449. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +3 -1
  450. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  451. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  452. package/templates/starter/src/app/starter-acceptance.timeline-playback.test.ts +5 -0
  453. package/templates/starter/src/app/starter-acceptance.ts +45 -10
  454. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +338 -0
  455. package/templates/starter/src/app/starter-acceptance.video-reference-study.test.ts +34 -236
  456. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +12 -0
  457. package/templates/starter/src/app/starter-acceptance.worklog-test-utils.ts +122 -6
  458. package/templates/starter/src/app/starter-acceptance.worklog.test.ts +152 -16
  459. package/templates/starter/src/app/starter-performance.gates.test.ts +20 -18
  460. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +22 -6
  461. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +5 -2
  462. package/templates/starter/src/routes/index.tsx +1 -0
  463. package/templates/starter/tsconfig.json +1 -0
  464. package/templates/ui/components/composites/tabs.tsx +2 -2
  465. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  466. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  467. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  468. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  469. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  470. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  471. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  472. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  473. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  474. package/templates/ui/components/controls/index.ts +3 -0
  475. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  476. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  477. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  478. package/templates/starter/scripts/toolcraft-browser-impact-selection.mjs +0 -26
  479. package/templates/starter/scripts/toolcraft-delivery-framework-refresh.test.mjs +0 -141
  480. package/templates/starter/scripts/toolcraft-delivery-functional-changed.test.mjs +0 -468
  481. package/templates/starter/scripts/toolcraft-delivery-functional-model-lifecycle.test.mjs +0 -254
  482. package/templates/starter/scripts/toolcraft-delivery-functional-semantic-regression.test.mjs +0 -135
  483. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-durability.test.mjs +0 -329
  484. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +0 -430
  485. package/templates/starter/scripts/toolcraft-delivery-plan-functional-model.test.mjs +0 -358
  486. package/templates/starter/scripts/toolcraft-functional-proof-regression-fixtures.mjs +0 -298
  487. package/templates/starter/scripts/toolcraft-verification-impact-inventory.d.mts +0 -62
  488. package/templates/starter/scripts/toolcraft-verification-impact-inventory.mjs +0 -344
  489. package/templates/starter/scripts/toolcraft-verification-impact-inventory.test.mjs +0 -204
  490. package/templates/starter/scripts/toolcraft-verification-impact-performance-candidates.test.mjs +0 -210
  491. package/templates/starter/scripts/toolcraft-verification-impact-resolution.mjs +0 -344
  492. package/templates/starter/scripts/toolcraft-verification-impact-resolution.test.mjs +0 -270
  493. package/templates/starter/scripts/toolcraft-verification-impact-semantic-resolution.test.mjs +0 -449
  494. package/templates/starter/scripts/toolcraft-verification-impact-support-resolution.test.mjs +0 -195
  495. package/templates/starter/scripts/toolcraft-verification-impact.d.mts +0 -132
  496. package/templates/starter/scripts/toolcraft-verification-impact.mjs +0 -169
  497. package/templates/starter/scripts/toolcraft-verification-impact.test.mjs +0 -300
  498. package/templates/starter/scripts/toolcraft-verification-input-roles.mjs +0 -197
  499. package/templates/starter/scripts/toolcraft-verification-input-roles.test.mjs +0 -225
  500. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  501. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
  502. package/templates/starter/src/app/acceptance/video-reference-study.ts +0 -217
  503. package/templates/starter/src/app/starter-verification-impact.json +0 -15
@@ -1,6 +1,9 @@
1
1
  import type {
2
2
  ToolcraftControlConditionSchema,
3
- ToolcraftControlSchema,
3
+ ResolvedToolcraftControlSchema,
4
+ } from "@repo/toolcraft-runtime";
5
+ import {
6
+ getToolcraftApplicabilityPredicates,
4
7
  } from "@repo/toolcraft-runtime";
5
8
 
6
9
  import type { ToolcraftControlLayoutFacts } from "./control-layout-model";
@@ -17,7 +20,7 @@ function normalizeToolcraftConditionValue(value: unknown): string | null {
17
20
 
18
21
  function getToolcraftConditionBranchTexts(
19
22
  condition: ToolcraftControlConditionSchema,
20
- gateControl: ToolcraftControlSchema,
23
+ gateControl: ResolvedToolcraftControlSchema,
21
24
  ): string[] {
22
25
  const rawValues =
23
26
  "equals" in condition
@@ -44,7 +47,7 @@ function sectionTitleLooksLikeDependencyBranch({
44
47
  sectionTitle,
45
48
  }: {
46
49
  condition: ToolcraftControlConditionSchema;
47
- gateControl: ToolcraftControlSchema;
50
+ gateControl: ResolvedToolcraftControlSchema;
48
51
  sectionTitle: string | undefined;
49
52
  }): boolean {
50
53
  const sectionText = normalizeToolcraftSemanticText(sectionTitle);
@@ -68,10 +71,18 @@ export function getToolcraftControlDependencyGroupingErrors(
68
71
  const errors: string[] = [];
69
72
 
70
73
  for (const item of facts.visibleControls) {
71
- for (const [conditionName, condition] of [
72
- ["visibleWhen", item.control.visibleWhen],
73
- ["disabledWhen", item.control.disabledWhen],
74
- ] as const) {
74
+ const conditions = [
75
+ ...getToolcraftApplicabilityPredicates(
76
+ item.control.applicability,
77
+ ).map(
78
+ (condition) => ["applicability", condition] as const,
79
+ ),
80
+ ...(item.control.disabledWhen
81
+ ? [["disabledWhen", item.control.disabledWhen] as const]
82
+ : []),
83
+ ];
84
+
85
+ for (const [conditionName, condition] of conditions) {
75
86
  if (!condition || !item.loosePrefix) {
76
87
  continue;
77
88
  }
@@ -86,7 +97,7 @@ export function getToolcraftControlDependencyGroupingErrors(
86
97
  gateControl.loosePrefix !== null &&
87
98
  gateControl.loosePrefix === item.loosePrefix;
88
99
  const looksLikeBranchSection =
89
- conditionName === "visibleWhen" &&
100
+ conditionName === "applicability" &&
90
101
  sectionTitleLooksLikeDependencyBranch({
91
102
  condition,
92
103
  gateControl: gateControl.control,
@@ -98,7 +109,7 @@ export function getToolcraftControlDependencyGroupingErrors(
98
109
  }
99
110
 
100
111
  errors.push(
101
- `${item.sectionLabel} / ${item.controlId} is gated by ${conditionName} target "${condition.target}" in ${gateControl.sectionLabel}, but it belongs to the same dependency group. Keep selectors and their dependent controls in one semantic section when they describe one product entity or branch; use visibleWhen for branch-specific controls inside that section instead of splitting branch controls into their own section. Do not use disabledWhen for product controls.`,
112
+ `${item.sectionLabel} / ${item.controlId} is gated by ${conditionName} target "${condition.target}" in ${gateControl.sectionLabel}, but it belongs to the same dependency group. Keep selectors and their dependent controls in one semantic section when they describe one product entity or branch; use conditional applicability for branch-specific controls inside that section instead of splitting branch controls into their own section. Do not use disabledWhen for product controls.`,
102
113
  );
103
114
  }
104
115
  }
@@ -2,10 +2,8 @@ import {
2
2
  getToolcraftFontPickerOwnedTypographyPart,
3
3
  } from "./control-labels";
4
4
  import type { ToolcraftControlLayoutFacts } from "./control-layout-model";
5
- import {
6
- getToolcraftSectionInventoryById,
7
- hasToolcraftInventorySplitEvidence,
8
- } from "./control-section-inventory";
5
+ import { hasToolcraftInventoryEntityAgreement } from "./control-section-entity-cohesion";
6
+ import { getToolcraftSectionInventoryById } from "./control-section-inventory";
9
7
  import { getControlLabelText } from "./controls";
10
8
  import type { ToolcraftControlSectionInventoryEntry } from "./types";
11
9
 
@@ -60,13 +58,13 @@ function getToolcraftProductEntitySplitErrors({
60
58
  for (const [prefix, sections] of facts.strictPrefixSections) {
61
59
  if (
62
60
  sections.size > 1 &&
63
- !hasToolcraftInventorySplitEvidence({ sectionInventoryById, sections })
61
+ !hasToolcraftInventoryEntityAgreement({ sectionInventoryById, sections })
64
62
  ) {
65
63
  const sectionLabels = [...sections].map(
66
64
  (sectionId) => facts.sectionLabelsById.get(sectionId) ?? sectionId,
67
65
  );
68
66
  errors.push(
69
- `Controls for product entity "${prefix}" are split across sections: ${sectionLabels.join(", ")}. Keep controls for the same product entity in one semantic section unless the Control Section Inventory declares workflowStage and splitReason for every split section.`,
67
+ `Controls for target family "${prefix}" are split across sections: ${sectionLabels.join(", ")}. Assign one shared entityId when the sections edit one logical entity; the entity cohesion validator decides whether that split is allowed.`,
70
68
  );
71
69
  }
72
70
  }
@@ -1,6 +1,6 @@
1
1
  import type {
2
2
  ResolvedToolcraftAppSchema,
3
- ToolcraftControlSchema,
3
+ ResolvedToolcraftControlSchema,
4
4
  } from "@repo/toolcraft-runtime";
5
5
 
6
6
  import { isToolcraftProductSectionControl } from "./controls";
@@ -12,7 +12,7 @@ import {
12
12
  } from "./semantic";
13
13
 
14
14
  export type ToolcraftControlLayoutControlEntry = {
15
- control: ToolcraftControlSchema;
15
+ control: ResolvedToolcraftControlSchema;
16
16
  controlId: string;
17
17
  loosePrefix: string | null;
18
18
  sectionLabel: string;
@@ -21,7 +21,7 @@ export type ToolcraftControlLayoutControlEntry = {
21
21
  };
22
22
 
23
23
  export type ToolcraftControlLayoutSectionFact = {
24
- controls: readonly [string, ToolcraftControlSchema][];
24
+ controls: readonly [string, ResolvedToolcraftControlSchema][];
25
25
  sectionLabel: string;
26
26
  sectionId: string;
27
27
  sectionLoosePrefixes: ReadonlySet<string>;
@@ -41,7 +41,7 @@ export type ToolcraftControlLayoutFacts = {
41
41
  };
42
42
 
43
43
  function getToolcraftControlSemanticCluster(
44
- control: ToolcraftControlSchema,
44
+ control: ResolvedToolcraftControlSchema,
45
45
  ): string {
46
46
  return (
47
47
  control.semanticGroup?.trim() ||
@@ -2,11 +2,14 @@ import {
2
2
  getToolcraftColorBankDescriptionError,
3
3
  getToolcraftColorBankLabelErrors,
4
4
  } from "./color-bank-labels";
5
+ import { getToolcraftColorRowGroupingErrors } from "./color-row-grouping";
5
6
  import {
6
7
  getToolcraftControlDescriptionError,
8
+ getToolcraftDuplicateSectionTitleLabelError,
7
9
  getToolcraftGenericControlLabelError,
8
10
  } from "./control-labels";
9
11
  import type { ToolcraftControlLayoutFacts } from "./control-layout-model";
12
+ import { TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS } from "./control-section-entity-cohesion";
10
13
  import {
11
14
  controlTypeSectionTitlePattern,
12
15
  genericControlSectionTitlePattern,
@@ -15,9 +18,6 @@ import {
15
18
  const broadControlSectionTitlePattern =
16
19
  /^(animation|export|flow|icon|logo|motion|output|scene|shape|shapes|text|typography|visual|visuals)$/i;
17
20
 
18
- const maxPreferredControlsPerSection = 7;
19
- const maxHardControlsPerSection = 10;
20
-
21
21
  export function getToolcraftControlLayoutSectionInvariantErrors(
22
22
  facts: ToolcraftControlLayoutFacts,
23
23
  ): string[] {
@@ -55,7 +55,7 @@ export function getToolcraftControlLayoutSectionInvariantErrors(
55
55
  .map(([controlId]) => controlId);
56
56
 
57
57
  if (
58
- controls.length > maxPreferredControlsPerSection &&
58
+ controls.length >= TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS &&
59
59
  missingSemanticGroups.length > 0
60
60
  ) {
61
61
  errors.push(
@@ -63,6 +63,10 @@ export function getToolcraftControlLayoutSectionInvariantErrors(
63
63
  );
64
64
  }
65
65
  errors.push(
66
+ ...getToolcraftColorRowGroupingErrors({
67
+ controls,
68
+ sectionLabel,
69
+ }),
66
70
  ...getToolcraftColorBankLabelErrors({
67
71
  controls,
68
72
  sectionLabel,
@@ -71,6 +75,18 @@ export function getToolcraftControlLayoutSectionInvariantErrors(
71
75
  );
72
76
 
73
77
  for (const [controlId, control] of controls) {
78
+ const duplicateSectionTitleLabelError =
79
+ getToolcraftDuplicateSectionTitleLabelError({
80
+ control,
81
+ controlId,
82
+ sectionLabel,
83
+ sectionTitle,
84
+ });
85
+
86
+ if (duplicateSectionTitleLabelError) {
87
+ errors.push(duplicateSectionTitleLabelError);
88
+ }
89
+
74
90
  const genericLabelError = getToolcraftGenericControlLabelError({
75
91
  control,
76
92
  controlId,
@@ -135,7 +151,7 @@ export function getToolcraftControlLayoutSectionHeuristicErrors(
135
151
  sectionTitle !== undefined && broadControlSectionTitlePattern.test(sectionTitle);
136
152
 
137
153
  if (
138
- controls.length > maxPreferredControlsPerSection &&
154
+ controls.length >= TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS &&
139
155
  hasBroadSectionTitle &&
140
156
  semanticClusters.size >= 3
141
157
  ) {
@@ -144,11 +160,6 @@ export function getToolcraftControlLayoutSectionHeuristicErrors(
144
160
  );
145
161
  }
146
162
 
147
- if (controls.length > maxHardControlsPerSection && semanticClusters.size > 1) {
148
- errors.push(
149
- `${sectionLabel} has ${controls.length} controls across ${semanticClusters.size} semantic clusters (${clusterList}). Controls-panel sections should stay discrete; split sections that grow past ${maxHardControlsPerSection} controls unless every control edits one tightly scoped entity.`,
150
- );
151
- }
152
163
  }
153
164
 
154
165
  return errors;
@@ -10,6 +10,12 @@ export function getRequiredToolcraftControlPartCoverage(
10
10
  return ["anchorGrid.position"];
11
11
  case "channelMixer":
12
12
  return ["channelMixer.activeChannel", "channelMixer.values"];
13
+ case "collectionActions":
14
+ return [
15
+ "collectionActions.add",
16
+ "collectionActions.remove",
17
+ "collectionActions.items",
18
+ ];
13
19
  case "curves":
14
20
  return control.variant === "single"
15
21
  ? ["curves.points"]
@@ -39,6 +45,8 @@ export function getRequiredToolcraftControlPartCoverage(
39
45
  return ["rangeInput.start", "rangeInput.end"];
40
46
  case "rangeSlider":
41
47
  return ["rangeSlider.lower", "rangeSlider.upper"];
48
+ case "sourceCollection":
49
+ return ["sourceCollection.items"];
42
50
  case "vector":
43
51
  return ["vector.x", "vector.y"];
44
52
  default:
@@ -0,0 +1,215 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ getToolcraftControlSectionEntityCohesionErrors,
5
+ hasToolcraftInventoryEntityAgreement,
6
+ } from "./control-section-entity-cohesion";
7
+ import type { ToolcraftControlSectionInventoryEntry } from "./types";
8
+
9
+ function makeSection({
10
+ count,
11
+ entity = "Renderable object",
12
+ entityId = "renderable-object",
13
+ id = "object",
14
+ splitReason,
15
+ workflowStage,
16
+ }: {
17
+ count: number;
18
+ entity?: string;
19
+ entityId?: string;
20
+ id?: string;
21
+ splitReason?: string;
22
+ workflowStage?: string;
23
+ }): ToolcraftControlSectionInventoryEntry {
24
+ return {
25
+ entity,
26
+ entityId,
27
+ groupingReason: `The ${id} controls edit one coherent part of the entity.`,
28
+ id,
29
+ splitReason,
30
+ targets: Array.from(
31
+ { length: count },
32
+ (_, index) => `${id}.value${index + 1}`,
33
+ ),
34
+ title: id,
35
+ workflowStage,
36
+ };
37
+ }
38
+
39
+ describe("Toolcraft control section entity cohesion", () => {
40
+ it.each([1, 7, 8, 10])(
41
+ "accepts one entity in one section with %s controls",
42
+ (count) => {
43
+ expect(
44
+ getToolcraftControlSectionEntityCohesionErrors([
45
+ makeSection({ count }),
46
+ ]),
47
+ ).toEqual([]);
48
+ },
49
+ );
50
+
51
+ it("rejects splitting an entity that fits in one section", () => {
52
+ const errors = getToolcraftControlSectionEntityCohesionErrors([
53
+ makeSection({ count: 3, id: "object-source" }),
54
+ makeSection({ count: 4, id: "object-style" }),
55
+ ]);
56
+
57
+ expect(errors).toContain(
58
+ 'Control Section Inventory entity "renderable-object" owns 7 controls across 2 sections (object-source, object-style). Entities with 10 or fewer controls must stay in one section.',
59
+ );
60
+ });
61
+
62
+ it("rejects an entity above the hard maximum left in one section", () => {
63
+ expect(
64
+ getToolcraftControlSectionEntityCohesionErrors([
65
+ makeSection({ count: 11 }),
66
+ ]),
67
+ ).toContain(
68
+ 'Control Section Inventory entity "renderable-object" owns 11 controls in section object. Split entities above 10 controls into explicit workflow stages.',
69
+ );
70
+ });
71
+
72
+ it("accepts a balanced large-entity workflow split", () => {
73
+ expect(
74
+ getToolcraftControlSectionEntityCohesionErrors([
75
+ makeSection({
76
+ count: 5,
77
+ id: "object-structure",
78
+ splitReason: "Structure is authored before the finishing workflow.",
79
+ workflowStage: "structure",
80
+ }),
81
+ makeSection({
82
+ count: 6,
83
+ id: "object-finish",
84
+ splitReason: "Finish is authored after the structural workflow.",
85
+ workflowStage: "finish",
86
+ }),
87
+ ]),
88
+ ).toEqual([]);
89
+ });
90
+
91
+ it("rejects a one-control tail in a split entity", () => {
92
+ expect(
93
+ getToolcraftControlSectionEntityCohesionErrors([
94
+ makeSection({
95
+ count: 10,
96
+ id: "object-primary",
97
+ splitReason: "Primary editing precedes the final workflow stage.",
98
+ workflowStage: "primary",
99
+ }),
100
+ makeSection({
101
+ count: 1,
102
+ id: "object-final",
103
+ splitReason: "The final workflow follows primary editing.",
104
+ workflowStage: "final",
105
+ }),
106
+ ]),
107
+ ).toContain(
108
+ 'Control Section Inventory entity "renderable-object" section object-final owns 1 control. Every section in a split entity must own between 2 and 10 controls.',
109
+ );
110
+ });
111
+
112
+ it("rejects a split section above the per-section maximum", () => {
113
+ expect(
114
+ getToolcraftControlSectionEntityCohesionErrors([
115
+ makeSection({
116
+ count: 11,
117
+ id: "object-primary",
118
+ splitReason: "Primary editing precedes the final workflow stage.",
119
+ workflowStage: "primary",
120
+ }),
121
+ makeSection({
122
+ count: 2,
123
+ id: "object-final",
124
+ splitReason: "The final workflow follows primary editing.",
125
+ workflowStage: "final",
126
+ }),
127
+ ]),
128
+ ).toContain(
129
+ 'Control Section Inventory entity "renderable-object" section object-primary owns 11 controls. Every section in a split entity must own between 2 and 10 controls.',
130
+ );
131
+ });
132
+
133
+ it("requires a workflow stage on every section of a split entity", () => {
134
+ expect(
135
+ getToolcraftControlSectionEntityCohesionErrors([
136
+ makeSection({
137
+ count: 6,
138
+ id: "object-primary",
139
+ splitReason: "Primary editing precedes the final workflow stage.",
140
+ }),
141
+ makeSection({
142
+ count: 6,
143
+ id: "object-final",
144
+ splitReason: "The final workflow follows primary editing.",
145
+ workflowStage: "final",
146
+ }),
147
+ ]),
148
+ ).toContain(
149
+ 'Control Section Inventory entity "renderable-object" section object-primary must declare workflowStage because the entity is split across sections.',
150
+ );
151
+ });
152
+
153
+ it("requires complete, unique split evidence and a consistent entity name", () => {
154
+ const errors = getToolcraftControlSectionEntityCohesionErrors([
155
+ makeSection({ count: 6, id: "object-a", workflowStage: "edit" }),
156
+ makeSection({
157
+ count: 6,
158
+ entity: "Different label",
159
+ id: "object-b",
160
+ splitReason: "This section follows the first editing stage.",
161
+ workflowStage: "edit",
162
+ }),
163
+ ]);
164
+
165
+ expect(errors).toEqual(
166
+ expect.arrayContaining([
167
+ expect.stringContaining("uses inconsistent entity names"),
168
+ expect.stringContaining("object-a must declare splitReason"),
169
+ expect.stringContaining('repeats workflowStage "edit"'),
170
+ ]),
171
+ );
172
+ });
173
+
174
+ it("keeps independent entities independent", () => {
175
+ expect(
176
+ getToolcraftControlSectionEntityCohesionErrors([
177
+ makeSection({ count: 4, entityId: "entity-a", id: "entity-a" }),
178
+ makeSection({ count: 4, entityId: "entity-b", id: "entity-b" }),
179
+ ]),
180
+ ).toEqual([]);
181
+ });
182
+
183
+ it("requires one shared entityId for target-prefix corroboration", () => {
184
+ const first = makeSection({ count: 6, id: "first" });
185
+ const second = makeSection({
186
+ count: 6,
187
+ entityId: "different-entity",
188
+ id: "second",
189
+ });
190
+ const sectionInventoryById = new Map([
191
+ [first.id, first],
192
+ [second.id, second],
193
+ ]);
194
+ const sections = new Set([first.id, second.id]);
195
+
196
+ expect(
197
+ hasToolcraftInventoryEntityAgreement({
198
+ sectionInventoryById,
199
+ sections,
200
+ }),
201
+ ).toBe(false);
202
+
203
+ sectionInventoryById.set(second.id, {
204
+ ...second,
205
+ entityId: first.entityId,
206
+ });
207
+
208
+ expect(
209
+ hasToolcraftInventoryEntityAgreement({
210
+ sectionInventoryById,
211
+ sections,
212
+ }),
213
+ ).toBe(true);
214
+ });
215
+ });
@@ -0,0 +1,132 @@
1
+ import type { ToolcraftControlSectionInventoryEntry } from "./types";
2
+
3
+ export const TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS = 8;
4
+
5
+ const TOOLCRAFT_MAX_CONTROLS_PER_SECTION = 10;
6
+ const TOOLCRAFT_MIN_CONTROLS_PER_SPLIT_SECTION = 2;
7
+ const TOOLCRAFT_MIN_SPLIT_REASON_LENGTH = 12;
8
+
9
+ export function hasToolcraftInventoryEntityAgreement({
10
+ sectionInventoryById,
11
+ sections,
12
+ }: {
13
+ sectionInventoryById: ReadonlyMap<
14
+ string,
15
+ ToolcraftControlSectionInventoryEntry
16
+ >;
17
+ sections: ReadonlySet<string>;
18
+ }): boolean {
19
+ const entries = [...sections].map((sectionId) =>
20
+ sectionInventoryById.get(sectionId),
21
+ );
22
+ const entityIds = entries.map((entry) => entry?.entityId?.trim() ?? "");
23
+
24
+ return entityIds.every(Boolean) && new Set(entityIds).size === 1;
25
+ }
26
+
27
+ export function getToolcraftControlSectionEntityCohesionErrors(
28
+ sectionInventory: readonly ToolcraftControlSectionInventoryEntry[],
29
+ ): string[] {
30
+ const errors: string[] = [];
31
+ const entriesByEntityId = new Map<
32
+ string,
33
+ ToolcraftControlSectionInventoryEntry[]
34
+ >();
35
+
36
+ for (const entry of sectionInventory) {
37
+ const entityId = entry.entityId?.trim() ?? "";
38
+ const entity = entry.entity?.trim() ?? "";
39
+
40
+ if (
41
+ !entityId ||
42
+ !entity ||
43
+ (entry.id?.trim().length ?? 0) === 0 ||
44
+ !Array.isArray(entry.targets) ||
45
+ entry.targets.length === 0
46
+ ) {
47
+ continue;
48
+ }
49
+
50
+ const entries = entriesByEntityId.get(entityId) ?? [];
51
+ entries.push(entry);
52
+ entriesByEntityId.set(entityId, entries);
53
+ }
54
+
55
+ for (const [entityId, entries] of entriesByEntityId) {
56
+ const controlCount = entries.reduce(
57
+ (total, entry) => total + entry.targets.length,
58
+ 0,
59
+ );
60
+ const sectionNames = entries.map((entry) => entry.title.trim() || entry.id);
61
+ const entityNames = new Set(entries.map((entry) => entry.entity.trim()));
62
+
63
+ if (entityNames.size > 1) {
64
+ errors.push(
65
+ `Control Section Inventory entity "${entityId}" uses inconsistent entity names: ${[
66
+ ...entityNames,
67
+ ].join(", ")}. Reuse one human-readable entity name for every section in the entity.`,
68
+ );
69
+ }
70
+
71
+ if (controlCount <= TOOLCRAFT_MAX_CONTROLS_PER_SECTION) {
72
+ if (entries.length > 1) {
73
+ errors.push(
74
+ `Control Section Inventory entity "${entityId}" owns ${controlCount} controls across ${entries.length} sections (${sectionNames.join(", ")}). Entities with ${TOOLCRAFT_MAX_CONTROLS_PER_SECTION} or fewer controls must stay in one section.`,
75
+ );
76
+ }
77
+ continue;
78
+ }
79
+
80
+ if (entries.length === 1) {
81
+ errors.push(
82
+ `Control Section Inventory entity "${entityId}" owns ${controlCount} controls in section ${sectionNames[0]}. Split entities above ${TOOLCRAFT_MAX_CONTROLS_PER_SECTION} controls into explicit workflow stages.`,
83
+ );
84
+ continue;
85
+ }
86
+
87
+ const sectionsByStage = new Map<string, string[]>();
88
+
89
+ for (const entry of entries) {
90
+ const sectionName = entry.title.trim() || entry.id;
91
+ const sectionControlCount = entry.targets.length;
92
+ const workflowStage = entry.workflowStage?.trim() ?? "";
93
+ const splitReason = entry.splitReason?.trim() ?? "";
94
+
95
+ if (
96
+ sectionControlCount < TOOLCRAFT_MIN_CONTROLS_PER_SPLIT_SECTION ||
97
+ sectionControlCount > TOOLCRAFT_MAX_CONTROLS_PER_SECTION
98
+ ) {
99
+ errors.push(
100
+ `Control Section Inventory entity "${entityId}" section ${sectionName} owns ${sectionControlCount} ${sectionControlCount === 1 ? "control" : "controls"}. Every section in a split entity must own between ${TOOLCRAFT_MIN_CONTROLS_PER_SPLIT_SECTION} and ${TOOLCRAFT_MAX_CONTROLS_PER_SECTION} controls.`,
101
+ );
102
+ }
103
+
104
+ if (!workflowStage) {
105
+ errors.push(
106
+ `Control Section Inventory entity "${entityId}" section ${sectionName} must declare workflowStage because the entity is split across sections.`,
107
+ );
108
+ } else {
109
+ const normalizedStage = workflowStage.toLowerCase();
110
+ const stageSections = sectionsByStage.get(normalizedStage) ?? [];
111
+ stageSections.push(sectionName);
112
+ sectionsByStage.set(normalizedStage, stageSections);
113
+ }
114
+
115
+ if (splitReason.length < TOOLCRAFT_MIN_SPLIT_REASON_LENGTH) {
116
+ errors.push(
117
+ `Control Section Inventory entity "${entityId}" section ${sectionName} must declare splitReason of at least ${TOOLCRAFT_MIN_SPLIT_REASON_LENGTH} characters because the entity is split across sections.`,
118
+ );
119
+ }
120
+ }
121
+
122
+ for (const [stage, stageSections] of sectionsByStage) {
123
+ if (stageSections.length > 1) {
124
+ errors.push(
125
+ `Control Section Inventory entity "${entityId}" repeats workflowStage "${stage}" across sections ${stageSections.join(", ")}. Workflow stages must be unique within one entity.`,
126
+ );
127
+ }
128
+ }
129
+ }
130
+
131
+ return errors;
132
+ }
@@ -40,6 +40,7 @@ function createSchema({
40
40
 
41
41
  const inventoryEntry = {
42
42
  entity: "Appearance",
43
+ entityId: "appearance",
43
44
  groupingReason: "These controls define the visible appearance of the output.",
44
45
  id: "appearance",
45
46
  targets: ["appearance.opacity"],
@@ -56,6 +57,53 @@ describe("Toolcraft Control Section Inventory identity", () => {
56
57
  ).toEqual([]);
57
58
  });
58
59
 
60
+ it("preserves inventory ownership when runtime Setup relocates product background controls", () => {
61
+ const schema = defineToolcraft({
62
+ canvas: { enabled: true },
63
+ panels: {
64
+ controls: {
65
+ sections: [
66
+ {
67
+ controls: {
68
+ background: {
69
+ applicability: { mode: "always" },
70
+ defaultValue: "#101010",
71
+ target: "appearance.background",
72
+ type: "color",
73
+ },
74
+ includeBackground: {
75
+ applicability: { mode: "always" },
76
+ defaultValue: true,
77
+ target: "export.includeBackground",
78
+ type: "switch",
79
+ },
80
+ },
81
+ id: "background",
82
+ title: "Background",
83
+ },
84
+ ],
85
+ title: "Controls",
86
+ },
87
+ },
88
+ });
89
+ const backgroundInventory = {
90
+ entity: "Output background",
91
+ entityId: "output-background",
92
+ groupingReason:
93
+ "Inclusion and color jointly define the exported background.",
94
+ id: "background",
95
+ targets: ["export.includeBackground", "appearance.background"],
96
+ title: "Background",
97
+ } as const;
98
+
99
+ expect(schema.panels.controls?.sections[0]?.id).toBe("runtime.setup");
100
+ expect(
101
+ getToolcraftControlSectionInventoryErrors(schema, [
102
+ backgroundInventory,
103
+ ]),
104
+ ).toEqual([]);
105
+ });
106
+
59
107
  it("rejects an inventory id that does not match the schema section", () => {
60
108
  const errors = getToolcraftControlSectionInventoryErrors(createSchema(), [
61
109
  { ...inventoryEntry, id: "different-section" },
@@ -80,6 +128,45 @@ describe("Toolcraft Control Section Inventory identity", () => {
80
128
  );
81
129
  });
82
130
 
131
+ it.each([
132
+ [
133
+ "blank entityId",
134
+ { ...inventoryEntry, entityId: "" },
135
+ 'Control Section Inventory entry "Original title" must declare a non-empty stable entityId.',
136
+ ],
137
+ [
138
+ "blank entity",
139
+ { ...inventoryEntry, entity: "" },
140
+ 'Control Section Inventory entry "Original title" must declare a human-readable entity name.',
141
+ ],
142
+ ])("rejects %s", (_label, entry, expectedMessage) => {
143
+ expect(
144
+ getToolcraftControlSectionInventoryErrors(createSchema(), [entry]),
145
+ ).toContain(expectedMessage);
146
+ });
147
+
148
+ it("rejects a missing entityId without throwing", () => {
149
+ const { entityId: _entityId, ...entryWithoutEntityId } = inventoryEntry;
150
+ const malformedInventory = [
151
+ entryWithoutEntityId,
152
+ ] as unknown as readonly ToolcraftControlSectionInventoryEntry[];
153
+
154
+ expect(() =>
155
+ getToolcraftControlSectionInventoryErrors(
156
+ createSchema(),
157
+ malformedInventory,
158
+ ),
159
+ ).not.toThrow();
160
+ expect(
161
+ getToolcraftControlSectionInventoryErrors(
162
+ createSchema(),
163
+ malformedInventory,
164
+ ),
165
+ ).toContain(
166
+ 'Control Section Inventory entry "Original title" must declare a non-empty stable entityId.',
167
+ );
168
+ });
169
+
83
170
  it.each([
84
171
  ["missing", undefined],
85
172
  ["non-string", 42],