@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
@@ -1,7 +1,7 @@
1
- import type {
2
- ResolvedToolcraftAppSchema,
3
- ResolvedToolcraftControlSchema,
4
- ToolcraftControlSchema,
1
+ import {
2
+ type ResolvedToolcraftAppSchema,
3
+ type ResolvedToolcraftControlSchema,
4
+ type ToolcraftControlSchema,
5
5
  } from "@repo/toolcraft-runtime";
6
6
 
7
7
  import {
@@ -9,10 +9,23 @@ import {
9
9
  isToolcraftProductSectionControl,
10
10
  } from "./controls";
11
11
  import { getToolcraftSectionLabel } from "./sections";
12
- import { getToolcraftLooseTargetPrefix } from "./semantic";
12
+ import {
13
+ getToolcraftLooseTargetPrefix,
14
+ normalizeToolcraftSemanticText,
15
+ } from "./semantic";
13
16
 
14
17
  const maxInlineSwitchLabelLength = 16;
15
18
  const maxInlineSwitchLabelWordCount = 2;
19
+ const normalizedVideoExportSectionTitle =
20
+ normalizeToolcraftSemanticText("Video Export");
21
+
22
+ type ToolcraftControlsSection = NonNullable<
23
+ ResolvedToolcraftAppSchema["panels"]["controls"]
24
+ >["sections"][number];
25
+
26
+ type ToolcraftControlLayoutGroup = NonNullable<
27
+ ToolcraftControlsSection["layoutGroups"]
28
+ >[number];
16
29
 
17
30
  function getInlineSwitchLabelText(
18
31
  controlId: string,
@@ -56,8 +69,42 @@ function controlsShareToolcraftTargetEntity(
56
69
  return Boolean(firstPrefix && firstPrefix === secondPrefix);
57
70
  }
58
71
 
72
+ function isToolcraftVideoExportSegmentedInlinePair({
73
+ layoutGroup,
74
+ section,
75
+ segmentedControlId,
76
+ }: {
77
+ layoutGroup: ToolcraftControlLayoutGroup;
78
+ section: ToolcraftControlsSection;
79
+ segmentedControlId: string;
80
+ }): boolean {
81
+ if (
82
+ normalizeToolcraftSemanticText(section.title) !==
83
+ normalizedVideoExportSectionTitle ||
84
+ layoutGroup.columns !== 2 ||
85
+ layoutGroup.controls.length !== 2
86
+ ) {
87
+ return false;
88
+ }
89
+
90
+ const formatControl = section.controls[segmentedControlId];
91
+ const resolutionControlId = layoutGroup.controls.find(
92
+ (controlId) => controlId !== segmentedControlId,
93
+ );
94
+ const resolutionControl = resolutionControlId
95
+ ? section.controls[resolutionControlId]
96
+ : undefined;
97
+
98
+ return (
99
+ formatControl?.type === "segmented" &&
100
+ formatControl.target === "export.video.format" &&
101
+ resolutionControl?.type === "select" &&
102
+ resolutionControl.target === "export.video.resolution"
103
+ );
104
+ }
105
+
59
106
  export function sectionHasInlineLayoutGroupForPair(
60
- section: NonNullable<ResolvedToolcraftAppSchema["panels"]["controls"]>["sections"][number],
107
+ section: ToolcraftControlsSection,
61
108
  firstControlId: string,
62
109
  secondControlId: string,
63
110
  ): boolean {
@@ -95,7 +142,13 @@ export function getToolcraftInlineLayoutErrors(
95
142
  }
96
143
 
97
144
  const segmentedIds = layoutGroup.controls.filter(
98
- (controlId) => section.controls[controlId]?.type === "segmented",
145
+ (controlId) =>
146
+ section.controls[controlId]?.type === "segmented" &&
147
+ !isToolcraftVideoExportSegmentedInlinePair({
148
+ layoutGroup,
149
+ section,
150
+ segmentedControlId: controlId,
151
+ }),
99
152
  );
100
153
 
101
154
  if (segmentedIds.length > 0) {
@@ -181,8 +234,10 @@ export function getToolcraftInlineLayoutErrors(
181
234
  !secondControlId ||
182
235
  !firstControl ||
183
236
  !secondControl ||
184
- firstControl.visibleWhen ||
185
- secondControl.visibleWhen ||
237
+ firstControl.applicability.mode ===
238
+ "conditional" ||
239
+ secondControl.applicability.mode ===
240
+ "conditional" ||
186
241
  !isBooleanControl(firstControl) ||
187
242
  !isBooleanControl(secondControl) ||
188
243
  !isInlineSwitchLabelSafe(firstControlId, firstControl) ||
@@ -148,7 +148,11 @@ export function getFileDropLifecycleCoverageErrors({
148
148
  );
149
149
  }
150
150
 
151
- if (control.multiple === true && !layersOwnMediaManagement) {
151
+ if (
152
+ control.multiple === true &&
153
+ control.variant !== "collection-actions" &&
154
+ !layersOwnMediaManagement
155
+ ) {
152
156
  if (!coverage.has("reorder") || !coverage.has("order-output")) {
153
157
  errors.push(
154
158
  `${label} multiple fileDrop acceptance must prove thumbnail/file reorder updates runtime media order and that preview, renderer, or export consumes that order.`,
@@ -1,10 +1,13 @@
1
1
  import type {
2
2
  ResolvedToolcraftAppSchema,
3
- ToolcraftControlSchema,
3
+ ResolvedToolcraftControlSchema,
4
+ } from "@repo/toolcraft-runtime";
5
+ import {
6
+ areToolcraftPredicateSetsProvablyExclusive,
7
+ getToolcraftApplicabilityPredicates,
4
8
  } from "@repo/toolcraft-runtime";
5
9
 
6
10
  import { isToolcraftProductSectionControl } from "./controls";
7
- import { areToolcraftConditionSetsProvablyExclusive } from "./condition-exclusivity";
8
11
 
9
12
  const vectorOrbitIntentPattern =
10
13
  /\b(camera\s+orbit|object\s+orbit|model\s+orbit|3d\s+(?:orientation|rotation)|view\s+orientation|model\s+rotation|object\s+rotation)\b/i;
@@ -34,7 +37,7 @@ function getCrossLength(
34
37
  );
35
38
  }
36
39
 
37
- function hasValidOrientationDefault(control: ToolcraftControlSchema): boolean {
40
+ function hasValidOrientationDefault(control: ResolvedToolcraftControlSchema): boolean {
38
41
  if (!control.defaultValue || typeof control.defaultValue !== "object") {
39
42
  return false;
40
43
  }
@@ -55,7 +58,7 @@ function getOrientationControlErrors({
55
58
  controlId,
56
59
  sectionTitle,
57
60
  }: {
58
- control: ToolcraftControlSchema;
61
+ control: ResolvedToolcraftControlSchema;
59
62
  controlId: string;
60
63
  sectionTitle: string;
61
64
  }): string[] {
@@ -112,16 +115,20 @@ export function getToolcraftOrientationGizmoErrors(
112
115
  }
113
116
 
114
117
  const leftConditions = [
115
- left.section.visibleWhen,
116
- left.control.visibleWhen,
117
- ].filter((condition) => condition !== undefined);
118
+ ...(left.section.visibleWhen ? [left.section.visibleWhen] : []),
119
+ ...getToolcraftApplicabilityPredicates(
120
+ left.control.applicability,
121
+ ),
122
+ ];
118
123
  const rightConditions = [
119
- right.section.visibleWhen,
120
- right.control.visibleWhen,
121
- ].filter((condition) => condition !== undefined);
124
+ ...(right.section.visibleWhen ? [right.section.visibleWhen] : []),
125
+ ...getToolcraftApplicabilityPredicates(
126
+ right.control.applicability,
127
+ ),
128
+ ];
122
129
 
123
130
  if (
124
- !areToolcraftConditionSetsProvablyExclusive(
131
+ !areToolcraftPredicateSetsProvablyExclusive(
125
132
  leftConditions,
126
133
  rightConditions,
127
134
  )
@@ -83,7 +83,7 @@ export function getToolcraftOutputBackgroundErrors({
83
83
 
84
84
  if (!schemaHasOutputBackgroundColorControl(controls)) {
85
85
  errors.push(
86
- "Product apps with Export PNG must expose a user-facing background color control such as appearance.background or scene.background. Preview, PNG export, and video export must read that runtime value instead of hardcoding the product background.",
86
+ "Product apps with artifact export must expose a user-facing background color control such as appearance.background or scene.background. Preview, image export, and video export must read that runtime value instead of hardcoding the product background.",
87
87
  );
88
88
  }
89
89
 
@@ -103,7 +103,7 @@ export function getToolcraftOutputBackgroundErrors({
103
103
 
104
104
  if (!schemaHasOutputBackgroundToggleControl(controls)) {
105
105
  errors.push(
106
- 'Product apps with Export PNG must expose export.includeBackground in runtime Setup as a Switch labeled "Background". Runtime artifact export owns PNG alpha and preserves the background for JPEG and video; bounded live preview must use shouldIncludeToolcraftPreviewBackground(state).',
106
+ 'Product apps with artifact export must expose export.includeBackground in runtime Setup as a Switch labeled "Background". Runtime artifact export owns image alpha and preserves the background for opaque image formats and video; bounded live preview must use shouldIncludeToolcraftPreviewBackground(state).',
107
107
  );
108
108
  }
109
109
 
@@ -1,20 +1,27 @@
1
+ import type { ToolcraftArtifactExportDelivery } from "./artifact-export-intent";
1
2
  import type { ToolcraftOutputExportFacts } from "./output-export-model";
2
3
 
3
- export function getToolcraftOutputExportLayoutErrors(
4
- facts: ToolcraftOutputExportFacts,
5
- ): string[] {
4
+ export function getToolcraftOutputExportLayoutErrors({
5
+ delivery,
6
+ facts,
7
+ }: {
8
+ delivery: ToolcraftArtifactExportDelivery;
9
+ facts: ToolcraftOutputExportFacts;
10
+ }): string[] {
11
+ if (!delivery.imageEnabled && !delivery.videoEnabled) {
12
+ return [];
13
+ }
14
+
6
15
  const errors: string[] = [];
7
16
 
8
17
  if (facts.backgroundSection) {
9
18
  errors.push(
10
- 'Product apps with Export PNG must not render a separate "Background" section; runtime Setup owns Background, Infinity canvas, and Color.',
19
+ 'Product apps with artifact export must not render a separate "Background" section; runtime Setup owns Background, Infinity canvas, and Color.',
11
20
  );
12
21
  }
13
22
 
14
23
  if (!facts.setupSection || facts.setupSectionIndex !== 0) {
15
- errors.push(
16
- "Runtime Setup must remain the first controls block.",
17
- );
24
+ errors.push("Runtime Setup must remain the first controls block.");
18
25
  }
19
26
 
20
27
  if (
@@ -23,18 +30,19 @@ export function getToolcraftOutputExportLayoutErrors(
23
30
  facts.finalExportSettingsIndex !== facts.panelActionsSectionIndex - 1
24
31
  ) {
25
32
  errors.push(
26
- 'Export settings must sit directly above sticky footer actions: Image Export for still apps, or Video Export after Image Export for animated apps.',
33
+ "The final enabled artifact export settings section must sit directly above sticky footer actions.",
27
34
  );
28
35
  }
29
36
 
30
37
  if (
31
- facts.hasVideoExportAction &&
38
+ delivery.imageEnabled &&
39
+ delivery.videoEnabled &&
32
40
  facts.imageExportSectionIndex >= 0 &&
33
41
  facts.videoExportSectionIndex >= 0 &&
34
42
  facts.imageExportSectionIndex !== facts.videoExportSectionIndex - 1
35
43
  ) {
36
44
  errors.push(
37
- 'Animated apps with both Export PNG and Export Video must place Image Export immediately before Video Export.',
45
+ 'Products with image and video delivery must place "Image Export" immediately before "Video Export".',
38
46
  );
39
47
  }
40
48
 
@@ -4,6 +4,10 @@ import type {
4
4
  } from "@repo/toolcraft-runtime";
5
5
 
6
6
  import { getControlLabelText } from "./controls";
7
+ import {
8
+ schemaHasPngExportPanelAction,
9
+ schemaHasVideoExportPanelAction,
10
+ } from "./output-export-actions";
7
11
  import { normalizeToolcraftSemanticText } from "./semantic";
8
12
 
9
13
  export type ToolcraftControlsSection = NonNullable<
@@ -14,6 +18,7 @@ export type ToolcraftOutputExportFacts = {
14
18
  backgroundColorEntry: readonly [string, ToolcraftControlSchema] | undefined;
15
19
  backgroundSection: ToolcraftControlsSection | undefined;
16
20
  finalExportSettingsIndex: number;
21
+ hasImageExportAction: boolean;
17
22
  hasVideoExportAction: boolean;
18
23
  imageExportSection: ToolcraftControlsSection | undefined;
19
24
  imageExportSectionIndex: number;
@@ -25,7 +30,10 @@ export type ToolcraftOutputExportFacts = {
25
30
  setupSection: ToolcraftControlsSection | undefined;
26
31
  setupSectionIndex: number;
27
32
  timelineEntry: readonly [string, ToolcraftControlSchema] | undefined;
33
+ videoExportSection: ToolcraftControlsSection | undefined;
28
34
  videoExportSectionIndex: number;
35
+ videoFormatEntry: readonly [string, ToolcraftControlSchema] | undefined;
36
+ videoResolutionEntry: readonly [string, ToolcraftControlSchema] | undefined;
29
37
  };
30
38
 
31
39
  export function getSearchableControlText({
@@ -47,9 +55,13 @@ export function getSearchableControlText({
47
55
  .replace(/([a-z])([A-Z])/g, "$1 $2");
48
56
  }
49
57
 
50
- function getFirstPanelActionsSectionIndex(schema: ResolvedToolcraftAppSchema): number {
58
+ function getFirstPanelActionsSectionIndex(
59
+ schema: ResolvedToolcraftAppSchema,
60
+ ): number {
51
61
  return (schema.panels.controls?.sections ?? []).findIndex((section) =>
52
- Object.values(section.controls).some((control) => control.type === "panelActions"),
62
+ Object.values(section.controls).some(
63
+ (control) => control.type === "panelActions",
64
+ ),
53
65
  );
54
66
  }
55
67
 
@@ -60,7 +72,8 @@ function getSchemaControlsSectionByTitle(
60
72
  const normalizedTitle = normalizeToolcraftSemanticText(title);
61
73
 
62
74
  return (schema.panels.controls?.sections ?? []).find(
63
- (section) => normalizeToolcraftSemanticText(section.title) === normalizedTitle,
75
+ (section) =>
76
+ normalizeToolcraftSemanticText(section.title) === normalizedTitle,
64
77
  );
65
78
  }
66
79
 
@@ -83,7 +96,9 @@ function getSectionControlEntryByTarget(
83
96
  return undefined;
84
97
  }
85
98
 
86
- return Object.entries(section.controls).find(([, control]) => control.target === target);
99
+ return Object.entries(section.controls).find(
100
+ ([, control]) => control.target === target,
101
+ );
87
102
  }
88
103
 
89
104
  function getOutputBackgroundColorEntry(
@@ -107,24 +122,43 @@ function getOutputBackgroundColorEntry(
107
122
  }
108
123
 
109
124
  export function buildToolcraftOutputExportFacts({
110
- hasVideoExportAction,
111
125
  schema,
112
126
  }: {
113
- hasVideoExportAction: boolean;
114
127
  schema: ResolvedToolcraftAppSchema;
115
128
  }): ToolcraftOutputExportFacts {
116
- const backgroundSection = getSchemaControlsSectionByTitle(schema, "Background");
129
+ const backgroundSection = getSchemaControlsSectionByTitle(
130
+ schema,
131
+ "Background",
132
+ );
117
133
  const setupSection = getSchemaControlsSectionByTitle(schema, "Setup");
118
- const imageExportSection = getSchemaControlsSectionByTitle(schema, "Image Export");
119
- const imageExportSectionIndex = getSchemaControlsSectionIndexByTitle(schema, "Image Export");
120
- const videoExportSectionIndex = getSchemaControlsSectionIndexByTitle(schema, "Video Export");
134
+ const imageExportSection = getSchemaControlsSectionByTitle(
135
+ schema,
136
+ "Image Export",
137
+ );
138
+ const videoExportSection = getSchemaControlsSectionByTitle(
139
+ schema,
140
+ "Video Export",
141
+ );
142
+ const imageExportSectionIndex = getSchemaControlsSectionIndexByTitle(
143
+ schema,
144
+ "Image Export",
145
+ );
146
+ const videoExportSectionIndex = getSchemaControlsSectionIndexByTitle(
147
+ schema,
148
+ "Video Export",
149
+ );
150
+ const hasImageExportAction = schemaHasPngExportPanelAction(schema);
151
+ const hasVideoExportAction = schemaHasVideoExportPanelAction(schema);
121
152
 
122
153
  return {
123
154
  backgroundColorEntry: getOutputBackgroundColorEntry(setupSection),
124
155
  backgroundSection,
125
156
  finalExportSettingsIndex: hasVideoExportAction
126
157
  ? videoExportSectionIndex
127
- : imageExportSectionIndex,
158
+ : hasImageExportAction
159
+ ? imageExportSectionIndex
160
+ : -1,
161
+ hasImageExportAction,
128
162
  hasVideoExportAction,
129
163
  imageExportSection,
130
164
  imageExportSectionIndex,
@@ -151,6 +185,15 @@ export function buildToolcraftOutputExportFacts({
151
185
  setupSection,
152
186
  "panels.timeline.extended",
153
187
  ),
188
+ videoExportSection,
154
189
  videoExportSectionIndex,
190
+ videoFormatEntry: getSectionControlEntryByTarget(
191
+ videoExportSection,
192
+ "export.video.format",
193
+ ),
194
+ videoResolutionEntry: getSectionControlEntryByTarget(
195
+ videoExportSection,
196
+ "export.video.resolution",
197
+ ),
155
198
  };
156
199
  }
@@ -1,5 +1,6 @@
1
1
  import type { ResolvedToolcraftAppSchema } from "@repo/toolcraft-runtime";
2
2
 
3
+ import { validateToolcraftArtifactExportIntentCorrespondence } from "./artifact-export-intent";
3
4
  import {
4
5
  getToolcraftOutputBackgroundErrors,
5
6
  isOutputBackgroundToggleControl,
@@ -11,9 +12,11 @@ import {
11
12
  import { getToolcraftOutputExportLayoutErrors } from "./output-export-layout-rules";
12
13
  import { buildToolcraftOutputExportFacts } from "./output-export-model";
13
14
  import { getToolcraftImageExportErrors } from "./output-image-export-rules";
15
+ import { getToolcraftVideoExportErrors } from "./output-video-export-rules";
14
16
  import { getToolcraftExportArtifactCoverageErrors } from "./export-artifact-coverage";
15
17
  import type {
16
18
  ToolcraftComponentAcceptance,
19
+ ToolcraftProductReadiness,
17
20
  ToolcraftVisibleControl,
18
21
  } from "./types";
19
22
 
@@ -26,31 +29,40 @@ export {
26
29
  export function getToolcraftOutputExportErrors({
27
30
  acceptance,
28
31
  controls,
32
+ productReadiness,
29
33
  schema,
30
- hasVideoExportAction = schemaHasVideoExportPanelAction(schema),
31
34
  }: {
32
35
  acceptance: readonly ToolcraftComponentAcceptance[];
33
36
  controls: readonly ToolcraftVisibleControl[];
34
- hasVideoExportAction?: boolean;
37
+ productReadiness: ToolcraftProductReadiness;
35
38
  schema: ResolvedToolcraftAppSchema;
36
39
  }): string[] {
37
40
  const exportArtifactCoverageErrors = getToolcraftExportArtifactCoverageErrors({
38
41
  acceptance,
39
42
  schema,
40
43
  });
41
- if (!schemaHasPngExportPanelAction(schema)) {
44
+ if (productReadiness.mode === "starter") {
42
45
  return exportArtifactCoverageErrors;
43
46
  }
44
47
 
45
- const facts = buildToolcraftOutputExportFacts({
46
- hasVideoExportAction,
47
- schema,
48
- });
48
+ const facts = buildToolcraftOutputExportFacts({ schema });
49
+ const { delivery, errors: intentErrors } =
50
+ validateToolcraftArtifactExportIntentCorrespondence({
51
+ hasImageExportAction: facts.hasImageExportAction,
52
+ hasImageExportSection: facts.imageExportSection !== undefined,
53
+ hasVideoExportAction: facts.hasVideoExportAction,
54
+ hasVideoExportSection: facts.videoExportSection !== undefined,
55
+ intent: productReadiness.exportIntent,
56
+ });
49
57
 
50
58
  return [
51
- ...getToolcraftOutputExportLayoutErrors(facts),
52
- ...getToolcraftOutputBackgroundErrors({ controls, facts }),
53
- ...getToolcraftImageExportErrors(facts),
59
+ ...intentErrors,
60
+ ...(delivery.imageEnabled ? getToolcraftImageExportErrors(facts) : []),
61
+ ...(delivery.videoEnabled ? getToolcraftVideoExportErrors(facts) : []),
62
+ ...(delivery.imageEnabled || delivery.videoEnabled
63
+ ? getToolcraftOutputBackgroundErrors({ controls, facts })
64
+ : []),
65
+ ...getToolcraftOutputExportLayoutErrors({ delivery, facts }),
54
66
  ...exportArtifactCoverageErrors,
55
67
  ];
56
68
  }
@@ -0,0 +1,99 @@
1
+ import { sectionHasInlineLayoutGroupForPair } from "./inline-layout";
2
+ import type { ToolcraftOutputExportFacts } from "./output-export-model";
3
+
4
+ function hasExactOptionValueSet(
5
+ actual: readonly string[],
6
+ expected: readonly string[],
7
+ ): boolean {
8
+ return (
9
+ actual.length === expected.length &&
10
+ new Set(actual).size === expected.length &&
11
+ expected.every((value) => actual.includes(value))
12
+ );
13
+ }
14
+
15
+ export function getToolcraftVideoExportErrors(
16
+ facts: ToolcraftOutputExportFacts,
17
+ ): string[] {
18
+ const errors: string[] = [];
19
+ const videoFormatControl = facts.videoFormatEntry?.[1];
20
+ const videoResolutionControl = facts.videoResolutionEntry?.[1];
21
+ const videoFormatOptionValues =
22
+ videoFormatControl?.options?.map((option) => option.value.toLowerCase()) ??
23
+ [];
24
+ const videoResolutionOptionValues =
25
+ videoResolutionControl?.options?.map((option) =>
26
+ option.value.toLowerCase(),
27
+ ) ?? [];
28
+
29
+ if (!facts.videoExportSection) {
30
+ errors.push(
31
+ 'Apps with Export Video must expose video export settings in a separate controls section titled "Video Export" directly above sticky footer export actions.',
32
+ );
33
+ }
34
+
35
+ if (!videoFormatControl) {
36
+ errors.push(
37
+ 'The separate "Video Export" section must include a format control with target "export.video.format".',
38
+ );
39
+ } else {
40
+ if (
41
+ videoFormatControl.type !== "select" &&
42
+ videoFormatControl.type !== "segmented"
43
+ ) {
44
+ errors.push("Video Export format must be a Select or Segmented control.");
45
+ }
46
+
47
+ if (!hasExactOptionValueSet(videoFormatOptionValues, ["mp4", "webm"])) {
48
+ errors.push(
49
+ 'Video Export format options must be exactly "mp4" and "webm".',
50
+ );
51
+ }
52
+
53
+ if (videoFormatControl.defaultValue !== "mp4") {
54
+ errors.push('Video Export format must default to "mp4".');
55
+ }
56
+ }
57
+
58
+ if (!videoResolutionControl) {
59
+ errors.push(
60
+ 'The separate "Video Export" section must include a resolution control with target "export.video.resolution".',
61
+ );
62
+ } else {
63
+ if (videoResolutionControl.type !== "select") {
64
+ errors.push("Video Export resolution must be a Select control.");
65
+ }
66
+
67
+ if (
68
+ !hasExactOptionValueSet(videoResolutionOptionValues, ["current", "4k"])
69
+ ) {
70
+ errors.push(
71
+ 'Video Export resolution options must be exactly "current" and "4k".',
72
+ );
73
+ }
74
+
75
+ if (videoResolutionControl.defaultValue !== "current") {
76
+ errors.push('Video Export resolution must default to "current".');
77
+ }
78
+ }
79
+
80
+ const videoFormatControlId = facts.videoFormatEntry?.[0];
81
+ const videoResolutionControlId = facts.videoResolutionEntry?.[0];
82
+ const videoExportHasInlinePair =
83
+ facts.videoExportSection !== undefined &&
84
+ videoFormatControlId !== undefined &&
85
+ videoResolutionControlId !== undefined &&
86
+ sectionHasInlineLayoutGroupForPair(
87
+ facts.videoExportSection,
88
+ videoFormatControlId,
89
+ videoResolutionControlId,
90
+ );
91
+
92
+ if (!videoExportHasInlinePair) {
93
+ errors.push(
94
+ "Video Export format and resolution must render as one exact two-column inline row.",
95
+ );
96
+ }
97
+
98
+ return errors;
99
+ }
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  ToolcraftBuiltInControlType as RuntimeToolcraftBuiltInControlType,
3
3
  ToolcraftControlOrderRole,
4
- ToolcraftControlSchema,
4
+ ResolvedToolcraftControlSchema,
5
5
  ToolcraftPersistableStateSlice,
6
6
  } from "@repo/toolcraft-runtime";
7
7
  import { TOOLCRAFT_BUILT_IN_CONTROL_TYPES } from "@repo/toolcraft-runtime";
@@ -102,8 +102,6 @@ export type ToolcraftPersistenceCoverage = "reload";
102
102
 
103
103
  export type ToolcraftSettingsTransferCoverage = "opt-out";
104
104
 
105
- export type ToolcraftConditionalVisibilityCoverage = "hidden" | "visible";
106
-
107
105
  export type ToolcraftOrientationGizmoCoverage =
108
106
  | "axis-drag"
109
107
  | "axis-snap"
@@ -199,6 +197,9 @@ export type ToolcraftControlPartCoverage =
199
197
  | "anchorGrid.position"
200
198
  | "channelMixer.activeChannel"
201
199
  | "channelMixer.values"
200
+ | "collectionActions.add"
201
+ | "collectionActions.items"
202
+ | "collectionActions.remove"
202
203
  | "curves.activeChannel"
203
204
  | "curves.points"
204
205
  | "colorOpacity.hex"
@@ -222,6 +223,7 @@ export type ToolcraftControlPartCoverage =
222
223
  | "rangeInput.start"
223
224
  | "rangeSlider.lower"
224
225
  | "rangeSlider.upper"
226
+ | "sourceCollection.items"
225
227
  | "vector.x"
226
228
  | "vector.y";
227
229
 
@@ -272,12 +274,27 @@ export type ToolcraftTransferMode =
272
274
  videoReferenceStudy?: ToolcraftVideoReferenceStudyEvidence;
273
275
  };
274
276
 
277
+ export type ToolcraftImageExportIntent =
278
+ | Readonly<{ mode: "toolcraft-default" }>
279
+ | Readonly<{ evidence: string; mode: "user-requested" }>
280
+ | Readonly<{ evidence: string; mode: "user-removed" }>;
281
+
282
+ export type ToolcraftVideoExportIntent =
283
+ | Readonly<{ mode: "not-requested" }>
284
+ | Readonly<{ evidence: string; mode: "user-requested" }>;
285
+
286
+ export type ToolcraftArtifactExportIntent = Readonly<{
287
+ image: ToolcraftImageExportIntent;
288
+ video: ToolcraftVideoExportIntent;
289
+ }>;
290
+
275
291
  export type ToolcraftProductReadiness =
276
292
  | {
277
293
  mode: "starter";
278
294
  reason: string;
279
295
  }
280
296
  | {
297
+ exportIntent: ToolcraftArtifactExportIntent;
281
298
  mode: "product";
282
299
  interactionOwnership: readonly ToolcraftInteractionOwnershipEntry[];
283
300
  productName: string;
@@ -342,13 +359,10 @@ export type ToolcraftComponentAcceptance = {
342
359
  | "all-required-background-output"
343
360
  | readonly ToolcraftBackgroundOutputCoverage[];
344
361
  userAction: string;
345
- visibilityCoverage?:
346
- | "all-conditional-visibility"
347
- | readonly ToolcraftConditionalVisibilityCoverage[];
348
362
  };
349
363
 
350
364
  export type ToolcraftVisibleControl = {
351
- control: ToolcraftControlSchema;
365
+ control: ResolvedToolcraftControlSchema;
352
366
  controlId: string;
353
367
  sectionTitle?: string;
354
368
  };
@@ -363,7 +377,8 @@ export type ToolcraftControlOrderItem = {
363
377
  };
364
378
 
365
379
  export type ToolcraftControlSectionInventoryEntry = {
366
- entity?: string;
380
+ entity: string;
381
+ entityId: string;
367
382
  groupingReason: string;
368
383
  id: string;
369
384
  splitReason?: string;