@pixel-point/toolcraft 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/package.json +1 -1
  2. package/src/generate-test-doc-assertions.mjs +58 -68
  3. package/src/generate-test-doc-assertions.test.mjs +43 -0
  4. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  5. package/src/generate-test-file-assertions.mjs +26 -75
  6. package/src/generate-test-product-iteration.test.mjs +17 -0
  7. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  8. package/src/generate-test-standalone-assertions.mjs +10 -0
  9. package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
  10. package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
  11. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  12. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  13. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  14. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  15. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
  16. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  17. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
  18. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
  19. package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
  20. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  21. package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
  22. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  23. package/templates/runtime/export/export-background.ts +0 -19
  24. package/templates/runtime/export/export.test.ts +45 -42
  25. package/templates/runtime/export/index.ts +44 -16
  26. package/templates/runtime/index.ts +2 -0
  27. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  28. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
  29. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
  30. package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
  31. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  32. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  33. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  34. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  35. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  36. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  37. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  38. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  39. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  40. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
  41. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  42. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  43. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
  44. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
  45. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  46. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  47. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  48. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  49. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  50. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  51. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  52. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  53. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  54. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  55. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  56. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  57. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  58. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  59. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  60. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  61. package/templates/runtime/react/index.ts +4 -0
  62. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  63. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  64. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  65. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  66. package/templates/runtime/scene/scene-bounds.ts +31 -17
  67. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  68. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  69. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  70. package/templates/runtime/schema/control-applicability.ts +433 -0
  71. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  72. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  73. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  74. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
  75. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  76. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  77. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  78. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  79. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  80. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  81. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  82. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  83. package/templates/runtime/schema/types.ts +68 -13
  84. package/templates/starter/AGENTS.md +12 -10
  85. package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
  86. package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
  87. package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
  88. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  89. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  90. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  91. package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
  92. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
  93. package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
  94. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  95. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  96. package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
  97. package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
  98. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  99. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
  100. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  101. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
  102. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
  103. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  104. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  105. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
  106. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  107. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  108. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  109. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
  110. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
  111. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  112. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  113. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
  114. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  115. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
  116. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  117. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  118. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  119. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
  120. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  121. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  122. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  123. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
  124. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  125. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  126. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  127. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  128. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  129. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  130. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  131. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  132. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  133. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  134. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  135. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  136. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  137. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  138. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  139. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  140. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  141. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  142. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  143. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  144. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  145. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  146. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  147. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  148. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  149. package/templates/starter/src/app/acceptance/output-export.ts +22 -10
  150. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  151. package/templates/starter/src/app/acceptance/types.ts +23 -8
  152. package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
  153. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  154. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  155. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  156. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  157. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  158. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  159. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  160. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  161. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  162. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  163. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  164. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  165. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  166. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  167. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  168. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  169. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  170. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
  171. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  172. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  173. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  174. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  175. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  176. package/templates/starter/src/app/starter-acceptance.ts +11 -2
  177. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  178. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  179. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
  180. package/templates/starter/src/routes/index.tsx +1 -0
  181. package/templates/ui/components/composites/tabs.tsx +2 -2
  182. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  183. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  184. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  185. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  186. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  187. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  188. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  189. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  190. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  191. package/templates/ui/components/controls/index.ts +3 -0
  192. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  193. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  194. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  195. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  196. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
@@ -5,11 +5,13 @@ import {
5
5
  getToolcraftAnimationModeChoiceErrors,
6
6
  } from "./animation-intent";
7
7
  import { getToolcraftControlAcceptanceErrors } from "./control-acceptance";
8
+ import { getToolcraftControlApplicabilityErrors } from "./control-applicability";
8
9
  import {
9
10
  getToolcraftControlSectionHeuristicErrors,
10
11
  getToolcraftControlSectionInvariantErrors,
11
12
  } from "./control-layout";
12
13
  import { getToolcraftControlOrderErrors } from "./control-order";
14
+ import { getToolcraftControlSectionEntityCohesionErrors } from "./control-section-entity-cohesion";
13
15
  import { getToolcraftControlSectionInventoryErrors } from "./control-section-inventory";
14
16
  import { isToolcraftVisibleAcceptanceControl } from "./controls";
15
17
  import { getToolcraftInlineLayoutErrors } from "./inline-layout";
@@ -146,12 +148,27 @@ const toolcraftAcceptanceValidators: readonly ToolcraftAcceptanceValidator[] = [
146
148
  ruleId: "controls-product-coverage",
147
149
  validate: ({ schema }) => getToolcraftOrientationGizmoErrors(schema),
148
150
  },
151
+ {
152
+ path: "schema.panels.controls.sections[].controls[].applicability",
153
+ ruleId: "controls-product-coverage",
154
+ validate: ({ controls, productReadiness }) =>
155
+ getToolcraftControlApplicabilityErrors({
156
+ controls,
157
+ productReadiness,
158
+ }),
159
+ },
149
160
  {
150
161
  path: "starterControlSectionInventory",
151
162
  ruleId: "controls-section-inventory-required",
152
163
  validate: ({ schema, sectionInventory }) =>
153
164
  getToolcraftControlSectionInventoryErrors(schema, sectionInventory),
154
165
  },
166
+ {
167
+ path: "starterControlSectionInventory",
168
+ ruleId: "controls-section-inventory-required",
169
+ validate: ({ sectionInventory }) =>
170
+ getToolcraftControlSectionEntityCohesionErrors(sectionInventory),
171
+ },
155
172
  {
156
173
  path: "schema.panels.controls.sections[].layoutGroups",
157
174
  ruleId: "controls-component-layout-invariants",
@@ -160,11 +177,11 @@ const toolcraftAcceptanceValidators: readonly ToolcraftAcceptanceValidator[] = [
160
177
  {
161
178
  path: "schema.export",
162
179
  ruleId: "output-export-required",
163
- validate: ({ acceptance, controls, hasVideoExportAction, schema }) =>
180
+ validate: ({ acceptance, controls, productReadiness, schema }) =>
164
181
  getToolcraftOutputExportErrors({
165
182
  acceptance,
166
183
  controls,
167
- hasVideoExportAction,
184
+ productReadiness,
168
185
  schema,
169
186
  }),
170
187
  },
@@ -0,0 +1,239 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ getToolcraftArtifactExportIntentEvidenceErrors,
5
+ resolveToolcraftArtifactExportIntent,
6
+ validateToolcraftArtifactExportIntentCorrespondence,
7
+ } from "./acceptance/artifact-export-intent";
8
+ import type { ToolcraftArtifactExportIntent } from "./acceptance/types";
9
+
10
+ const deliveryCases: readonly Readonly<{
11
+ expected: Readonly<{
12
+ imageEnabled: boolean;
13
+ videoEnabled: boolean;
14
+ }>;
15
+ intent: ToolcraftArtifactExportIntent;
16
+ name: string;
17
+ }>[] = [
18
+ {
19
+ expected: { imageEnabled: true, videoEnabled: false },
20
+ intent: {
21
+ image: { mode: "toolcraft-default" },
22
+ video: { mode: "not-requested" },
23
+ },
24
+ name: "enables default image export without unrequested video export",
25
+ },
26
+ {
27
+ expected: { imageEnabled: true, videoEnabled: false },
28
+ intent: {
29
+ image: {
30
+ evidence: "The user explicitly requested PNG delivery.",
31
+ mode: "user-requested",
32
+ },
33
+ video: { mode: "not-requested" },
34
+ },
35
+ name: "enables explicitly requested image export",
36
+ },
37
+ {
38
+ expected: { imageEnabled: false, videoEnabled: false },
39
+ intent: {
40
+ image: {
41
+ evidence: "The user explicitly requested no image export.",
42
+ mode: "user-removed",
43
+ },
44
+ video: { mode: "not-requested" },
45
+ },
46
+ name: "disables explicitly removed image export",
47
+ },
48
+ {
49
+ expected: { imageEnabled: true, videoEnabled: true },
50
+ intent: {
51
+ image: { mode: "toolcraft-default" },
52
+ video: {
53
+ evidence: "The user explicitly requested MP4 delivery.",
54
+ mode: "user-requested",
55
+ },
56
+ },
57
+ name: "enables explicitly requested video export",
58
+ },
59
+ {
60
+ expected: { imageEnabled: true, videoEnabled: true },
61
+ intent: {
62
+ image: {
63
+ evidence: "The user explicitly requested PNG delivery.",
64
+ mode: "user-requested",
65
+ },
66
+ video: {
67
+ evidence: "The user explicitly requested MP4 delivery.",
68
+ mode: "user-requested",
69
+ },
70
+ },
71
+ name: "enables explicitly requested image and video export",
72
+ },
73
+ {
74
+ expected: { imageEnabled: false, videoEnabled: true },
75
+ intent: {
76
+ image: {
77
+ evidence: "The user explicitly requested no image export.",
78
+ mode: "user-removed",
79
+ },
80
+ video: {
81
+ evidence: "The user explicitly requested MP4 delivery.",
82
+ mode: "user-requested",
83
+ },
84
+ },
85
+ name: "keeps requested video export when image export is removed",
86
+ },
87
+ ];
88
+
89
+ const evidenceCases: readonly Readonly<{
90
+ expected: readonly string[];
91
+ intent: ToolcraftArtifactExportIntent;
92
+ name: string;
93
+ }>[] = [
94
+ {
95
+ expected: [],
96
+ intent: {
97
+ image: { mode: "toolcraft-default" },
98
+ video: { mode: "not-requested" },
99
+ },
100
+ name: "accepts default image and not-requested video modes",
101
+ },
102
+ {
103
+ expected: [],
104
+ intent: {
105
+ image: {
106
+ evidence: "The user explicitly requested PNG delivery.",
107
+ mode: "user-requested",
108
+ },
109
+ video: { mode: "not-requested" },
110
+ },
111
+ name: "accepts nonblank requested image evidence",
112
+ },
113
+ {
114
+ expected: [],
115
+ intent: {
116
+ image: {
117
+ evidence: "The user explicitly requested no image export.",
118
+ mode: "user-removed",
119
+ },
120
+ video: { mode: "not-requested" },
121
+ },
122
+ name: "accepts nonblank image removal evidence",
123
+ },
124
+ {
125
+ expected: [],
126
+ intent: {
127
+ image: { mode: "toolcraft-default" },
128
+ video: {
129
+ evidence: "The user explicitly requested MP4 delivery.",
130
+ mode: "user-requested",
131
+ },
132
+ },
133
+ name: "accepts nonblank requested video evidence",
134
+ },
135
+ {
136
+ expected: [
137
+ "Image export user-removed intent requires non-empty evidence.",
138
+ ],
139
+ intent: {
140
+ image: { evidence: " \n\t ", mode: "user-removed" },
141
+ video: { mode: "not-requested" },
142
+ },
143
+ name: "rejects blank image removal evidence",
144
+ },
145
+ {
146
+ expected: [
147
+ "Image export user-requested intent requires non-empty evidence.",
148
+ ],
149
+ intent: {
150
+ image: { evidence: " ", mode: "user-requested" },
151
+ video: { mode: "not-requested" },
152
+ },
153
+ name: "rejects blank requested image evidence",
154
+ },
155
+ {
156
+ expected: [
157
+ "Video export user-requested intent requires non-empty evidence.",
158
+ ],
159
+ intent: {
160
+ image: { mode: "toolcraft-default" },
161
+ video: { evidence: "\t", mode: "user-requested" },
162
+ },
163
+ name: "rejects blank requested video evidence",
164
+ },
165
+ {
166
+ expected: [
167
+ "Image export user-removed intent requires non-empty evidence.",
168
+ "Video export user-requested intent requires non-empty evidence.",
169
+ ],
170
+ intent: {
171
+ image: { evidence: " ", mode: "user-removed" },
172
+ video: { evidence: "\n", mode: "user-requested" },
173
+ },
174
+ name: "aggregates blank image and video evidence errors",
175
+ },
176
+ ];
177
+
178
+ describe("Toolcraft artifact export intent", () => {
179
+ it.each(deliveryCases)("$name", ({ expected, intent }) => {
180
+ const delivery = resolveToolcraftArtifactExportIntent(intent);
181
+
182
+ expect(delivery).toEqual(expected);
183
+ expect(Object.isFrozen(delivery)).toBe(true);
184
+ });
185
+
186
+ it.each(evidenceCases)("$name", ({ expected, intent }) => {
187
+ expect(getToolcraftArtifactExportIntentEvidenceErrors(intent)).toEqual(
188
+ expected,
189
+ );
190
+ });
191
+
192
+ it("validates enabled and disabled delivery against typed schema facts", () => {
193
+ expect(
194
+ validateToolcraftArtifactExportIntentCorrespondence({
195
+ hasImageExportAction: false,
196
+ hasImageExportSection: false,
197
+ hasVideoExportAction: true,
198
+ hasVideoExportSection: true,
199
+ intent: {
200
+ image: { mode: "toolcraft-default" },
201
+ video: { mode: "not-requested" },
202
+ },
203
+ }),
204
+ ).toEqual({
205
+ delivery: { imageEnabled: true, videoEnabled: false },
206
+ errors: [
207
+ 'Image export intent "toolcraft-default" requires an export-image panel action.',
208
+ 'Image export intent "toolcraft-default" requires an "Image Export" section.',
209
+ 'Video export intent "not-requested" must not have an export-video panel action.',
210
+ 'Video export intent "not-requested" must not have a "Video Export" section.',
211
+ ],
212
+ });
213
+ });
214
+
215
+ it("aggregates evidence and correspondence errors without prose inference", () => {
216
+ expect(
217
+ validateToolcraftArtifactExportIntentCorrespondence({
218
+ hasImageExportAction: true,
219
+ hasImageExportSection: true,
220
+ hasVideoExportAction: false,
221
+ hasVideoExportSection: false,
222
+ intent: {
223
+ image: { evidence: " ", mode: "user-removed" },
224
+ video: { evidence: "\t", mode: "user-requested" },
225
+ },
226
+ }),
227
+ ).toEqual({
228
+ delivery: { imageEnabled: false, videoEnabled: true },
229
+ errors: [
230
+ "Image export user-removed intent requires non-empty evidence.",
231
+ "Video export user-requested intent requires non-empty evidence.",
232
+ 'Image export intent "user-removed" must not have an export-image panel action.',
233
+ 'Image export intent "user-removed" must not have an "Image Export" section.',
234
+ 'Video export intent "user-requested" requires an export-video panel action.',
235
+ 'Video export intent "user-requested" requires a "Video Export" section.',
236
+ ],
237
+ });
238
+ });
239
+ });
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
+ import type { ToolcraftProductReadiness } from "./acceptance/types";
3
4
  import { defineContractSchemaFixture, validateContractAcceptance } from "./starter-acceptance.contract-fixtures";
4
5
  import {
5
6
  makeBackgroundSection,
@@ -7,6 +8,33 @@ import {
7
8
  } from "./starter-acceptance.export-test-utils";
8
9
  import { makeControlAcceptance } from "./starter-acceptance.test-utils";
9
10
 
11
+ const imageExportProductReadiness: ToolcraftProductReadiness = {
12
+ exportIntent: {
13
+ image: { mode: "toolcraft-default" },
14
+ video: { mode: "not-requested" },
15
+ },
16
+ interactionOwnership: [],
17
+ mode: "product",
18
+ productName: "Background image export fixture",
19
+ productSummary: "A static image fixture for background export validation.",
20
+ requestedBehavior: "Export the image with configurable background output.",
21
+ viewInteraction: {
22
+ mode: "non-spatial",
23
+ reason: "The fixture renders a static two-dimensional image.",
24
+ },
25
+ };
26
+
27
+ const videoExportProductReadiness: ToolcraftProductReadiness = {
28
+ ...imageExportProductReadiness,
29
+ exportIntent: {
30
+ image: { mode: "toolcraft-default" },
31
+ video: {
32
+ evidence: "The background fixture explicitly exercises video background export.",
33
+ mode: "user-requested",
34
+ },
35
+ },
36
+ };
37
+
10
38
  describe("Toolcraft background export acceptance contract", () => {
11
39
  it("requires png export apps to expose background color and png background toggle controls", () => {
12
40
  const schemaWithoutBackgroundControls = defineContractSchemaFixture({
@@ -43,6 +71,7 @@ describe("Toolcraft background export acceptance contract", () => {
43
71
  expect(
44
72
  validateContractAcceptance({
45
73
  schema: schemaWithoutBackgroundControls,
74
+ productReadiness: imageExportProductReadiness,
46
75
  acceptance: [
47
76
  {
48
77
  actionCoverage: ["export.png"],
@@ -118,6 +147,7 @@ describe("Toolcraft background export acceptance contract", () => {
118
147
  expect(
119
148
  validateContractAcceptance({
120
149
  schema: schemaWithLegacyBackgroundControls,
150
+ productReadiness: imageExportProductReadiness,
121
151
  acceptance: [
122
152
  makeControlAcceptance("appearance.background", "color"),
123
153
  makeControlAcceptance("export.includeBackground", "switch"),
@@ -185,6 +215,7 @@ describe("Toolcraft background export acceptance contract", () => {
185
215
  });
186
216
  const errors = validateContractAcceptance({
187
217
  schema: schemaWithBackgroundControls,
218
+ productReadiness: imageExportProductReadiness,
188
219
  acceptance: [
189
220
  makeControlAcceptance("appearance.background", "color"),
190
221
  {
@@ -262,6 +293,7 @@ describe("Toolcraft background export acceptance contract", () => {
262
293
  expect(
263
294
  validateContractAcceptance({
264
295
  schema: schemaWithBackgroundControls,
296
+ productReadiness: imageExportProductReadiness,
265
297
  acceptance: [
266
298
  makeControlAcceptance("appearance.background", "color"),
267
299
  makeControlAcceptance("export.includeBackground", "switch"),
@@ -332,6 +364,7 @@ describe("Toolcraft background export acceptance contract", () => {
332
364
  expect(
333
365
  validateContractAcceptance({
334
366
  schema: schemaWithVideoExport,
367
+ productReadiness: videoExportProductReadiness,
335
368
  acceptance: [
336
369
  makeControlAcceptance("appearance.background", "color"),
337
370
  {
@@ -9,6 +9,10 @@ import { makeControlAcceptance } from "./starter-acceptance.test-utils";
9
9
  import type { ToolcraftProductReadiness } from "./acceptance/types";
10
10
 
11
11
  const infinityProductReadiness: ToolcraftProductReadiness = {
12
+ exportIntent: {
13
+ image: { mode: "toolcraft-default" },
14
+ video: { mode: "not-requested" },
15
+ },
12
16
  interactionOwnership: [],
13
17
  mode: "product",
14
18
  productName: "Infinity contract fixture",
@@ -20,6 +24,17 @@ const infinityProductReadiness: ToolcraftProductReadiness = {
20
24
  },
21
25
  };
22
26
 
27
+ const infinityVideoProductReadiness: ToolcraftProductReadiness = {
28
+ ...infinityProductReadiness,
29
+ exportIntent: {
30
+ image: { mode: "toolcraft-default" },
31
+ video: {
32
+ evidence: "The Infinity canvas fixture explicitly exercises video export bounds.",
33
+ mode: "user-requested",
34
+ },
35
+ },
36
+ };
37
+
23
38
  function createFixedOutputSchema({
24
39
  height = 1080,
25
40
  width = 1920,
@@ -167,7 +182,7 @@ describe("Toolcraft canvas sizing acceptance coverage", () => {
167
182
  validateContractAcceptance({
168
183
  schema: createEditableOutputSchema("export-video"),
169
184
  acceptance: [makeInfinityCanvasAcceptance("mode-and-restoration")],
170
- productReadiness: infinityProductReadiness,
185
+ productReadiness: infinityVideoProductReadiness,
171
186
  }),
172
187
  ).toEqual(
173
188
  expect.arrayContaining([
@@ -7,6 +7,71 @@ import {
7
7
  import { makeControlAcceptance } from "./starter-acceptance.test-utils";
8
8
 
9
9
  describe("starter acceptance compound control part coverage contract", () => {
10
+ it("distinguishes user-owned and source-owned collection parts", () => {
11
+ const collectionSchema = defineContractSchemaFixture({
12
+ canvas: { enabled: true },
13
+ panels: {
14
+ controls: {
15
+ sections: [
16
+ {
17
+ controls: {
18
+ editableColors: {
19
+ defaultValue: [{ hex: "#DFFF1A" }],
20
+ itemControl: { label: false, type: "color" },
21
+ target: "colors.editable",
22
+ type: "collectionActions",
23
+ },
24
+ editableLayers: {
25
+ defaultValue: [{ enabled: true, strength: 0.5 }],
26
+ itemControls: {
27
+ enabled: {
28
+ defaultValue: true,
29
+ label: "Enabled",
30
+ type: "switch",
31
+ },
32
+ strength: {
33
+ defaultValue: 0.5,
34
+ label: "Strength",
35
+ max: 1,
36
+ min: 0,
37
+ type: "slider",
38
+ },
39
+ },
40
+ itemLabel: "Layer",
41
+ target: "layers.editable",
42
+ type: "collectionActions",
43
+ },
44
+ sourceColors: {
45
+ defaultValue: [{ hex: "#8CFF3A" }],
46
+ itemControl: { label: false, type: "color" },
47
+ target: "colors.source",
48
+ type: "sourceCollection",
49
+ },
50
+ },
51
+ title: "Fill Sections",
52
+ },
53
+ ],
54
+ title: "Controls",
55
+ },
56
+ },
57
+ });
58
+ const acceptance = [
59
+ makeControlAcceptance("colors.editable", "collectionActions"),
60
+ makeControlAcceptance("layers.editable", "collectionActions"),
61
+ makeControlAcceptance("colors.source", "sourceCollection"),
62
+ ];
63
+
64
+ expect(
65
+ validateContractAcceptance({ schema: collectionSchema, acceptance }),
66
+ ).toEqual(
67
+ expect.arrayContaining([
68
+ "Fill Sections / editableColors (colors.editable) must declare controlPartCoverage for every semantic value part: collectionActions.add, collectionActions.remove, collectionActions.items.",
69
+ "Fill Sections / editableLayers (layers.editable) must declare controlPartCoverage for every semantic value part: collectionActions.add, collectionActions.remove, collectionActions.items.",
70
+ "Fill Sections / sourceColors (colors.source) must declare controlPartCoverage for every semantic value part: sourceCollection.items.",
71
+ ]),
72
+ );
73
+ });
74
+
10
75
  it("requires compound controls to cover every semantic value part", () => {
11
76
  const compoundSchema = defineContractSchemaFixture({
12
77
  canvas: { enabled: true },
@@ -20,6 +20,47 @@ const schema = {
20
20
  } satisfies ToolcraftAppSchema;
21
21
 
22
22
  describe("Toolcraft acceptance contract schema fixtures", () => {
23
+ it("authors neutral fixture controls explicitly without masking legacy input", () => {
24
+ const fixture = defineContractSchemaFixture({
25
+ canvas: { enabled: true },
26
+ panels: {
27
+ controls: {
28
+ sections: [
29
+ {
30
+ controls: {
31
+ legacy: {
32
+ target: "shape.legacy",
33
+ type: "slider",
34
+ visibleWhen: { equals: true, target: "shape.enabled" },
35
+ },
36
+ neutral: {
37
+ target: "shape.enabled",
38
+ type: "switch",
39
+ },
40
+ },
41
+ id: "shape",
42
+ title: "Shape",
43
+ },
44
+ ],
45
+ title: "Controls",
46
+ },
47
+ },
48
+ });
49
+ const controls = fixture.panels.controls?.sections.find(
50
+ (section) => section.id === "shape",
51
+ )?.controls;
52
+
53
+ expect(controls?.neutral?.applicability).toEqual({
54
+ mode: "always",
55
+ origin: "explicit",
56
+ });
57
+ expect(controls?.legacy?.applicability).toEqual({
58
+ all: [{ equals: true, target: "shape.enabled" }],
59
+ mode: "conditional",
60
+ origin: "legacy",
61
+ });
62
+ });
63
+
23
64
  it("opts unrelated unit fixtures out without changing the production default", () => {
24
65
  const productionDefault = defineToolcraft(schema);
25
66
  const contractFixture = defineContractSchemaFixture(schema);
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  defineToolcraft,
3
+ resolveToolcraftControlApplicability,
4
+ type ResolvedToolcraftAppSchema,
3
5
  type ToolcraftAppSchema,
4
6
  } from "@repo/toolcraft-runtime";
5
7
 
@@ -16,8 +18,32 @@ import {
16
18
  } from "./acceptance/validate-coverage";
17
19
 
18
20
  export function defineContractSchemaFixture(schema: ToolcraftAppSchema) {
21
+ const controls = schema.panels.controls
22
+ ? {
23
+ ...schema.panels.controls,
24
+ sections: schema.panels.controls.sections.map((section) => ({
25
+ ...section,
26
+ controls: Object.fromEntries(
27
+ Object.entries(section.controls).map(([controlId, control]) => [
28
+ controlId,
29
+ control.applicability || control.visibleWhen
30
+ ? control
31
+ : {
32
+ ...control,
33
+ applicability: { mode: "always" as const },
34
+ },
35
+ ]),
36
+ ),
37
+ })),
38
+ }
39
+ : undefined;
40
+
19
41
  return defineToolcraft({
20
42
  ...schema,
43
+ panels: {
44
+ ...schema.panels,
45
+ ...(controls ? { controls } : {}),
46
+ },
21
47
  persistence: { storage: "none" },
22
48
  });
23
49
  }
@@ -54,28 +80,98 @@ export const contractProductReadinessFixture: ToolcraftProductReadiness = {
54
80
  reason: "Neutral contract fixture without a product-owned spatial scene.",
55
81
  };
56
82
 
57
- export function validateContractAcceptance(
58
- overrides: Partial<ToolcraftAcceptanceValidationInput> = {},
59
- ): string[] {
60
- return validateToolcraftAcceptanceCoverage({
83
+ type ContractAcceptanceOverrides = Omit<
84
+ Partial<ToolcraftAcceptanceValidationInput>,
85
+ "schema"
86
+ > & {
87
+ schema?: ContractValidationSchemaFixture;
88
+ };
89
+
90
+ type ContractValidationSchemaFixture = Omit<
91
+ ResolvedToolcraftAppSchema,
92
+ "panels"
93
+ > & {
94
+ panels: Omit<ResolvedToolcraftAppSchema["panels"], "controls"> & {
95
+ controls?: ToolcraftAppSchema["panels"]["controls"];
96
+ };
97
+ };
98
+
99
+ function resolveContractValidationSchemaFixture(
100
+ schema: ContractValidationSchemaFixture,
101
+ ): ResolvedToolcraftAppSchema {
102
+ const {
103
+ controls: _fixtureControls,
104
+ ...panelsWithoutFixtureControls
105
+ } = schema.panels;
106
+ const controls = schema.panels.controls
107
+ ? {
108
+ ...schema.panels.controls,
109
+ sections: schema.panels.controls.sections.map((section) => ({
110
+ ...section,
111
+ controls: Object.fromEntries(
112
+ Object.entries(section.controls).map(([controlId, control]) => {
113
+ const {
114
+ applicability: authoredApplicability,
115
+ visibleWhen,
116
+ ...controlWithoutApplicability
117
+ } = control;
118
+
119
+ return [
120
+ controlId,
121
+ {
122
+ ...controlWithoutApplicability,
123
+ applicability: resolveToolcraftControlApplicability({
124
+ applicability:
125
+ authoredApplicability ??
126
+ (visibleWhen ? undefined : { mode: "always" }),
127
+ visibleWhen,
128
+ }),
129
+ },
130
+ ];
131
+ }),
132
+ ),
133
+ })),
134
+ }
135
+ : undefined;
136
+
137
+ // Contract tests deliberately preserve malformed post-normalization fields so
138
+ // the matching acceptance validator, rather than defineToolcraft, reports them.
139
+ return {
140
+ ...schema,
141
+ panels: {
142
+ ...panelsWithoutFixtureControls,
143
+ ...(controls ? { controls } : {}),
144
+ },
145
+ } as ResolvedToolcraftAppSchema;
146
+ }
147
+
148
+ function resolveContractAcceptanceInput(
149
+ overrides: ContractAcceptanceOverrides,
150
+ ): ToolcraftAcceptanceValidationInput {
151
+ const { schema = contractSchemaFixture, ...remainingOverrides } = overrides;
152
+
153
+ return {
61
154
  acceptance: contractAcceptanceFixture,
62
155
  productReadiness: contractProductReadinessFixture,
63
- schema: contractSchemaFixture,
156
+ schema: resolveContractValidationSchemaFixture(schema),
64
157
  sectionInventory: contractSectionInventoryFixture,
65
158
  transferMode: contractTransferModeFixture,
66
- ...overrides,
67
- });
159
+ ...remainingOverrides,
160
+ };
161
+ }
162
+
163
+ export function validateContractAcceptance(
164
+ overrides: ContractAcceptanceOverrides = {},
165
+ ): string[] {
166
+ return validateToolcraftAcceptanceCoverage(
167
+ resolveContractAcceptanceInput(overrides),
168
+ );
68
169
  }
69
170
 
70
171
  export function validateContractAcceptanceDiagnostics(
71
- overrides: Partial<ToolcraftAcceptanceValidationInput> = {},
172
+ overrides: ContractAcceptanceOverrides = {},
72
173
  ) {
73
- return validateToolcraftAcceptanceDiagnostics({
74
- acceptance: contractAcceptanceFixture,
75
- productReadiness: contractProductReadinessFixture,
76
- schema: contractSchemaFixture,
77
- sectionInventory: contractSectionInventoryFixture,
78
- transferMode: contractTransferModeFixture,
79
- ...overrides,
80
- });
174
+ return validateToolcraftAcceptanceDiagnostics(
175
+ resolveContractAcceptanceInput(overrides),
176
+ );
81
177
  }