@pixel-point/toolcraft 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/package.json +1 -1
  2. package/src/generate-test-doc-assertions.mjs +58 -68
  3. package/src/generate-test-doc-assertions.test.mjs +43 -0
  4. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  5. package/src/generate-test-file-assertions.mjs +26 -75
  6. package/src/generate-test-product-iteration.test.mjs +17 -0
  7. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  8. package/src/generate-test-standalone-assertions.mjs +10 -0
  9. package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
  10. package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
  11. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  12. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  13. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  14. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  15. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
  16. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  17. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
  18. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
  19. package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
  20. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  21. package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
  22. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  23. package/templates/runtime/export/export-background.ts +0 -19
  24. package/templates/runtime/export/export.test.ts +45 -42
  25. package/templates/runtime/export/index.ts +44 -16
  26. package/templates/runtime/index.ts +2 -0
  27. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  28. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
  29. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
  30. package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
  31. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  32. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  33. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  34. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  35. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  36. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  37. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  38. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  39. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  40. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
  41. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  42. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  43. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
  44. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
  45. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  46. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  47. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  48. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  49. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  50. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  51. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  52. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  53. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  54. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  55. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  56. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  57. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  58. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  59. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  60. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  61. package/templates/runtime/react/index.ts +4 -0
  62. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  63. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  64. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  65. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  66. package/templates/runtime/scene/scene-bounds.ts +31 -17
  67. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  68. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  69. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  70. package/templates/runtime/schema/control-applicability.ts +433 -0
  71. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  72. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  73. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  74. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
  75. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  76. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  77. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  78. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  79. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  80. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  81. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  82. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  83. package/templates/runtime/schema/types.ts +68 -13
  84. package/templates/starter/AGENTS.md +12 -10
  85. package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
  86. package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
  87. package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
  88. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  89. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  90. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  91. package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
  92. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
  93. package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
  94. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  95. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  96. package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
  97. package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
  98. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  99. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
  100. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  101. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
  102. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
  103. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  104. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  105. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
  106. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  107. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  108. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  109. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
  110. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
  111. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  112. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  113. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
  114. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  115. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
  116. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  117. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  118. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  119. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
  120. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  121. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  122. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  123. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
  124. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  125. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  126. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  127. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  128. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  129. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  130. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  131. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  132. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  133. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  134. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  135. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  136. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  137. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  138. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  139. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  140. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  141. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  142. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  143. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  144. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  145. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  146. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  147. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  148. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  149. package/templates/starter/src/app/acceptance/output-export.ts +22 -10
  150. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  151. package/templates/starter/src/app/acceptance/types.ts +23 -8
  152. package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
  153. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  154. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  155. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  156. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  157. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  158. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  159. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  160. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  161. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  162. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  163. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  164. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  165. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  166. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  167. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  168. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  169. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  170. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
  171. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  172. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  173. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  174. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  175. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  176. package/templates/starter/src/app/starter-acceptance.ts +11 -2
  177. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  178. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  179. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
  180. package/templates/starter/src/routes/index.tsx +1 -0
  181. package/templates/ui/components/composites/tabs.tsx +2 -2
  182. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  183. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  184. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  185. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  186. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  187. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  188. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  189. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  190. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  191. package/templates/ui/components/controls/index.ts +3 -0
  192. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  193. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  194. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  195. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  196. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
@@ -0,0 +1,182 @@
1
+ import ts from "typescript";
2
+
3
+ const workspaceUiPrimitiveModulePattern =
4
+ /^@repo\/ui(?:$|\/(?:src\/)?(?:components\/)?primitives(?:\/|$))/u;
5
+
6
+ function isToolcraftPublicUiPrimitiveModule(moduleSpecifier) {
7
+ const normalizedSpecifier = moduleSpecifier.replaceAll("\\", "/");
8
+
9
+ return (
10
+ workspaceUiPrimitiveModulePattern.test(normalizedSpecifier) ||
11
+ /^[#@]\/toolcraft\/ui$/u.test(normalizedSpecifier) ||
12
+ /^[#@]\/toolcraft\/ui\/primitives(?:\/|$)/u.test(normalizedSpecifier) ||
13
+ /^[#@]\/toolcraft\/ui\/components\/primitives(?:\/|$)/u.test(
14
+ normalizedSpecifier,
15
+ )
16
+ );
17
+ }
18
+
19
+ function unwrapExpression(node) {
20
+ let current = node;
21
+
22
+ while (
23
+ ts.isAwaitExpression(current) ||
24
+ ts.isParenthesizedExpression(current) ||
25
+ ts.isAsExpression(current) ||
26
+ ts.isTypeAssertionExpression(current) ||
27
+ ts.isSatisfiesExpression(current) ||
28
+ ts.isNonNullExpression(current)
29
+ ) {
30
+ current = current.expression;
31
+ }
32
+
33
+ return current;
34
+ }
35
+
36
+ function getImportModuleSpecifier(declaration) {
37
+ if (ts.isImportSpecifier(declaration)) {
38
+ const importDeclaration = declaration.parent.parent.parent;
39
+ return ts.isImportDeclaration(importDeclaration) &&
40
+ ts.isStringLiteralLike(importDeclaration.moduleSpecifier)
41
+ ? importDeclaration.moduleSpecifier.text
42
+ : null;
43
+ }
44
+
45
+ if (ts.isNamespaceImport(declaration)) {
46
+ const importDeclaration = declaration.parent.parent;
47
+ return ts.isImportDeclaration(importDeclaration) &&
48
+ ts.isStringLiteralLike(importDeclaration.moduleSpecifier)
49
+ ? importDeclaration.moduleSpecifier.text
50
+ : null;
51
+ }
52
+
53
+ if (ts.isImportClause(declaration)) {
54
+ const importDeclaration = declaration.parent;
55
+ return ts.isImportDeclaration(importDeclaration) &&
56
+ ts.isStringLiteralLike(importDeclaration.moduleSpecifier)
57
+ ? importDeclaration.moduleSpecifier.text
58
+ : null;
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ export function createToolcraftUiInputBindingTracker({
65
+ checker,
66
+ resolveStaticString,
67
+ }) {
68
+ const bindingKinds = new Map();
69
+ const resolvingSymbols = new Set();
70
+
71
+ function classifyExpression(expression) {
72
+ const current = unwrapExpression(expression);
73
+
74
+ if (ts.isIdentifier(current)) {
75
+ const symbol = checker.getSymbolAtLocation(current);
76
+ return symbol ? classifySymbol(symbol) : null;
77
+ }
78
+
79
+ if (
80
+ ts.isPropertyAccessExpression(current) &&
81
+ current.name.text === "Input" &&
82
+ classifyExpression(current.expression) === "namespace"
83
+ ) {
84
+ return "input";
85
+ }
86
+
87
+ if (
88
+ ts.isCallExpression(current) &&
89
+ current.arguments.length > 0 &&
90
+ (current.expression.kind === ts.SyntaxKind.ImportKeyword ||
91
+ (ts.isIdentifier(current.expression) &&
92
+ current.expression.text === "require"))
93
+ ) {
94
+ const moduleSpecifier = resolveStaticString(current.arguments[0]);
95
+ return moduleSpecifier &&
96
+ isToolcraftPublicUiPrimitiveModule(moduleSpecifier)
97
+ ? "namespace"
98
+ : null;
99
+ }
100
+
101
+ return null;
102
+ }
103
+
104
+ function classifyDeclaration(declaration) {
105
+ if (ts.isImportSpecifier(declaration)) {
106
+ const importedName = declaration.propertyName?.text ?? declaration.name.text;
107
+ const moduleSpecifier = getImportModuleSpecifier(declaration);
108
+ return importedName === "Input" &&
109
+ moduleSpecifier &&
110
+ isToolcraftPublicUiPrimitiveModule(moduleSpecifier)
111
+ ? "input"
112
+ : null;
113
+ }
114
+
115
+ if (ts.isNamespaceImport(declaration)) {
116
+ const moduleSpecifier = getImportModuleSpecifier(declaration);
117
+ return moduleSpecifier &&
118
+ isToolcraftPublicUiPrimitiveModule(moduleSpecifier)
119
+ ? "namespace"
120
+ : null;
121
+ }
122
+
123
+ if (ts.isImportClause(declaration)) {
124
+ const moduleSpecifier = getImportModuleSpecifier(declaration);
125
+ return moduleSpecifier &&
126
+ isToolcraftPublicUiPrimitiveModule(moduleSpecifier) &&
127
+ /(?:^|\/)input(?:\.[cm]?[jt]sx?)?$/iu.test(moduleSpecifier)
128
+ ? "input"
129
+ : null;
130
+ }
131
+
132
+ if (
133
+ ts.isImportEqualsDeclaration(declaration) &&
134
+ ts.isExternalModuleReference(declaration.moduleReference) &&
135
+ declaration.moduleReference.expression &&
136
+ ts.isStringLiteralLike(declaration.moduleReference.expression) &&
137
+ isToolcraftPublicUiPrimitiveModule(declaration.moduleReference.expression.text)
138
+ ) {
139
+ return "namespace";
140
+ }
141
+
142
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
143
+ return classifyExpression(declaration.initializer);
144
+ }
145
+
146
+ if (
147
+ ts.isBindingElement(declaration) &&
148
+ !declaration.dotDotDotToken &&
149
+ (declaration.propertyName?.getText() ?? declaration.name.getText()) ===
150
+ "Input"
151
+ ) {
152
+ const variableDeclaration = declaration.parent.parent;
153
+ return ts.isVariableDeclaration(variableDeclaration) &&
154
+ variableDeclaration.initializer &&
155
+ classifyExpression(variableDeclaration.initializer) === "namespace"
156
+ ? "input"
157
+ : null;
158
+ }
159
+
160
+ return null;
161
+ }
162
+
163
+ function classifySymbol(symbol) {
164
+ if (bindingKinds.has(symbol)) return bindingKinds.get(symbol);
165
+ if (resolvingSymbols.has(symbol)) return null;
166
+
167
+ resolvingSymbols.add(symbol);
168
+ const bindingKind =
169
+ symbol.declarations
170
+ ?.map(classifyDeclaration)
171
+ .find((candidate) => candidate !== null) ?? null;
172
+ resolvingSymbols.delete(symbol);
173
+ bindingKinds.set(symbol, bindingKind);
174
+ return bindingKind;
175
+ }
176
+
177
+ function isTrackedInputTag(tagName) {
178
+ return classifyExpression(tagName) === "input";
179
+ }
180
+
181
+ return { isTrackedInputTag };
182
+ }
@@ -0,0 +1,146 @@
1
+ import ts from "typescript";
2
+
3
+ import { createToolcraftUiInputBindingTracker } from "./toolcraft-product-control-bindings.mjs";
4
+
5
+ const workspacePrivateControlModulePattern =
6
+ /^@repo\/ui\/(?:src\/)?(?:components\/controls(?:\/|$)|controls\/)/u;
7
+
8
+ const forbiddenInputTypes = new Map([
9
+ ["checkbox", "checkbox or switch"],
10
+ ["color", "color or colorOpacity"],
11
+ ["file", "fileDrop"],
12
+ ["radio", "segmented or select"],
13
+ ["range", "slider or rangeSlider"],
14
+ ]);
15
+
16
+ function normalizeSpecifier(value) {
17
+ return value.replaceAll("\\", "/");
18
+ }
19
+
20
+ export function isToolcraftPrivateControlImplementationModule({
21
+ moduleSpecifier,
22
+ resolvedSpecifier,
23
+ }) {
24
+ const normalizedSpecifier = normalizeSpecifier(moduleSpecifier);
25
+ const normalizedResolvedSpecifier = resolvedSpecifier
26
+ ? normalizeSpecifier(resolvedSpecifier)
27
+ : "";
28
+
29
+ return (
30
+ workspacePrivateControlModulePattern.test(normalizedSpecifier) ||
31
+ /^[#@]\/toolcraft\/ui\/(?:components\/)?controls\//u.test(
32
+ normalizedSpecifier,
33
+ ) ||
34
+ /^[#@]\/toolcraft\/ui\/components\/controls(?:\/|$)/u.test(
35
+ normalizedSpecifier,
36
+ ) ||
37
+ /\/src\/toolcraft\/ui\/components\/controls(?:\/|$)/u.test(
38
+ normalizedResolvedSpecifier,
39
+ ) ||
40
+ /\/packages\/ui\/src\/components\/controls(?:\/|$)/u.test(
41
+ normalizedResolvedSpecifier,
42
+ )
43
+ );
44
+ }
45
+
46
+ function getJsxTagName(tagName) {
47
+ if (ts.isIdentifier(tagName)) return tagName.text;
48
+ if (ts.isPropertyAccessExpression(tagName)) return tagName.name.text;
49
+ return null;
50
+ }
51
+
52
+ export function createToolcraftProductControlInspector({
53
+ checker,
54
+ getNodeLocation,
55
+ repoPath,
56
+ resolveStaticString,
57
+ sourceFile,
58
+ }) {
59
+ const { isTrackedInputTag } = createToolcraftUiInputBindingTracker({
60
+ checker,
61
+ resolveStaticString,
62
+ });
63
+
64
+ function createViolation(node, message) {
65
+ return {
66
+ ...getNodeLocation(sourceFile, node),
67
+ kind: "native-control-recreation",
68
+ message,
69
+ repoPath,
70
+ };
71
+ }
72
+
73
+ function getInputType(attributes) {
74
+ let inputType = "text";
75
+ for (const property of attributes.properties) {
76
+ if (ts.isJsxSpreadAttribute(property)) {
77
+ inputType = null;
78
+ continue;
79
+ }
80
+ if (!ts.isIdentifier(property.name) || property.name.text !== "type") {
81
+ continue;
82
+ }
83
+ if (!property.initializer) {
84
+ inputType = null;
85
+ } else if (ts.isStringLiteral(property.initializer)) {
86
+ inputType = property.initializer.text;
87
+ } else if (
88
+ ts.isJsxExpression(property.initializer) &&
89
+ property.initializer.expression
90
+ ) {
91
+ inputType = resolveStaticString(property.initializer.expression) ?? null;
92
+ } else {
93
+ inputType = null;
94
+ }
95
+ }
96
+ return inputType?.toLowerCase() ?? null;
97
+ }
98
+
99
+ function inspectNode(node) {
100
+ if (!ts.isJsxOpeningElement(node) && !ts.isJsxSelfClosingElement(node)) {
101
+ return [];
102
+ }
103
+
104
+ const tagName = getJsxTagName(node.tagName);
105
+ if (tagName === "select") {
106
+ return [
107
+ createViolation(
108
+ node.tagName,
109
+ "Product source must not recreate the built-in Select with a native <select>. Declare a select schema control.",
110
+ ),
111
+ ];
112
+ }
113
+ if (tagName === "textarea") {
114
+ return [
115
+ createViolation(
116
+ node.tagName,
117
+ "Product source must not recreate built-in text or code controls with a native <textarea>. Declare a text or code schema control.",
118
+ ),
119
+ ];
120
+ }
121
+
122
+ if (tagName !== "input" && !isTrackedInputTag(node.tagName)) return [];
123
+
124
+ const inputType = getInputType(node.attributes);
125
+ if (inputType === null) {
126
+ return [
127
+ createViolation(
128
+ node.tagName,
129
+ "Product Input type must be statically resolvable so the Toolcraft boundary can verify that it does not recreate a built-in control.",
130
+ ),
131
+ ];
132
+ }
133
+
134
+ const builtInSchemaType = forbiddenInputTypes.get(inputType);
135
+ if (!builtInSchemaType) return [];
136
+
137
+ return [
138
+ createViolation(
139
+ node.tagName,
140
+ `Product source must not recreate a built-in control with input type "${inputType}". Declare the built-in ${builtInSchemaType} schema control.`,
141
+ ),
142
+ ];
143
+ }
144
+
145
+ return { inspectNode };
146
+ }
@@ -0,0 +1,186 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { evaluateToolcraftProductBoundary } from "./toolcraft-product-boundary.mjs";
5
+ import { createToolcraftProductBoundaryFixture as createFixture } from "./toolcraft-product-boundary-test-fixtures.mjs";
6
+
7
+ const workspaceUiModule = ["@", "repo/ui"].join("");
8
+
9
+ test("finds built-in controls through every module form", async (context) => {
10
+ const rootDir = await createFixture(context, {
11
+ "src/features/control-bridge.ts": `
12
+ export { SliderControl as ProductSlider } from "@repo/ui/controls";
13
+ export { Color as ProductColor } from "@/toolcraft/ui";
14
+ `,
15
+ "src/features/dynamic.ts": `
16
+ export async function loadControls() {
17
+ return import("@/toolcraft/ui/components/controls/slider");
18
+ }
19
+ `,
20
+ "src/features/namespace.ts": `
21
+ import * as Controls from "@repo/ui";
22
+ export const Slider = Controls.Slider;
23
+ `,
24
+ });
25
+
26
+ const result = await evaluateToolcraftProductBoundary({ rootDir });
27
+
28
+ assert.deepEqual(
29
+ result.violations.map((violation) => violation.kind),
30
+ [
31
+ "built-in-control",
32
+ "built-in-control",
33
+ "built-in-control-implementation",
34
+ "built-in-control",
35
+ ],
36
+ );
37
+ });
38
+
39
+ test("rejects every deep import path into built-in control implementations", async (context) => {
40
+ const rootDir = await createFixture(context, {
41
+ "src/features/direct.tsx": `
42
+ import { ColorPickerPopover } from "@/toolcraft/ui/components/controls/color/color-picker-popover";
43
+ export const Direct = ColorPickerPopover;
44
+ `,
45
+ "src/features/directory-index.ts": `
46
+ import * as PrivateControls from "${workspaceUiModule}/components/controls";
47
+ export const DirectoryIndex = PrivateControls;
48
+ `,
49
+ "src/features/dynamic.ts": `
50
+ export const loadPrivateControl = () => import("${workspaceUiModule}/components/controls/color/color-value-utils");
51
+ `,
52
+ "src/features/namespace.ts": `
53
+ import * as PrivateColor from "@/toolcraft/ui/components/controls/color";
54
+ export const Namespace = PrivateColor;
55
+ `,
56
+ "src/features/reexport.ts": `
57
+ export { getCommittedHexColor } from "@/toolcraft/ui/components/controls/color/color-value-utils";
58
+ `,
59
+ "src/features/require.ts": `
60
+ export const privateControl = require("@/toolcraft/ui/components/controls/color/color-picker-popover");
61
+ `,
62
+ "src/features/workspace-source.ts": `
63
+ import { ColorPickerPopover } from "${workspaceUiModule}/src/components/controls/color/color-picker-popover";
64
+ export const WorkspaceSource = ColorPickerPopover;
65
+ `,
66
+ });
67
+
68
+ const result = await evaluateToolcraftProductBoundary({ rootDir });
69
+
70
+ assert.equal(result.violations.length, 7);
71
+ assert.equal(
72
+ result.violations.every(
73
+ (violation) => violation.kind === "built-in-control-implementation",
74
+ ),
75
+ true,
76
+ );
77
+ assert.equal(
78
+ result.violations.every((violation) =>
79
+ /deep control implementation|public schema control/u.test(
80
+ violation.message,
81
+ ),
82
+ ),
83
+ true,
84
+ );
85
+ });
86
+
87
+ test("rejects the badge color-control copy at both private imports", async (context) => {
88
+ const rootDir = await createFixture(context, {
89
+ "src/features/badge-color-control.tsx": `
90
+ import { ColorPickerPopover } from "@/toolcraft/ui/components/controls/color/color-picker-popover";
91
+ import { getCommittedHexColor } from "@/toolcraft/ui/components/controls/color/color-value-utils";
92
+ export const BadgeColorControl = { ColorPickerPopover, getCommittedHexColor };
93
+ `,
94
+ });
95
+
96
+ const result = await evaluateToolcraftProductBoundary({ rootDir });
97
+
98
+ assert.deepEqual(
99
+ result.violations.map(({ column, kind, line, repoPath }) => ({
100
+ column,
101
+ kind,
102
+ line,
103
+ repoPath,
104
+ })),
105
+ [
106
+ {
107
+ column: 7,
108
+ kind: "built-in-control-implementation",
109
+ line: 2,
110
+ repoPath: "src/features/badge-color-control.tsx",
111
+ },
112
+ {
113
+ column: 7,
114
+ kind: "built-in-control-implementation",
115
+ line: 3,
116
+ repoPath: "src/features/badge-color-control.tsx",
117
+ },
118
+ ],
119
+ );
120
+ });
121
+
122
+ test("rejects native and kit-primitive recreations of built-in controls", async (context) => {
123
+ const rootDir = await createFixture(context, {
124
+ "src/features/kit-inputs.tsx": `
125
+ import { Input as FieldInput } from "@/toolcraft/ui/primitives";
126
+ import * as Primitives from "@/toolcraft/ui/primitives";
127
+ const AliasedInput = FieldInput;
128
+ const { Input: RequiredInput } = require("@/toolcraft/ui/primitives");
129
+ export const Color = <FieldInput type="color" />;
130
+ export const File = <AliasedInput type={"file"} />;
131
+ export const Range = <Primitives.Input type="range" />;
132
+ export const Radio = <RequiredInput type="radio" />;
133
+ export function Dynamic({ kind }) {
134
+ return <FieldInput type={kind} />;
135
+ }
136
+ `,
137
+ "src/features/native.tsx": `
138
+ const rangeType = "range";
139
+ export const Range = <input type={rangeType} />;
140
+ export const Checkbox = <input type="checkbox" />;
141
+ export const Radio = <input type="radio" />;
142
+ export const Select = <select><option>One</option></select>;
143
+ export const Notes = <textarea />;
144
+ `,
145
+ "src/features/private-primitive.tsx": `
146
+ import PrimitiveInput from "${workspaceUiModule}/components/primitives/input";
147
+ export const PrivatePrimitiveColor = <PrimitiveInput type="color" />;
148
+ `,
149
+ });
150
+
151
+ const result = await evaluateToolcraftProductBoundary({ rootDir });
152
+
153
+ assert.equal(result.violations.length, 11);
154
+ assert.equal(
155
+ result.violations.every(
156
+ (violation) => violation.kind === "native-control-recreation",
157
+ ),
158
+ true,
159
+ );
160
+ });
161
+
162
+ test("allows generic text inputs, buttons, canvas, and product components", async (context) => {
163
+ const rootDir = await createFixture(context, {
164
+ "src/features/safe-controls.tsx": `
165
+ import { Button, Input as FieldInput } from "@/toolcraft/ui/primitives";
166
+ function ProductInput() { return <div>Product value</div>; }
167
+ function ShadowedInput(FieldInput) { return <FieldInput type="color" />; }
168
+ export const Safe = (
169
+ <>
170
+ <FieldInput type="text" />
171
+ <input type="email" />
172
+ <input type="hidden" />
173
+ <Button>Run</Button>
174
+ <button type="button">Local action</button>
175
+ <canvas />
176
+ <ProductInput />
177
+ <ShadowedInput />
178
+ </>
179
+ );
180
+ `,
181
+ });
182
+
183
+ const result = await evaluateToolcraftProductBoundary({ rootDir });
184
+
185
+ assert.deepEqual(result.violations, []);
186
+ });
@@ -1,8 +1,4 @@
1
1
  import ts from "typescript";
2
-
3
- const forbiddenRuntimeExportNames = new Set([
4
- "createToolcraftPngExportCanvas",
5
- ]);
6
2
  const forbiddenGlobalExportConstructors = new Set([
7
3
  "MediaRecorder",
8
4
  "VideoEncoder",
@@ -21,24 +17,17 @@ export function getToolcraftProductExportModuleKind(
21
17
  return "runtime-export-mechanics";
22
18
  }
23
19
  if (
24
- normalizedSpecifier === "@repo/toolcraft-runtime" ||
25
- /^[#@]\/toolcraft\/runtime\/?$/u.test(normalizedSpecifier) ||
26
- /\/src\/toolcraft\/runtime$/u.test(resolvedSpecifier ?? "")
20
+ /^@repo\/toolcraft-runtime\/export(?:\/|$)/u.test(normalizedSpecifier) ||
21
+ /^[#@]\/toolcraft\/runtime\/export(?:\/|$)/u.test(normalizedSpecifier) ||
22
+ /\/src\/toolcraft\/runtime\/export(?:\/|$)/u.test(resolvedSpecifier ?? "")
27
23
  ) {
28
- return "runtime-export-api";
24
+ return "runtime-export-mechanics";
29
25
  }
30
26
  return null;
31
27
  }
32
28
 
33
- export function isForbiddenToolcraftRuntimeExport(
34
- moduleKind,
35
- importedName,
36
- ) {
37
- return (
38
- moduleKind === "runtime-export-mechanics" ||
39
- (moduleKind === "runtime-export-api" &&
40
- forbiddenRuntimeExportNames.has(importedName))
41
- );
29
+ export function isForbiddenToolcraftRuntimeExport(moduleKind) {
30
+ return moduleKind === "runtime-export-mechanics";
42
31
  }
43
32
 
44
33
  function getDeclarationScope(node, sourceFile) {