@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
@@ -60,7 +60,11 @@ export function createSingleFileDropSchema({
60
60
  });
61
61
  }
62
62
 
63
- export function createMultipleFileDropSchema() {
63
+ export function createMultipleFileDropSchema({
64
+ collectionActions = false,
65
+ }: {
66
+ collectionActions?: boolean;
67
+ } = {}) {
64
68
  return defineContractSchemaFixture({
65
69
  canvas: { enabled: true },
66
70
  panels: {
@@ -69,6 +73,12 @@ export function createMultipleFileDropSchema() {
69
73
  {
70
74
  controls: {
71
75
  sources: {
76
+ ...(collectionActions
77
+ ? {
78
+ assetKind: "file" as const,
79
+ variant: "collection-actions",
80
+ }
81
+ : {}),
72
82
  defaultValue: [],
73
83
  label: "Source images",
74
84
  multiple: true,
@@ -15,6 +15,10 @@ import {
15
15
  } from "./starter-acceptance.media-upload.fixtures";
16
16
 
17
17
  const layeredMediaProductReadiness: ToolcraftProductReadiness = {
18
+ exportIntent: {
19
+ image: { mode: "toolcraft-default" },
20
+ video: { mode: "not-requested" },
21
+ },
18
22
  interactionOwnership: [
19
23
  {
20
24
  alternative: {
@@ -284,6 +288,30 @@ describe("Toolcraft media upload acceptance coverage", () => {
284
288
  ).toEqual([]);
285
289
  });
286
290
 
291
+ it("does not require reorder coverage from collection-actions upload slots", () => {
292
+ const schema = createMultipleFileDropSchema({ collectionActions: true });
293
+
294
+ const errors = validateContractAcceptance({
295
+ schema,
296
+ acceptance: [
297
+ createFileDropAcceptance({
298
+ automatedTestName: "source files add and remove slots",
299
+ browserTestName: "browser: source files add and remove slots",
300
+ expectedObservable:
301
+ "Compact plus adds an empty upload slot and compact minus removes the final slot or attached file.",
302
+ fixture: "source file fixture",
303
+ id: "media.sources",
304
+ mediaLifecycleCoverage: ["upload", "remove", "reset"],
305
+ target: "media.sources",
306
+ userAction:
307
+ "Add an upload slot, attach a file, remove the final slot, and reset.",
308
+ }),
309
+ ],
310
+ });
311
+
312
+ expect(errors.filter((error) => error.includes("reorder"))).toEqual([]);
313
+ });
314
+
287
315
  it("assigns reorder and selected image transform proof to Layers without duplicating fileDrop claims", () => {
288
316
  const fileDropSchema = createMultipleFileDropSchema();
289
317
  const layeredSchema = {
@@ -0,0 +1,384 @@
1
+ import type {
2
+ ToolcraftActionSchema,
3
+ ToolcraftAppSchema,
4
+ ToolcraftControlSectionSchema,
5
+ } from "@repo/toolcraft-runtime";
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ import {
9
+ getToolcraftOutputExportErrors,
10
+ schemaHasPngExportPanelAction,
11
+ schemaHasVideoExportPanelAction,
12
+ } from "./acceptance/output-export";
13
+ import { buildToolcraftOutputExportFacts } from "./acceptance/output-export-model";
14
+ import type {
15
+ ToolcraftArtifactExportIntent,
16
+ ToolcraftComponentAcceptance,
17
+ ToolcraftExportArtifactCoverage,
18
+ ToolcraftProductReadiness,
19
+ } from "./acceptance/types";
20
+ import { collectToolcraftVisibleAcceptanceControls } from "./acceptance/validate-coverage";
21
+ import { defineContractSchemaFixture } from "./starter-acceptance.contract-fixtures";
22
+ import {
23
+ makeBackgroundSection,
24
+ makeImageExportSection,
25
+ makeVideoExportSection,
26
+ } from "./starter-acceptance.export-test-utils";
27
+
28
+ type ProductReadiness = Extract<ToolcraftProductReadiness, { mode: "product" }>;
29
+
30
+ function makeProductReadiness(
31
+ exportIntent: ToolcraftArtifactExportIntent,
32
+ ): ProductReadiness {
33
+ return {
34
+ exportIntent,
35
+ interactionOwnership: [],
36
+ mode: "product",
37
+ productName: "Explicit export intent fixture",
38
+ productSummary: "A synthetic product for export-intent acceptance coverage.",
39
+ requestedBehavior: "Deliver only the explicitly configured artifact types.",
40
+ viewInteraction: {
41
+ mode: "non-spatial",
42
+ reason: "The synthetic output is two-dimensional.",
43
+ },
44
+ };
45
+ }
46
+
47
+ function makeOutputActionsSection({
48
+ image,
49
+ video,
50
+ }: {
51
+ image: boolean;
52
+ video: boolean;
53
+ }): ToolcraftControlSectionSchema {
54
+ const actions: ToolcraftActionSchema[] = [];
55
+
56
+ if (image) {
57
+ actions.push({
58
+ icon: "upload-simple",
59
+ label: "Export PNG",
60
+ role: "export-image",
61
+ value: "export.png",
62
+ });
63
+ }
64
+
65
+ if (video) {
66
+ actions.push({
67
+ icon: "upload-simple",
68
+ label: "Export Video",
69
+ role: "export-video",
70
+ value: "export.video",
71
+ });
72
+ }
73
+
74
+ return {
75
+ actionGroup: "secondary",
76
+ controls: {
77
+ outputActions: {
78
+ actions,
79
+ target: "actions.output",
80
+ type: "panelActions",
81
+ },
82
+ },
83
+ title: "Export",
84
+ };
85
+ }
86
+
87
+ function makeExportSchema({
88
+ imageAction = false,
89
+ imageSection = false,
90
+ timeline = false,
91
+ videoAction = false,
92
+ videoSection = false,
93
+ }: {
94
+ imageAction?: boolean;
95
+ imageSection?: boolean;
96
+ timeline?: boolean;
97
+ videoAction?: boolean;
98
+ videoSection?: boolean;
99
+ } = {}) {
100
+ const sections: ToolcraftControlSectionSchema[] = [];
101
+
102
+ if (imageAction || videoAction) {
103
+ sections.push(makeBackgroundSection());
104
+ }
105
+ if (imageSection) {
106
+ sections.push(makeImageExportSection());
107
+ }
108
+ if (videoSection) {
109
+ sections.push(makeVideoExportSection());
110
+ }
111
+ if (imageAction || videoAction) {
112
+ sections.push(makeOutputActionsSection({ image: imageAction, video: videoAction }));
113
+ }
114
+
115
+ const panels: ToolcraftAppSchema["panels"] = {
116
+ controls: { sections, title: "Controls" },
117
+ ...(timeline
118
+ ? {
119
+ timeline: {
120
+ defaultDurationSeconds: 8,
121
+ enabled: true,
122
+ mode: "playback",
123
+ },
124
+ }
125
+ : {}),
126
+ };
127
+
128
+ return defineContractSchemaFixture({
129
+ canvas: { enabled: true, sizing: { mode: "editable-output" } },
130
+ panels,
131
+ });
132
+ }
133
+
134
+ function makeArtifactCoverage({
135
+ image,
136
+ video,
137
+ }: {
138
+ image: boolean;
139
+ video: boolean;
140
+ }): readonly ToolcraftComponentAcceptance[] {
141
+ if (!image && !video) {
142
+ return [];
143
+ }
144
+
145
+ const exportArtifactCoverage: ToolcraftExportArtifactCoverage[] = [];
146
+
147
+ if (image) {
148
+ exportArtifactCoverage.push("all-required-image-export-behavior");
149
+ }
150
+ if (video) {
151
+ exportArtifactCoverage.push("all-required-video-export-behavior");
152
+ }
153
+
154
+ return [
155
+ {
156
+ actionCoverage: [
157
+ ...(image ? ["export.png"] : []),
158
+ ...(video ? ["export.video"] : []),
159
+ ],
160
+ automated: true,
161
+ automatedTestName: "exports the configured artifacts",
162
+ browser: true,
163
+ browserTestName: "browser: exports the configured artifacts",
164
+ componentType: "panelActions",
165
+ evidence: "exported-bytes",
166
+ expectedObservable: "Configured export actions create validated artifact bytes.",
167
+ exportArtifactCoverage,
168
+ fixture: "explicit export intent fixture",
169
+ id: "actions.output",
170
+ kind: "control",
171
+ target: "actions.output",
172
+ userAction: "Click each configured export action and inspect its artifact.",
173
+ },
174
+ ];
175
+ }
176
+
177
+ function getExplicitExportErrors({
178
+ intent,
179
+ schema,
180
+ }: {
181
+ intent: ToolcraftArtifactExportIntent;
182
+ schema: ReturnType<typeof defineContractSchemaFixture>;
183
+ }): string[] {
184
+ return getToolcraftOutputExportErrors({
185
+ acceptance: makeArtifactCoverage({
186
+ image: schemaHasPngExportPanelAction(schema),
187
+ video: schemaHasVideoExportPanelAction(schema),
188
+ }),
189
+ controls: collectToolcraftVisibleAcceptanceControls(schema),
190
+ productReadiness: makeProductReadiness(intent),
191
+ schema,
192
+ });
193
+ }
194
+
195
+ describe("Toolcraft explicit output export intent", () => {
196
+ it("rejects a video action and section when video delivery was not requested", () => {
197
+ const errors = getExplicitExportErrors({
198
+ intent: {
199
+ image: { mode: "toolcraft-default" },
200
+ video: { mode: "not-requested" },
201
+ },
202
+ schema: makeExportSchema({
203
+ imageAction: true,
204
+ imageSection: true,
205
+ videoAction: true,
206
+ videoSection: true,
207
+ }),
208
+ });
209
+
210
+ expect(errors).toEqual(
211
+ expect.arrayContaining([
212
+ 'Video export intent "not-requested" must not have an export-video panel action.',
213
+ 'Video export intent "not-requested" must not have a "Video Export" section.',
214
+ ]),
215
+ );
216
+ });
217
+
218
+ it("requires a video action and section when video delivery was explicitly requested", () => {
219
+ const errors = getExplicitExportErrors({
220
+ intent: {
221
+ image: { mode: "toolcraft-default" },
222
+ video: {
223
+ evidence: "The user explicitly requested MP4 delivery.",
224
+ mode: "user-requested",
225
+ },
226
+ },
227
+ schema: makeExportSchema({ imageAction: true, imageSection: true }),
228
+ });
229
+
230
+ expect(errors).toEqual(
231
+ expect.arrayContaining([
232
+ 'Video export intent "user-requested" requires an export-video panel action.',
233
+ 'Video export intent "user-requested" requires a "Video Export" section.',
234
+ 'Apps with Export Video must expose video export settings in a separate controls section titled "Video Export" directly above sticky footer export actions.',
235
+ 'The separate "Video Export" section must include a format control with target "export.video.format".',
236
+ 'The separate "Video Export" section must include a resolution control with target "export.video.resolution".',
237
+ ]),
238
+ );
239
+ });
240
+
241
+ it.each([
242
+ {
243
+ expected:
244
+ 'Image export intent "user-removed" must not have an export-image panel action.',
245
+ imageAction: true,
246
+ imageSection: false,
247
+ name: "action",
248
+ },
249
+ {
250
+ expected:
251
+ 'Image export intent "user-removed" must not have an "Image Export" section.',
252
+ imageAction: false,
253
+ imageSection: true,
254
+ name: "settings section",
255
+ },
256
+ ])("rejects an image $name after explicit image removal", ({
257
+ expected,
258
+ imageAction,
259
+ imageSection,
260
+ }) => {
261
+ expect(
262
+ getExplicitExportErrors({
263
+ intent: {
264
+ image: {
265
+ evidence: "The user explicitly requested no image artifact.",
266
+ mode: "user-removed",
267
+ },
268
+ video: { mode: "not-requested" },
269
+ },
270
+ schema: makeExportSchema({ imageAction, imageSection }),
271
+ }),
272
+ ).toContain(expected);
273
+ });
274
+
275
+ it("requires the default image action when image delivery was not removed", () => {
276
+ expect(
277
+ getExplicitExportErrors({
278
+ intent: {
279
+ image: { mode: "toolcraft-default" },
280
+ video: { mode: "not-requested" },
281
+ },
282
+ schema: makeExportSchema(),
283
+ }),
284
+ ).toContain(
285
+ 'Image export intent "toolcraft-default" requires an export-image panel action.',
286
+ );
287
+ });
288
+
289
+ it("surfaces blank explicit export evidence through output validation", () => {
290
+ expect(
291
+ getExplicitExportErrors({
292
+ intent: {
293
+ image: { evidence: " \n\t", mode: "user-removed" },
294
+ video: { mode: "not-requested" },
295
+ },
296
+ schema: makeExportSchema(),
297
+ }),
298
+ ).toContain(
299
+ "Image export user-removed intent requires non-empty evidence.",
300
+ );
301
+ });
302
+
303
+ it("accepts explicit video-only delivery", () => {
304
+ expect(
305
+ getExplicitExportErrors({
306
+ intent: {
307
+ image: {
308
+ evidence: "The user explicitly requested no image artifact.",
309
+ mode: "user-removed",
310
+ },
311
+ video: {
312
+ evidence: "The user explicitly requested MP4 delivery.",
313
+ mode: "user-requested",
314
+ },
315
+ },
316
+ schema: makeExportSchema({ videoAction: true, videoSection: true }),
317
+ }),
318
+ ).toEqual([]);
319
+ });
320
+
321
+ it("accepts an explicitly no-export product without a universal PNG error", () => {
322
+ expect(
323
+ getExplicitExportErrors({
324
+ intent: {
325
+ image: {
326
+ evidence: "The user explicitly requested no downloadable artifacts.",
327
+ mode: "user-removed",
328
+ },
329
+ video: { mode: "not-requested" },
330
+ },
331
+ schema: makeExportSchema(),
332
+ }),
333
+ ).toEqual([]);
334
+ });
335
+
336
+ it("does not infer video delivery from an enabled playback timeline", () => {
337
+ const errors = getExplicitExportErrors({
338
+ intent: {
339
+ image: { mode: "toolcraft-default" },
340
+ video: { mode: "not-requested" },
341
+ },
342
+ schema: makeExportSchema({
343
+ imageAction: true,
344
+ imageSection: true,
345
+ timeline: true,
346
+ }),
347
+ });
348
+
349
+ expect(errors).toEqual([]);
350
+ expect(errors.join("\n")).not.toContain("Video Export");
351
+ expect(errors.join("\n")).not.toContain("all-required-video-export-behavior");
352
+ });
353
+
354
+ it("accepts explicit image and video delivery", () => {
355
+ expect(
356
+ getExplicitExportErrors({
357
+ intent: {
358
+ image: { mode: "toolcraft-default" },
359
+ video: {
360
+ evidence: "The user explicitly requested video delivery.",
361
+ mode: "user-requested",
362
+ },
363
+ },
364
+ schema: makeExportSchema({
365
+ imageAction: true,
366
+ imageSection: true,
367
+ videoAction: true,
368
+ videoSection: true,
369
+ }),
370
+ }),
371
+ ).toEqual([]);
372
+ });
373
+
374
+ it("derives the final export settings index from actions, not stray sections", () => {
375
+ const facts = buildToolcraftOutputExportFacts({
376
+ schema: makeExportSchema({ imageSection: true }),
377
+ });
378
+
379
+ expect(facts.hasImageExportAction).toBe(false);
380
+ expect(facts.hasVideoExportAction).toBe(false);
381
+ expect(facts.imageExportSectionIndex).toBeGreaterThanOrEqual(0);
382
+ expect(facts.finalExportSettingsIndex).toBe(-1);
383
+ });
384
+ });
@@ -54,6 +54,10 @@ describe("Toolcraft output export synthetic rules", () => {
54
54
  getToolcraftOutputExportErrors({
55
55
  acceptance: [],
56
56
  controls: [],
57
+ productReadiness: {
58
+ mode: "starter",
59
+ reason: "The synthetic starter verifies stray export-action coverage.",
60
+ },
57
61
  schema: videoOnlySchema,
58
62
  }),
59
63
  ).toEqual([