@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
@@ -0,0 +1,224 @@
1
+ import {
2
+ defineToolcraft,
3
+ type ToolcraftControlSchema,
4
+ } from "@repo/toolcraft-runtime";
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ import { getToolcraftControlApplicabilityErrors } from "./acceptance/control-applicability";
8
+ import type { ToolcraftProductReadiness } from "./acceptance/types";
9
+ import { collectToolcraftVisibleAcceptanceControls } from "./acceptance/validate-coverage";
10
+
11
+ const productReadiness: ToolcraftProductReadiness = {
12
+ exportIntent: {
13
+ image: { mode: "toolcraft-default" },
14
+ video: { mode: "not-requested" },
15
+ },
16
+ interactionOwnership: [],
17
+ mode: "product",
18
+ productName: "Shape fixture",
19
+ productSummary: "Neutral applicability contract fixture.",
20
+ requestedBehavior: "Show only controls that apply to the selected shape.",
21
+ viewInteraction: { mode: "non-spatial", reason: "Schema-only fixture." },
22
+ };
23
+
24
+ const shapeKind: ToolcraftControlSchema = {
25
+ applicability: { mode: "always" },
26
+ defaultValue: "circle",
27
+ options: [
28
+ { label: "Circle", value: "circle" },
29
+ { label: "Square", value: "square" },
30
+ { label: "Polygon", value: "polygon" },
31
+ { label: "Star", value: "star" },
32
+ ],
33
+ target: "shape.kind",
34
+ type: "segmented",
35
+ };
36
+
37
+ const alwaysControls: Record<string, ToolcraftControlSchema> = {
38
+ kind: shapeKind,
39
+ cornerRadius: {
40
+ applicability: {
41
+ all: [
42
+ {
43
+ oneOf: ["square", "polygon", "star"],
44
+ target: "shape.kind",
45
+ },
46
+ ],
47
+ mode: "conditional",
48
+ },
49
+ target: "shape.cornerRadius",
50
+ type: "slider",
51
+ },
52
+ depth: {
53
+ applicability: {
54
+ all: [{ equals: "star", target: "shape.kind" }],
55
+ mode: "conditional",
56
+ },
57
+ target: "shape.depth",
58
+ type: "slider",
59
+ },
60
+ sides: {
61
+ applicability: {
62
+ all: [
63
+ { oneOf: ["polygon", "star"], target: "shape.kind" },
64
+ ],
65
+ mode: "conditional",
66
+ },
67
+ target: "shape.sides",
68
+ type: "slider",
69
+ },
70
+ };
71
+
72
+ function validateProduct(
73
+ controls: Record<string, ToolcraftControlSchema>,
74
+ ): string[] {
75
+ const schema = defineToolcraft({
76
+ canvas: { enabled: true },
77
+ panels: {
78
+ controls: {
79
+ sections: [{ controls, id: "shape", title: "Shape" }],
80
+ title: "Controls",
81
+ },
82
+ },
83
+ });
84
+
85
+ return getToolcraftControlApplicabilityErrors({
86
+ controls: collectToolcraftVisibleAcceptanceControls(schema),
87
+ productReadiness,
88
+ });
89
+ }
90
+
91
+ describe("starter product control applicability", () => {
92
+ it("requires explicit product applicability and rejects legacy visibility", () => {
93
+ expect(
94
+ validateProduct({
95
+ kind: shapeKind,
96
+ sides: { target: "shape.sides", type: "slider" },
97
+ }),
98
+ ).toContainEqual(
99
+ expect.stringContaining("shape.sides must declare explicit applicability"),
100
+ );
101
+
102
+ expect(
103
+ validateProduct({
104
+ kind: shapeKind,
105
+ sides: {
106
+ target: "shape.sides",
107
+ type: "slider",
108
+ visibleWhen: { equals: "star", target: "shape.kind" },
109
+ },
110
+ }),
111
+ ).toContainEqual(
112
+ expect.stringContaining("shape.sides uses legacy visibleWhen"),
113
+ );
114
+ });
115
+
116
+ it("rejects missing, invalid, contradictory, and unsupported selectors", () => {
117
+ expect(
118
+ validateProduct({
119
+ kind: shapeKind,
120
+ sides: {
121
+ applicability: {
122
+ all: [{ equals: true, target: "shape.missing" }],
123
+ mode: "conditional",
124
+ },
125
+ target: "shape.sides",
126
+ type: "slider",
127
+ },
128
+ }),
129
+ ).toContainEqual(
130
+ expect.stringContaining('predicate target "shape.missing" does not exist'),
131
+ );
132
+
133
+ expect(
134
+ validateProduct({
135
+ kind: shapeKind,
136
+ sides: {
137
+ applicability: {
138
+ all: [{ equals: "triangle", target: "shape.kind" }],
139
+ mode: "conditional",
140
+ },
141
+ target: "shape.sides",
142
+ type: "slider",
143
+ },
144
+ }),
145
+ ).toContainEqual(
146
+ expect.stringContaining(
147
+ 'value "triangle" is not an option of shape.kind',
148
+ ),
149
+ );
150
+
151
+ expect(
152
+ validateProduct({
153
+ kind: shapeKind,
154
+ sides: {
155
+ applicability: {
156
+ all: [
157
+ { equals: "polygon", target: "shape.kind" },
158
+ { equals: "star", target: "shape.kind" },
159
+ ],
160
+ mode: "conditional",
161
+ },
162
+ target: "shape.sides",
163
+ type: "slider",
164
+ },
165
+ }),
166
+ ).toContainEqual(
167
+ expect.stringContaining("shape.sides applicability is unsatisfiable"),
168
+ );
169
+
170
+ expect(
171
+ validateProduct({
172
+ kind: shapeKind,
173
+ name: {
174
+ applicability: { mode: "always" },
175
+ target: "shape.name",
176
+ type: "text",
177
+ },
178
+ sides: {
179
+ applicability: {
180
+ all: [{ equals: "named", target: "shape.name" }],
181
+ mode: "conditional",
182
+ },
183
+ target: "shape.sides",
184
+ type: "slider",
185
+ },
186
+ }),
187
+ ).toContainEqual(
188
+ expect.stringContaining(
189
+ "shape.name is not a supported applicability selector",
190
+ ),
191
+ );
192
+ });
193
+
194
+ it("rejects numeric gates with no satisfying selector value", () => {
195
+ expect(
196
+ validateProduct({
197
+ count: {
198
+ applicability: { mode: "always" },
199
+ defaultValue: 2,
200
+ max: 3,
201
+ min: 1,
202
+ step: 1,
203
+ target: "shape.count",
204
+ type: "slider",
205
+ variant: "discrete",
206
+ },
207
+ sides: {
208
+ applicability: {
209
+ all: [{ greaterThan: 10, target: "shape.count" }],
210
+ mode: "conditional",
211
+ },
212
+ target: "shape.sides",
213
+ type: "slider",
214
+ },
215
+ }),
216
+ ).toContainEqual(
217
+ expect.stringContaining("shape.sides has no satisfying value in shape.count"),
218
+ );
219
+ });
220
+
221
+ it("accepts a complete finite applicability declaration", () => {
222
+ expect(validateProduct(alwaysControls)).toEqual([]);
223
+ });
224
+ });
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
3
  getToolcraftSegmentedStaticFitError,
4
+ type ToolcraftCollectionItemControlType,
4
5
  } from "@repo/toolcraft-runtime";
5
6
 
6
7
  import {
@@ -18,7 +19,7 @@ const SCREENSHOT_LABELS = ["Default", "Ink Drops", "Solar", "Background"];
18
19
  const BOUNDARY_LABELS = ["Ink Drops", "Default", "Solar", "RGB"];
19
20
 
20
21
  function createRawCollectionModesSchema(
21
- itemControlType: string,
22
+ itemControlType: ToolcraftCollectionItemControlType,
22
23
  labels: readonly string[],
23
24
  ) {
24
25
  return {
@@ -64,6 +65,11 @@ const collectionModesAcceptance = [
64
65
  browser: true,
65
66
  browserTestName: "browser: collection modes update rendered output",
66
67
  componentType: "collectionActions",
68
+ controlPartCoverage: [
69
+ "collectionActions.add",
70
+ "collectionActions.remove",
71
+ "collectionActions.items",
72
+ ] as const,
67
73
  evidence: "rendered-pixels" as const,
68
74
  expectedObservable: "Adding and editing modes changes rendered output.",
69
75
  fixture: "collection modes fixture",
@@ -339,7 +345,7 @@ describe("starter acceptance control order contract", () => {
339
345
  it("reports canonical fit guidance for an overwide collectionActions itemControl", () => {
340
346
  const itemControl = {
341
347
  options: SCREENSHOT_LABELS.map((label) => ({ label })),
342
- type: "segmented",
348
+ type: "segmented" as const,
343
349
  };
344
350
  const fitGuidance = getToolcraftSegmentedStaticFitError(itemControl);
345
351
 
@@ -359,10 +365,59 @@ describe("starter acceptance control order contract", () => {
359
365
  );
360
366
  });
361
367
 
368
+ it("reports canonical fit guidance for every compound itemControls field", () => {
369
+ const schema = createRawCollectionModesSchema("segmented", SCREENSHOT_LABELS);
370
+ const modes = schema.panels.controls.sections[0]!.controls.modes;
371
+ const compoundSchema = {
372
+ ...schema,
373
+ panels: {
374
+ ...schema.panels,
375
+ controls: {
376
+ ...schema.panels.controls,
377
+ sections: [
378
+ {
379
+ ...schema.panels.controls.sections[0],
380
+ controls: {
381
+ modes: {
382
+ ...modes,
383
+ defaultValue: [{ enabled: true, mode: "default" }],
384
+ itemControl: undefined,
385
+ itemControls: {
386
+ enabled: {
387
+ defaultValue: true,
388
+ label: "Enabled",
389
+ type: "switch" as const,
390
+ },
391
+ mode: modes.itemControl,
392
+ },
393
+ },
394
+ },
395
+ },
396
+ ],
397
+ },
398
+ },
399
+ };
400
+ const fitGuidance = getToolcraftSegmentedStaticFitError(
401
+ modes.itemControl,
402
+ );
403
+
404
+ expect(fitGuidance).not.toBeNull();
405
+ expect(
406
+ validateContractAcceptance({
407
+ schema: compoundSchema,
408
+ acceptance: collectionModesAcceptance,
409
+ }),
410
+ ).toEqual(
411
+ expect.arrayContaining([
412
+ `Style / modes (appearance.modes) itemControls.mode ${fitGuidance}`,
413
+ ]),
414
+ );
415
+ });
416
+
362
417
  it.each([
363
418
  { labels: BOUNDARY_LABELS, type: "segmented" },
364
419
  { labels: SCREENSHOT_LABELS, type: "select" },
365
- ])(
420
+ ] as const)(
366
421
  "does not report itemControl fit guidance for nested $type",
367
422
  ({ labels, type }) => {
368
423
  const errors = validateContractAcceptance({
@@ -160,115 +160,4 @@ describe("starter acceptance control state contract", () => {
160
160
  );
161
161
  });
162
162
 
163
- it("requires visibleWhen controls to reference a real target and prove hidden behavior", () => {
164
- const schemaWithVisibleDependency = defineContractSchemaFixture({
165
- canvas: { enabled: true },
166
- panels: {
167
- controls: {
168
- sections: [
169
- {
170
- controls: {
171
- shadeCount: {
172
- defaultValue: 2,
173
- label: "Shades",
174
- max: 5,
175
- min: 1,
176
- step: 1,
177
- target: "shapes.shadeCount",
178
- type: "slider",
179
- variant: "discrete",
180
- },
181
- shade3: {
182
- defaultValue: { hex: "#BBBBBB" },
183
- label: "Shade 3",
184
- target: "shapes.color3",
185
- type: "color",
186
- visibleWhen: {
187
- greaterThanOrEqual: 3,
188
- target: "shapes.count",
189
- },
190
- },
191
- },
192
- title: "Shapes",
193
- },
194
- ],
195
- title: "Controls",
196
- },
197
- },
198
- });
199
-
200
- expect(
201
- validateContractAcceptance({
202
- schema: schemaWithVisibleDependency,
203
- acceptance: [
204
- makeControlAcceptance("shapes.shadeCount", "slider"),
205
- makeControlAcceptance("shapes.color3", "color"),
206
- ],
207
- }),
208
- ).toEqual(
209
- expect.arrayContaining([
210
- expect.stringContaining(
211
- "visibleWhen target shapes.count does not match another schema control target",
212
- ),
213
- expect.stringContaining(
214
- "uses visibleWhen and must declare visibilityCoverage",
215
- ),
216
- ]),
217
- );
218
-
219
- const schemaWithValidVisibleDependency = defineContractSchemaFixture({
220
- canvas: { enabled: true },
221
- panels: {
222
- controls: {
223
- sections: [
224
- {
225
- controls: {
226
- shadeCount: {
227
- defaultValue: 2,
228
- label: "Shades",
229
- max: 5,
230
- min: 1,
231
- step: 1,
232
- target: "shapes.shadeCount",
233
- type: "slider",
234
- variant: "discrete",
235
- },
236
- shade3: {
237
- defaultValue: { hex: "#BBBBBB" },
238
- label: "Shade 3",
239
- target: "shapes.color3",
240
- type: "color",
241
- visibleWhen: {
242
- greaterThanOrEqual: 3,
243
- target: "shapes.shadeCount",
244
- },
245
- },
246
- },
247
- title: "Shapes",
248
- },
249
- ],
250
- title: "Controls",
251
- },
252
- },
253
- });
254
- const errors = validateContractAcceptance({
255
- schema: schemaWithValidVisibleDependency,
256
- acceptance: [
257
- makeControlAcceptance("shapes.shadeCount", "slider"),
258
- {
259
- ...makeControlAcceptance("shapes.color3", "color"),
260
- expectedObservable: "Третий цвет доступен только для соответствующего состояния продукта.",
261
- userAction: "Переключить количество оттенков и проверить оба состояния.",
262
- visibilityCoverage: ["hidden", "visible"],
263
- },
264
- ],
265
- });
266
-
267
- expect(errors).not.toEqual(
268
- expect.arrayContaining([
269
- expect.stringContaining("visibleWhen target"),
270
- expect.stringContaining("uses visibleWhen and must declare visibilityCoverage"),
271
- ]),
272
- );
273
- });
274
163
  });
@@ -6,7 +6,7 @@ import {
6
6
  } from "./starter-acceptance.contract-fixtures";
7
7
 
8
8
  describe("starter acceptance custom control collection contract", () => {
9
- it("requires collection-like custom controls to compare actions and collectionActions", () => {
9
+ it("requires collection-like custom controls to compare both cardinality owners and actions", () => {
10
10
  const schema = defineContractSchemaFixture({
11
11
  canvas: { enabled: true },
12
12
  panels: {
@@ -71,6 +71,7 @@ describe("starter acceptance custom control collection contract", () => {
71
71
  ],
72
72
  }),
73
73
  ).toEqual([
74
+ "Masks / maskEditor (masks) builtInFitCheck.checkedBuiltIns must include sourceCollection when the custom control owns a repeated runtime item set whose cardinality could be source-owned.",
74
75
  "Masks / maskEditor (masks) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
75
76
  "Masks / maskEditor (masks) builtInFitCheck.checkedBuiltIns must include actions when the custom control exposes local command buttons such as add, remove, delete, duplicate, sort, normalize, or clear.",
76
77
  ]);
@@ -135,6 +136,7 @@ describe("starter acceptance custom control collection contract", () => {
135
136
  ],
136
137
  }),
137
138
  ).toEqual([
139
+ "Pattern / patternSet (pattern.set) builtInFitCheck.checkedBuiltIns must include sourceCollection when the custom control owns a repeated runtime item set whose cardinality could be source-owned.",
138
140
  "Pattern / patternSet (pattern.set) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
139
141
  ]);
140
142
  });
@@ -48,7 +48,7 @@ describe("starter acceptance valid custom control contract", () => {
48
48
  "reorder",
49
49
  "selection",
50
50
  ],
51
- checkedBuiltIns: ["actions", "collectionActions", "fileDrop", "imagePicker", "select"],
51
+ checkedBuiltIns: ["actions", "collectionActions", "fileDrop", "imagePicker", "select", "sourceCollection"],
52
52
  closestBuiltIn: "fileDrop",
53
53
  productObservable: "Ordering uploaded glyphs changes the rendered glyph ramp output.",
54
54
  whyInsufficient: "FileDrop imports source files, but it does not provide density ordering, per-glyph preview, reorder, and remove behavior in one runtime value.",
@@ -1,3 +1,25 @@
1
+ import type {
2
+ ToolcraftArtifactExportIntent,
3
+ ToolcraftProductReadiness,
4
+ } from "./acceptance/types";
5
+
6
+ export function makeExportSettingsProductReadiness(
7
+ exportIntent: ToolcraftArtifactExportIntent,
8
+ ): Extract<ToolcraftProductReadiness, { mode: "product" }> {
9
+ return {
10
+ exportIntent,
11
+ interactionOwnership: [],
12
+ mode: "product",
13
+ productName: "Export settings fixture",
14
+ productSummary: "A synthetic product for image and video export settings.",
15
+ requestedBehavior: "Export the explicitly requested artifact types.",
16
+ viewInteraction: {
17
+ mode: "non-spatial",
18
+ reason: "The synthetic output is two-dimensional.",
19
+ },
20
+ };
21
+ }
22
+
1
23
  export function makeBackgroundSection() {
2
24
  return {
3
25
  controls: {
@@ -3,12 +3,26 @@ import { describe, expect, it } from "vitest";
3
3
  import { defineContractSchemaFixture, validateContractAcceptance } from "./starter-acceptance.contract-fixtures";
4
4
  import {
5
5
  makeBackgroundSection,
6
+ makeExportSettingsProductReadiness,
6
7
  makeImageExportSection,
7
8
  makeVideoExportSection,
8
9
  } from "./starter-acceptance.export-test-utils";
9
10
  import { makeControlAcceptance } from "./starter-acceptance.test-utils";
10
11
 
11
- describe("Toolcraft image and video export settings acceptance contract", () => {
12
+ const imageOnlyProductReadiness = makeExportSettingsProductReadiness({
13
+ image: { mode: "toolcraft-default" },
14
+ video: { mode: "not-requested" },
15
+ });
16
+
17
+ const imageAndVideoProductReadiness = makeExportSettingsProductReadiness({
18
+ image: { mode: "toolcraft-default" },
19
+ video: {
20
+ evidence: "The user explicitly requested video delivery.",
21
+ mode: "user-requested",
22
+ },
23
+ });
24
+
25
+ describe("Toolcraft image export settings acceptance contract", () => {
12
26
  it("requires still-output apps to expose image export format and resolution settings", () => {
13
27
  const schemaWithoutImageExportSettings = defineContractSchemaFixture({
14
28
  canvas: {
@@ -58,13 +72,15 @@ describe("Toolcraft image and video export settings acceptance contract", () =>
58
72
  componentType: "panelActions",
59
73
  evidence: "exported-bytes",
60
74
  expectedObservable: "Export PNG creates output bytes and reads image format, image resolution, background color, and include-background state.",
75
+ exportArtifactCoverage: "all-required-image-export-behavior",
61
76
  fixture: "export fixture",
62
77
  id: "actions.output",
63
78
  kind: "control",
64
79
  target: "actions.output",
65
- userAction: "Toggle Include off, verify preview has no product background, export PNG with alpha, and verify video export keeps the background.",
80
+ userAction: "Toggle Include off, verify preview has no product background, export PNG with alpha, and verify the image artifact preserves transparency.",
66
81
  },
67
82
  ],
83
+ productReadiness: imageOnlyProductReadiness,
68
84
  }),
69
85
  ).toEqual(
70
86
  expect.arrayContaining([
@@ -142,6 +158,7 @@ describe("Toolcraft image and video export settings acceptance contract", () =>
142
158
  componentType: "panelActions",
143
159
  evidence: "exported-bytes",
144
160
  expectedObservable: "Export PNG creates output bytes and reads image format, image resolution, background color, and include-background state. JPG changes file type; 8K changes exported pixel dimensions to an 8192px long edge.",
161
+ exportArtifactCoverage: "all-required-image-export-behavior",
145
162
  fixture: "export fixture",
146
163
  id: "actions.output",
147
164
  kind: "control",
@@ -149,6 +166,7 @@ describe("Toolcraft image and video export settings acceptance contract", () =>
149
166
  userAction: "Set format to JPG and resolution to 8K, then export and decode the output image to verify file type and long-edge dimensions.",
150
167
  },
151
168
  ],
169
+ productReadiness: imageOnlyProductReadiness,
152
170
  }),
153
171
  ).not.toEqual(
154
172
  expect.arrayContaining([
@@ -159,7 +177,7 @@ describe("Toolcraft image and video export settings acceptance contract", () =>
159
177
  );
160
178
  });
161
179
 
162
- it("requires animated apps with PNG and video actions to expose Image Export before Video Export", () => {
180
+ it("requires explicit image-plus-video delivery to expose Image Export before Video Export", () => {
163
181
  const schemaWithoutImageSettings = defineContractSchemaFixture({
164
182
  canvas: {
165
183
  enabled: true,
@@ -211,19 +229,24 @@ describe("Toolcraft image and video export settings acceptance contract", () =>
211
229
  {
212
230
  actionCoverage: ["export.video", "export.png"],
213
231
  automated: true,
214
- automatedTestName: "exports animated and still output",
232
+ automatedTestName: "exports image and explicitly requested video output",
215
233
  browser: true,
216
234
  browserTestName: "browser: exports video and image output",
217
235
  componentType: "panelActions",
218
236
  evidence: "exported-bytes",
219
237
  expectedObservable: "Export Video and Export PNG both create output bytes and read their runtime export settings.",
220
- fixture: "animated export fixture",
238
+ exportArtifactCoverage: [
239
+ "all-required-image-export-behavior",
240
+ "all-required-video-export-behavior",
241
+ ],
242
+ fixture: "explicit image-plus-video export fixture",
221
243
  id: "actions.output",
222
244
  kind: "control",
223
245
  target: "actions.output",
224
- userAction: "Export video and PNG from the same timeline state, then verify both files exist.",
246
+ userAction: "Export video and PNG with their configured settings, then verify both files exist.",
225
247
  },
226
248
  ],
249
+ productReadiness: imageAndVideoProductReadiness,
227
250
  }),
228
251
  ).toEqual(
229
252
  expect.arrayContaining([
@@ -234,7 +257,7 @@ describe("Toolcraft image and video export settings acceptance contract", () =>
234
257
  );
235
258
  });
236
259
 
237
- it("accepts animated apps with Image Export immediately before Video Export", () => {
260
+ it("accepts explicit image-plus-video delivery with Image Export immediately before Video Export", () => {
238
261
  const schemaWithDualExportSettings = defineContractSchemaFixture({
239
262
  canvas: {
240
263
  enabled: true,
@@ -303,19 +326,24 @@ describe("Toolcraft image and video export settings acceptance contract", () =>
303
326
  {
304
327
  actionCoverage: ["export.video", "export.png"],
305
328
  automated: true,
306
- automatedTestName: "exports animated and still output",
329
+ automatedTestName: "exports image and explicitly requested video output",
307
330
  browser: true,
308
331
  browserTestName: "browser: exports video and image output",
309
332
  componentType: "panelActions",
310
333
  evidence: "exported-bytes",
311
334
  expectedObservable: "Export Video and Export PNG both create output bytes and read their runtime export settings.",
312
- fixture: "animated export fixture",
335
+ exportArtifactCoverage: [
336
+ "all-required-image-export-behavior",
337
+ "all-required-video-export-behavior",
338
+ ],
339
+ fixture: "explicit image-plus-video export fixture",
313
340
  id: "actions.output",
314
341
  kind: "control",
315
342
  target: "actions.output",
316
343
  userAction: "Choose JPG and 8K, choose MP4 and Current, then export PNG and video and verify both outputs use their selected settings.",
317
344
  },
318
345
  ],
346
+ productReadiness: imageAndVideoProductReadiness,
319
347
  }),
320
348
  ).not.toEqual(
321
349
  expect.arrayContaining([
@@ -100,6 +100,10 @@ function readiness(
100
100
  interactionOwnership: readonly ToolcraftInteractionOwnershipEntry[],
101
101
  ): ToolcraftProductReadiness {
102
102
  return {
103
+ exportIntent: {
104
+ image: { mode: "toolcraft-default" },
105
+ video: { mode: "not-requested" },
106
+ },
103
107
  interactionOwnership,
104
108
  mode: "product",
105
109
  productName: "Interaction fixture",
@@ -151,6 +155,10 @@ function acceptance({
151
155
  describe("Toolcraft interaction surface ownership", () => {
152
156
  it("runs ownership validation through the protected acceptance pipeline", () => {
153
157
  const productReadiness = {
158
+ exportIntent: {
159
+ image: { mode: "toolcraft-default" },
160
+ video: { mode: "not-requested" },
161
+ },
154
162
  mode: "product",
155
163
  productName: "Legacy fixture",
156
164
  productSummary: "A product declaration created before interaction ownership.",
@@ -172,6 +180,10 @@ describe("Toolcraft interaction surface ownership", () => {
172
180
 
173
181
  it("rejects product readiness that omits the ownership inventory", () => {
174
182
  const productReadiness = {
183
+ exportIntent: {
184
+ image: { mode: "toolcraft-default" },
185
+ video: { mode: "not-requested" },
186
+ },
175
187
  mode: "product",
176
188
  productName: "Legacy fixture",
177
189
  productSummary: "A product declaration created before interaction ownership.",