@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
@@ -12,13 +12,14 @@ import {
12
12
  getRequiredToolcraftControlPartCoverage,
13
13
  type ToolcraftBackgroundOutputCoverage,
14
14
  type ToolcraftComponentAcceptance,
15
- type ToolcraftConditionalVisibilityCoverage,
15
+ type ToolcraftControlSectionInventoryEntry,
16
16
  type ToolcraftInfinityCanvasCoverage,
17
17
  type ToolcraftModelImportCoverage,
18
18
  type ToolcraftOrientationGizmoCoverage,
19
19
  type ToolcraftTimelinePlaybackCoverage,
20
20
  TOOLCRAFT_REQUIRED_MODEL_IMPORT_COVERAGE,
21
21
  } from "../src/app/starter-acceptance";
22
+ import { expandToolcraftControlApplicabilityRequirements } from "./browser-runtime-applicability-requirements";
22
23
  import { getToolcraftPerformancePathTestName } from "./performance-path-helpers";
23
24
 
24
25
  type BrowserAcceptanceRequirementSource = Pick<
@@ -41,10 +42,14 @@ type BrowserAcceptanceRequirementSource = Pick<
41
42
  | "target"
42
43
  | "timelineCoverage"
43
44
  | "timelinePlaybackCoverage"
44
- | "visibilityCoverage"
45
- >;
45
+ > & {
46
+ kind?: ToolcraftComponentAcceptance["kind"];
47
+ };
46
48
 
47
- type BrowserSchemaRequirementSource = Pick<ResolvedToolcraftAppSchema, "panels">;
49
+ type BrowserSchemaRequirementSource = Pick<
50
+ ResolvedToolcraftAppSchema,
51
+ "canvas" | "panels"
52
+ >;
48
53
 
49
54
  function getAcceptanceEvidenceType(
50
55
  evidence: ToolcraftComponentAcceptance["evidence"],
@@ -93,18 +98,6 @@ const timelinePlaybackCoverage = Object.keys(
93
98
  timelineEvidenceTypeByCoverage,
94
99
  ) as Array<keyof typeof timelineEvidenceTypeByCoverage>;
95
100
 
96
- const conditionalVisibilityEvidenceTypeByCoverage = {
97
- hidden: "conditional-control-hidden",
98
- visible: "conditional-control-visible",
99
- } as const satisfies Record<
100
- ToolcraftConditionalVisibilityCoverage,
101
- ToolcraftBrowserRuntimeRequirement["evidenceType"]
102
- >;
103
-
104
- const conditionalVisibilityCoverage = Object.keys(
105
- conditionalVisibilityEvidenceTypeByCoverage,
106
- ) as ToolcraftConditionalVisibilityCoverage[];
107
-
108
101
  const backgroundOutputEvidenceTypeByCoverage = {
109
102
  "image-transparent-when-excluded": "background-image-transparency",
110
103
  "infinity-viewport-color-and-dependency": "background-infinity-viewport",
@@ -170,7 +163,7 @@ const modelImportEvidenceTypeByCoverage = {
170
163
  ToolcraftBrowserRuntimeRequirement["evidenceType"]
171
164
  >;
172
165
 
173
- export function deriveToolcraftBrowserRuntimeRequirements(
166
+ function deriveUnqualifiedToolcraftBrowserRuntimeRequirements(
174
167
  acceptance: readonly BrowserAcceptanceRequirementSource[],
175
168
  schema?: BrowserSchemaRequirementSource,
176
169
  ): ToolcraftBrowserRuntimeRequirement[] {
@@ -237,15 +230,6 @@ export function deriveToolcraftBrowserRuntimeRequirements(
237
230
  if (entry.referenceCoverage || entry.referenceTimelineCoverage) {
238
231
  evidenceTypes.push("reference-parity");
239
232
  }
240
- const visibilityCoverage =
241
- entry.visibilityCoverage === "all-conditional-visibility"
242
- ? conditionalVisibilityCoverage
243
- : entry.visibilityCoverage ?? [];
244
- evidenceTypes.push(
245
- ...visibilityCoverage.map(
246
- (item) => conditionalVisibilityEvidenceTypeByCoverage[item],
247
- ),
248
- );
249
233
  const backgroundCoverage =
250
234
  entry.backgroundOutputCoverage === "all-required-background-output"
251
235
  ? backgroundOutputCoverage.filter(
@@ -342,6 +326,25 @@ export function deriveToolcraftBrowserRuntimeRequirements(
342
326
  });
343
327
  }
344
328
 
329
+ export function deriveToolcraftBrowserRuntimeRequirements(
330
+ acceptance: readonly BrowserAcceptanceRequirementSource[],
331
+ schema?: BrowserSchemaRequirementSource,
332
+ sectionInventory: readonly ToolcraftControlSectionInventoryEntry[] = [],
333
+ ): ToolcraftBrowserRuntimeRequirement[] {
334
+ return acceptance.flatMap((entry) => {
335
+ const templates = deriveUnqualifiedToolcraftBrowserRuntimeRequirements(
336
+ [entry],
337
+ schema,
338
+ );
339
+ return expandToolcraftControlApplicabilityRequirements({
340
+ entry,
341
+ schema,
342
+ sectionInventory,
343
+ templates,
344
+ });
345
+ });
346
+ }
347
+
345
348
  const pathProductOutcomeInteractions = new Set<
346
349
  ToolcraftPerformancePath["interaction"]
347
350
  >([
@@ -55,3 +55,28 @@ export async function setSyntheticControlOwner(
55
55
  { target, visible },
56
56
  );
57
57
  }
58
+
59
+ export async function setSyntheticSwitchControlOwner(
60
+ page: Page,
61
+ target: string,
62
+ checked: boolean,
63
+ ): Promise<void> {
64
+ await setSyntheticControlOwner(page, target, true);
65
+ await page.locator("[data-synthetic-target]").evaluateAll(
66
+ (owners, entry) => {
67
+ const owner = owners.find(
68
+ (candidate) =>
69
+ candidate.getAttribute("data-synthetic-target") === entry.target,
70
+ );
71
+ const button = owner?.querySelector("button");
72
+
73
+ if (!button) {
74
+ throw new Error(`Missing synthetic switch owner ${entry.target}.`);
75
+ }
76
+
77
+ button.setAttribute("aria-checked", String(entry.checked));
78
+ button.setAttribute("role", "switch");
79
+ },
80
+ { checked, target },
81
+ );
82
+ }
@@ -76,6 +76,7 @@
76
76
  { "type": "checkbox", "visualComponent": "Checkbox" },
77
77
  { "type": "actions", "visualComponent": "Actions" },
78
78
  { "type": "collectionActions", "visualComponent": "CollectionActions" },
79
+ { "type": "sourceCollection", "visualComponent": "ControlsPanelCollectionItems" },
79
80
  { "type": "panelActions", "visualComponent": "PanelActions" },
80
81
  { "type": "colorOpacity", "visualComponent": "ColorOpacity" },
81
82
  { "type": "palette", "visualComponent": "Palette" },
@@ -1,5 +1,3 @@
1
- import path from "node:path";
2
-
3
1
  import ts from "typescript";
4
2
 
5
3
  import {
@@ -8,9 +6,10 @@ import {
8
6
  } from "./toolcraft-integrity-policy.mjs";
9
7
  import {
10
8
  createToolcraftProductExportInspector,
11
- getToolcraftProductExportModuleKind,
12
9
  isForbiddenToolcraftRuntimeExport,
13
10
  } from "./toolcraft-product-export-boundary.mjs";
11
+ import { createToolcraftProductControlInspector } from "./toolcraft-product-control-boundary.mjs";
12
+ import { getToolcraftSensitiveModuleKind } from "./toolcraft-product-boundary-module-policy.mjs";
14
13
  import { inspectToolcraftProductStyleNode } from "./toolcraft-product-style-boundary.mjs";
15
14
 
16
15
  const runtimeSurfaceNames = new Set(runtimeSurfaceComponentNames);
@@ -23,88 +22,9 @@ const runtimeModelPresentationInternalNames = new Set([
23
22
  "useToolcraftModelRenderHost",
24
23
  ]);
25
24
 
26
- function toPosixPath(value) {
27
- return value.split(path.sep).join("/");
28
- }
29
-
30
- function resolveSpecifierPath(sourceFilePath, moduleSpecifier, rootDir) {
31
- if (moduleSpecifier.startsWith("@/") || moduleSpecifier.startsWith("#/")) {
32
- return toPosixPath(path.resolve(rootDir, "src", moduleSpecifier.slice(2)));
33
- }
34
- if (moduleSpecifier.startsWith("/")) {
35
- return toPosixPath(path.resolve(rootDir, moduleSpecifier.slice(1)));
36
- }
37
- if (moduleSpecifier.startsWith(".")) {
38
- return toPosixPath(path.resolve(path.dirname(sourceFilePath), moduleSpecifier));
39
- }
40
- return null;
41
- }
42
-
43
- function getSensitiveModuleKind(sourceFilePath, moduleSpecifier, rootDir) {
44
- const normalizedSpecifier = moduleSpecifier.replaceAll("\\", "/");
45
- const resolvedSpecifier = resolveSpecifierPath(
46
- sourceFilePath,
47
- normalizedSpecifier,
48
- rootDir,
49
- );
50
- const exportModuleKind = getToolcraftProductExportModuleKind(
51
- normalizedSpecifier,
52
- resolvedSpecifier,
53
- );
54
- if (exportModuleKind) return exportModuleKind;
55
-
56
- if (
57
- /^@repo\/toolcraft-runtime\/react\/model-rendering(?:\/|$)/u.test(
58
- normalizedSpecifier,
59
- ) ||
60
- /^[#@]\/toolcraft\/runtime\/react\/model-rendering(?:\/|$)/u.test(
61
- normalizedSpecifier,
62
- ) ||
63
- resolvedSpecifier?.includes("/src/toolcraft/runtime/react/model-rendering")
64
- ) {
65
- return "runtime-model-presentation";
66
- }
67
-
68
- if (
69
- /^three\/(?:addons|examples\/jsm)\/loaders(?:\/|$)/u.test(
70
- normalizedSpecifier,
71
- ) ||
72
- /^@gltf-transform(?:\/|$)/u.test(normalizedSpecifier) ||
73
- /^@repo\/toolcraft-runtime\/model-import(?:\/|$)/u.test(
74
- normalizedSpecifier,
75
- ) ||
76
- /^[#@]\/toolcraft\/runtime\/model-import(?:\/|$)/u.test(
77
- normalizedSpecifier,
78
- ) ||
79
- resolvedSpecifier?.includes("/src/toolcraft/runtime/model-import")
80
- ) {
81
- return "runtime-model-import";
82
- }
83
-
84
- if (
85
- /^@repo\/toolcraft-runtime\/react(?:\/|$)/u.test(normalizedSpecifier) ||
86
- /^[#@]\/toolcraft\/runtime\/react(?:\/|$)/u.test(normalizedSpecifier) ||
87
- resolvedSpecifier?.includes("/src/toolcraft/runtime/react")
88
- ) {
89
- return "runtime-react";
90
- }
91
-
92
- if (
93
- /^@repo\/ui(?:\/controls(?:\/|$)|$)/u.test(normalizedSpecifier) ||
94
- /^[#@]\/toolcraft\/ui(?:\/components\/controls(?:\/|$)|\/controls(?:\/|$)|$)/u.test(
95
- normalizedSpecifier,
96
- ) ||
97
- resolvedSpecifier?.includes("/src/toolcraft/ui/components/controls") ||
98
- /\/src\/toolcraft\/ui$/u.test(resolvedSpecifier ?? "")
99
- ) {
100
- return "ui-controls";
101
- }
102
-
103
- return null;
104
- }
105
-
106
25
  function isForbiddenNamedExport(moduleKind, importedName) {
107
- if (isForbiddenToolcraftRuntimeExport(moduleKind, importedName)) return true;
26
+ if (moduleKind === "ui-control-implementation") return true;
27
+ if (isForbiddenToolcraftRuntimeExport(moduleKind)) return true;
108
28
  if (
109
29
  moduleKind === "runtime-model-import" ||
110
30
  moduleKind === "runtime-model-presentation"
@@ -117,6 +37,42 @@ function isForbiddenNamedExport(moduleKind, importedName) {
117
37
  importedName !== "createControlHistoryGroupId");
118
38
  }
119
39
 
40
+ function getImportViolationKind(moduleKind, importedName) {
41
+ switch (moduleKind) {
42
+ case "runtime-export-mechanics":
43
+ return "runtime-export-ownership";
44
+ case "runtime-model-presentation":
45
+ return "runtime-model-presentation-ownership";
46
+ case "runtime-react":
47
+ return runtimeModelPresentationInternalNames.has(importedName)
48
+ ? "runtime-model-presentation-ownership"
49
+ : "runtime-surface";
50
+ case "runtime-model-import":
51
+ return "runtime-model-import-ownership";
52
+ case "ui-control-implementation":
53
+ return "built-in-control-implementation";
54
+ default:
55
+ return "built-in-control";
56
+ }
57
+ }
58
+
59
+ function getViolationMessage(kind, subject, moduleSpecifier) {
60
+ switch (kind) {
61
+ case "runtime-export-ownership":
62
+ return `Product source must not own artifact encoding, recording, or download mechanics${subject} from ${moduleSpecifier}. Declare exportRenderer and let runtime export actions encode and download the artifact.`;
63
+ case "runtime-surface":
64
+ return `Product source must not import or re-export host-owned runtime surface${subject} from ${moduleSpecifier}. Use ToolcraftAppComposition, schema, canvasContent, controlRenderers, onPanelAction, and runtime commands.`;
65
+ case "runtime-model-presentation-ownership":
66
+ return `Product source must not import raw model render hosts or standard model canvas internals${subject} from ${moduleSpecifier}. Declare modelPresentation and use useToolcraftModelPresentationConsumer for visible custom model output.`;
67
+ case "runtime-model-import-ownership":
68
+ return `Product source must not import or re-export model loaders, topology/repair infrastructure, or runtime model-import internals${subject} from ${moduleSpecifier}. Declare a model fileDrop in schema and use the runtime-owned import, analysis, repair, persistence, preview, and export pipeline.`;
69
+ case "built-in-control-implementation":
70
+ return `Product source must not import or re-export a deep control implementation${subject} from ${moduleSpecifier}. Use the public schema control instead of copying or composing its private mechanics.`;
71
+ default:
72
+ return `Product source must not import or re-export built-in control${subject} from ${moduleSpecifier}. Declare it through schema or justify a true custom controlRenderer.`;
73
+ }
74
+ }
75
+
120
76
  function createViolation({
121
77
  getNodeLocation,
122
78
  importedName,
@@ -127,26 +83,17 @@ function createViolation({
127
83
  sourceFile,
128
84
  }) {
129
85
  const subject = importedName ? ` ${importedName}` : "";
130
- const message =
131
- kind === "runtime-export-ownership"
132
- ? `Product source must not own artifact encoding, recording, or download mechanics${subject} from ${moduleSpecifier}. Declare exportRenderer and let runtime export actions encode and download the artifact.`
133
- : kind === "runtime-surface"
134
- ? `Product source must not import or re-export host-owned runtime surface${subject} from ${moduleSpecifier}. Use ToolcraftAppComposition, schema, canvasContent, controlRenderers, onPanelAction, and runtime commands.`
135
- : kind === "runtime-model-presentation-ownership"
136
- ? `Product source must not import raw model render hosts or standard model canvas internals${subject} from ${moduleSpecifier}. Declare modelPresentation and use useToolcraftModelPresentationConsumer for visible custom model output.`
137
- : kind === "runtime-model-import-ownership"
138
- ? `Product source must not import or re-export model loaders, topology/repair infrastructure, or runtime model-import internals${subject} from ${moduleSpecifier}. Declare a model fileDrop in schema and use the runtime-owned import, analysis, repair, persistence, preview, and export pipeline.`
139
- : `Product source must not import or re-export built-in control${subject} from ${moduleSpecifier}. Declare it through schema or justify a true custom controlRenderer.`;
140
86
  return {
141
87
  ...getNodeLocation(sourceFile, node),
142
88
  kind,
143
- message,
89
+ message: getViolationMessage(kind, subject, moduleSpecifier),
144
90
  repoPath,
145
91
  };
146
92
  }
147
93
 
148
94
  export function inspectToolcraftProductSource({
149
95
  absolutePath,
96
+ checker,
150
97
  getNodeLocation,
151
98
  repoPath,
152
99
  resolveStaticString,
@@ -159,6 +106,13 @@ export function inspectToolcraftProductSource({
159
106
  repoPath,
160
107
  sourceFile,
161
108
  });
109
+ const inspectControls = createToolcraftProductControlInspector({
110
+ checker,
111
+ getNodeLocation,
112
+ repoPath,
113
+ resolveStaticString,
114
+ sourceFile,
115
+ });
162
116
 
163
117
  function report(node, moduleSpecifier, moduleKind, importedName) {
164
118
  if (importedName && !isForbiddenNamedExport(moduleKind, importedName)) {
@@ -168,19 +122,7 @@ export function inspectToolcraftProductSource({
168
122
  createViolation({
169
123
  getNodeLocation,
170
124
  importedName,
171
- kind:
172
- moduleKind === "runtime-export-api" ||
173
- moduleKind === "runtime-export-mechanics"
174
- ? "runtime-export-ownership"
175
- : moduleKind === "runtime-model-presentation"
176
- ? "runtime-model-presentation-ownership"
177
- : moduleKind === "runtime-react"
178
- ? runtimeModelPresentationInternalNames.has(importedName)
179
- ? "runtime-model-presentation-ownership"
180
- : "runtime-surface"
181
- : moduleKind === "runtime-model-import"
182
- ? "runtime-model-import-ownership"
183
- : "built-in-control",
125
+ kind: getImportViolationKind(moduleKind, importedName),
184
126
  moduleSpecifier,
185
127
  node,
186
128
  repoPath,
@@ -190,7 +132,25 @@ export function inspectToolcraftProductSource({
190
132
  }
191
133
 
192
134
  function getModuleKind(moduleSpecifier) {
193
- return getSensitiveModuleKind(absolutePath, moduleSpecifier, rootDir);
135
+ return getToolcraftSensitiveModuleKind({
136
+ moduleSpecifier,
137
+ rootDir,
138
+ sourceFilePath: absolutePath,
139
+ });
140
+ }
141
+
142
+ function inspectModuleSpecifier(node, moduleSpecifier) {
143
+ const moduleKind = getModuleKind(moduleSpecifier);
144
+ if (!moduleKind) return null;
145
+ if (moduleKind === "ui-control-implementation") {
146
+ report(node, moduleSpecifier, moduleKind);
147
+ return null;
148
+ }
149
+ if (isForbiddenToolcraftRuntimeExport(moduleKind)) {
150
+ report(node, moduleSpecifier, moduleKind);
151
+ return null;
152
+ }
153
+ return moduleKind;
194
154
  }
195
155
 
196
156
  function visit(node) {
@@ -202,6 +162,7 @@ export function inspectToolcraftProductSource({
202
162
  sourceFile,
203
163
  }),
204
164
  ...inspectExportMechanics(node),
165
+ ...inspectControls.inspectNode(node),
205
166
  );
206
167
 
207
168
  if (
@@ -209,7 +170,7 @@ export function inspectToolcraftProductSource({
209
170
  ts.isStringLiteralLike(node.moduleSpecifier)
210
171
  ) {
211
172
  const moduleSpecifier = node.moduleSpecifier.text;
212
- const moduleKind = getModuleKind(moduleSpecifier);
173
+ const moduleKind = inspectModuleSpecifier(node, moduleSpecifier);
213
174
  const importClause = node.importClause;
214
175
 
215
176
  if (moduleKind && importClause && !importClause.isTypeOnly) {
@@ -238,7 +199,7 @@ export function inspectToolcraftProductSource({
238
199
  ts.isStringLiteralLike(node.moduleSpecifier)
239
200
  ) {
240
201
  const moduleSpecifier = node.moduleSpecifier.text;
241
- const moduleKind = getModuleKind(moduleSpecifier);
202
+ const moduleKind = inspectModuleSpecifier(node, moduleSpecifier);
242
203
 
243
204
  if (moduleKind && !node.isTypeOnly) {
244
205
  if (!node.exportClause || ts.isNamespaceExport(node.exportClause)) {
@@ -260,14 +221,23 @@ export function inspectToolcraftProductSource({
260
221
 
261
222
  if (
262
223
  ts.isImportEqualsDeclaration(node) &&
263
- !node.isTypeOnly &&
264
224
  ts.isExternalModuleReference(node.moduleReference) &&
265
225
  node.moduleReference.expression &&
266
226
  ts.isStringLiteralLike(node.moduleReference.expression)
267
227
  ) {
268
228
  const moduleSpecifier = node.moduleReference.expression.text;
269
- const moduleKind = getModuleKind(moduleSpecifier);
270
- if (moduleKind) report(node, moduleSpecifier, moduleKind);
229
+ const moduleKind = inspectModuleSpecifier(node, moduleSpecifier);
230
+ if (moduleKind && !node.isTypeOnly) {
231
+ report(node, moduleSpecifier, moduleKind);
232
+ }
233
+ }
234
+
235
+ if (
236
+ ts.isImportTypeNode(node) &&
237
+ ts.isLiteralTypeNode(node.argument) &&
238
+ ts.isStringLiteralLike(node.argument.literal)
239
+ ) {
240
+ inspectModuleSpecifier(node, node.argument.literal.text);
271
241
  }
272
242
 
273
243
  if (
@@ -288,7 +258,7 @@ export function inspectToolcraftProductSource({
288
258
  ts.forEachChild(node, visit);
289
259
  return;
290
260
  }
291
- const moduleKind = getModuleKind(moduleSpecifier);
261
+ const moduleKind = inspectModuleSpecifier(node, moduleSpecifier);
292
262
  if (moduleKind) report(node, moduleSpecifier, moduleKind);
293
263
  }
294
264
 
@@ -0,0 +1,101 @@
1
+ import path from "node:path";
2
+
3
+ import {
4
+ getToolcraftProductExportModuleKind,
5
+ } from "./toolcraft-product-export-boundary.mjs";
6
+ import {
7
+ isToolcraftPrivateControlImplementationModule,
8
+ } from "./toolcraft-product-control-boundary.mjs";
9
+
10
+ function toPosixPath(value) {
11
+ return value.split(path.sep).join("/");
12
+ }
13
+
14
+ function resolveSpecifierPath(sourceFilePath, moduleSpecifier, rootDir) {
15
+ if (moduleSpecifier.startsWith("@/") || moduleSpecifier.startsWith("#/")) {
16
+ return toPosixPath(path.resolve(rootDir, "src", moduleSpecifier.slice(2)));
17
+ }
18
+ if (moduleSpecifier.startsWith("/")) {
19
+ return toPosixPath(path.resolve(rootDir, moduleSpecifier.slice(1)));
20
+ }
21
+ if (moduleSpecifier.startsWith(".")) {
22
+ return toPosixPath(path.resolve(path.dirname(sourceFilePath), moduleSpecifier));
23
+ }
24
+ return null;
25
+ }
26
+
27
+ export function getToolcraftSensitiveModuleKind({
28
+ moduleSpecifier,
29
+ rootDir,
30
+ sourceFilePath,
31
+ }) {
32
+ const normalizedSpecifier = moduleSpecifier.replaceAll("\\", "/");
33
+ const resolvedSpecifier = resolveSpecifierPath(
34
+ sourceFilePath,
35
+ normalizedSpecifier,
36
+ rootDir,
37
+ );
38
+ const exportModuleKind = getToolcraftProductExportModuleKind(
39
+ normalizedSpecifier,
40
+ resolvedSpecifier,
41
+ );
42
+ if (exportModuleKind) return exportModuleKind;
43
+
44
+ if (
45
+ isToolcraftPrivateControlImplementationModule({
46
+ moduleSpecifier: normalizedSpecifier,
47
+ resolvedSpecifier,
48
+ })
49
+ ) {
50
+ return "ui-control-implementation";
51
+ }
52
+
53
+ if (
54
+ /^@repo\/toolcraft-runtime\/react\/model-rendering(?:\/|$)/u.test(
55
+ normalizedSpecifier,
56
+ ) ||
57
+ /^[#@]\/toolcraft\/runtime\/react\/model-rendering(?:\/|$)/u.test(
58
+ normalizedSpecifier,
59
+ ) ||
60
+ resolvedSpecifier?.includes("/src/toolcraft/runtime/react/model-rendering")
61
+ ) {
62
+ return "runtime-model-presentation";
63
+ }
64
+
65
+ if (
66
+ /^three\/(?:addons|examples\/jsm)\/loaders(?:\/|$)/u.test(
67
+ normalizedSpecifier,
68
+ ) ||
69
+ /^@gltf-transform(?:\/|$)/u.test(normalizedSpecifier) ||
70
+ /^@repo\/toolcraft-runtime\/model-import(?:\/|$)/u.test(
71
+ normalizedSpecifier,
72
+ ) ||
73
+ /^[#@]\/toolcraft\/runtime\/model-import(?:\/|$)/u.test(
74
+ normalizedSpecifier,
75
+ ) ||
76
+ resolvedSpecifier?.includes("/src/toolcraft/runtime/model-import")
77
+ ) {
78
+ return "runtime-model-import";
79
+ }
80
+
81
+ if (
82
+ /^@repo\/toolcraft-runtime\/react(?:\/|$)/u.test(normalizedSpecifier) ||
83
+ /^[#@]\/toolcraft\/runtime\/react(?:\/|$)/u.test(normalizedSpecifier) ||
84
+ resolvedSpecifier?.includes("/src/toolcraft/runtime/react")
85
+ ) {
86
+ return "runtime-react";
87
+ }
88
+
89
+ if (
90
+ /^@repo\/ui(?:\/controls(?:\/|$)|$)/u.test(normalizedSpecifier) ||
91
+ /^[#@]\/toolcraft\/ui(?:\/components\/controls(?:\/|$)|\/controls(?:\/|$)|$)/u.test(
92
+ normalizedSpecifier,
93
+ ) ||
94
+ resolvedSpecifier?.includes("/src/toolcraft/ui/components/controls") ||
95
+ /\/src\/toolcraft\/ui$/u.test(resolvedSpecifier ?? "")
96
+ ) {
97
+ return "ui-controls";
98
+ }
99
+
100
+ return null;
101
+ }
@@ -75,32 +75,6 @@ test("checks Vite root-absolute imports against the same product boundary", asyn
75
75
  );
76
76
  });
77
77
 
78
- test("finds built-in controls through re-exports, aliases, namespaces, and dynamic imports", async (context) => {
79
- const rootDir = await createFixture(context, {
80
- "src/features/control-bridge.ts": `
81
- export { SliderControl as ProductSlider } from "@repo/ui/controls";
82
- export { Color as ProductColor } from "@/toolcraft/ui";
83
- `,
84
- "src/features/dynamic.ts": `
85
- export async function loadControls() {
86
- return import("@/toolcraft/ui/components/controls/slider");
87
- }
88
- `,
89
- "src/features/namespace.ts": `
90
- import * as Controls from "@repo/ui";
91
- export const Slider = Controls.Slider;
92
- `,
93
- });
94
-
95
- const result = await evaluateToolcraftProductBoundary({ rootDir });
96
-
97
- assert.equal(result.violations.length, 4);
98
- assert.equal(
99
- result.violations.every((violation) => violation.kind === "built-in-control"),
100
- true,
101
- );
102
- });
103
-
104
78
  test("keeps model import, topology, and repair infrastructure runtime-owned", async (context) => {
105
79
  const rootDir = await createFixture(context, {
106
80
  "src/features/direct-loaders.ts": `
@@ -143,52 +117,6 @@ test("keeps model import, topology, and repair infrastructure runtime-owned", as
143
117
  );
144
118
  });
145
119
 
146
- test("keeps artifact encoding and download mechanics runtime-owned", async (context) => {
147
- const rootDir = await createFixture(context, {
148
- "src/features/export-mechanics.ts": `
149
- import { createToolcraftPngExportCanvas } from "@/toolcraft/runtime";
150
- import { Output } from "mediabunny";
151
- export function exportArtifact(canvas: HTMLCanvasElement) {
152
- const recorder = new MediaRecorder(canvas.captureStream());
153
- const encoder = new VideoEncoder({ error() {}, output() {} });
154
- canvas.toBlob(() => undefined);
155
- canvas.toDataURL();
156
- URL.createObjectURL(new Blob());
157
- void createToolcraftPngExportCanvas;
158
- void Output;
159
- void recorder;
160
- void encoder;
161
- }
162
- `,
163
- "src/features/safe-renderer.ts": `
164
- export const renderFrame = ({ context }) => context.fillRect(0, 0, 1, 1);
165
- `,
166
- "src/features/shadowed-globals.ts": `
167
- export function localNames(MediaRecorder, VideoEncoder, URL) {
168
- void new MediaRecorder();
169
- void new VideoEncoder();
170
- URL.createObjectURL();
171
- }
172
- `,
173
- });
174
-
175
- const result = await evaluateToolcraftProductBoundary({ rootDir });
176
-
177
- assert.equal(result.violations.length, 8);
178
- assert.equal(
179
- result.violations.every(
180
- (violation) => violation.kind === "runtime-export-ownership",
181
- ),
182
- true,
183
- );
184
- assert.equal(
185
- result.violations.some((violation) =>
186
- violation.repoPath.endsWith("shadowed-globals.ts"),
187
- ),
188
- false,
189
- );
190
- });
191
-
192
120
  test("ignores tests but checks a product bridge imported by production", async (context) => {
193
121
  const rootDir = await createFixture(context, {
194
122
  "src/features/bridge.ts": `