@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.
- package/package.json +1 -1
- package/src/generate-test-doc-assertions.mjs +58 -68
- package/src/generate-test-doc-assertions.test.mjs +43 -0
- package/src/generate-test-doc-policy-assertions.mjs +92 -0
- package/src/generate-test-file-assertions.mjs +26 -75
- package/src/generate-test-product-iteration.test.mjs +17 -0
- package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
- package/src/generate-test-standalone-assertions.mjs +10 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
- package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
- package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
- package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
- package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
- package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
- package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
- package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
- package/templates/runtime/contracts/component-contracts.test.ts +2 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
- package/templates/runtime/contracts/decision-contracts.ts +10 -10
- package/templates/runtime/export/export-background.ts +0 -19
- package/templates/runtime/export/export.test.ts +45 -42
- package/templates/runtime/export/index.ts +44 -16
- package/templates/runtime/index.ts +2 -0
- package/templates/runtime/model-import/model-import-limits.ts +15 -4
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
- package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
- package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
- package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
- package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
- package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
- package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
- package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
- package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
- package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
- package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
- package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
- package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
- package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
- package/templates/runtime/react/index.ts +4 -0
- package/templates/runtime/react/model-rendering/model-export.ts +1 -1
- package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
- package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
- package/templates/runtime/scene/scene-bounds.test.ts +23 -0
- package/templates/runtime/scene/scene-bounds.ts +31 -17
- package/templates/runtime/schema/app-capabilities.test.ts +4 -0
- package/templates/runtime/schema/collection-item-controls.ts +29 -0
- package/templates/runtime/schema/control-applicability.test.ts +127 -0
- package/templates/runtime/schema/control-applicability.ts +433 -0
- package/templates/runtime/schema/control-schema-normalization.ts +177 -11
- package/templates/runtime/schema/controls-panel-actions.ts +2 -2
- package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
- package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
- package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
- package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
- package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
- package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
- package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
- package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
- package/templates/runtime/schema/runtime-setup-section.ts +24 -5
- package/templates/runtime/schema/types.ts +68 -13
- package/templates/starter/AGENTS.md +12 -10
- package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
- package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
- package/templates/starter/docs/toolcraft/component-rules.md +8 -6
- package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
- package/templates/starter/docs/toolcraft/core/layout.md +7 -7
- package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
- package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
- package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
- package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
- package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
- package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
- package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
- package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
- package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
- package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
- package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
- package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
- package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
- package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
- package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
- package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
- package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
- package/templates/starter/src/app/acceptance/conditions.ts +1 -1
- package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
- package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
- package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
- package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
- package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
- package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
- package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
- package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
- package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
- package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
- package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
- package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
- package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
- package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
- package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
- package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
- package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
- package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
- package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
- package/templates/starter/src/app/acceptance/output-export.ts +22 -10
- package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
- package/templates/starter/src/app/acceptance/types.ts +23 -8
- package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
- package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
- package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
- package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
- package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
- package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
- package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
- package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
- package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
- package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
- package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
- package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
- package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
- package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
- package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
- package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
- package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
- package/templates/starter/src/app/starter-acceptance.ts +11 -2
- package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
- package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
- package/templates/starter/src/routes/index.tsx +1 -0
- package/templates/ui/components/composites/tabs.tsx +2 -2
- package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
- package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
- package/templates/ui/components/controls/collection-actions/index.ts +2 -0
- package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
- package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
- package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
- package/templates/ui/components/controls/file-drop/index.ts +1 -0
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
- package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
- package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
- package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import {
|
|
2
|
+
areToolcraftPredicatesProvablyExclusive,
|
|
3
|
+
areToolcraftValuesEqual,
|
|
4
|
+
doesToolcraftPredicateMatchValue,
|
|
5
|
+
getToolcraftApplicabilityPredicates,
|
|
6
|
+
type ResolvedToolcraftControlSchema,
|
|
7
|
+
type ToolcraftControlPredicateSchema,
|
|
8
|
+
} from "@repo/toolcraft-runtime";
|
|
9
|
+
|
|
10
|
+
import type { ToolcraftAcceptanceValidationContext } from "./validation-pipeline";
|
|
11
|
+
|
|
12
|
+
export type ToolcraftApplicabilitySelectorValue = boolean | number | string;
|
|
13
|
+
|
|
14
|
+
export type ToolcraftApplicabilitySelectorDomain =
|
|
15
|
+
| Readonly<{
|
|
16
|
+
status: "supported";
|
|
17
|
+
values: readonly ToolcraftApplicabilitySelectorValue[];
|
|
18
|
+
}>
|
|
19
|
+
| Readonly<{
|
|
20
|
+
reason: string;
|
|
21
|
+
status: "unsupported";
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
const numericOperators = [
|
|
25
|
+
"greaterThan",
|
|
26
|
+
"greaterThanOrEqual",
|
|
27
|
+
"lessThan",
|
|
28
|
+
"lessThanOrEqual",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
function addUniqueValue(
|
|
32
|
+
values: ToolcraftApplicabilitySelectorValue[],
|
|
33
|
+
value: unknown,
|
|
34
|
+
): void {
|
|
35
|
+
if (
|
|
36
|
+
(typeof value === "boolean" ||
|
|
37
|
+
typeof value === "number" ||
|
|
38
|
+
typeof value === "string") &&
|
|
39
|
+
!values.some((existing) => areToolcraftValuesEqual(existing, value))
|
|
40
|
+
) {
|
|
41
|
+
values.push(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getPredicateFiniteValues(
|
|
46
|
+
predicate: ToolcraftControlPredicateSchema,
|
|
47
|
+
): unknown[] {
|
|
48
|
+
return [
|
|
49
|
+
...(Object.prototype.hasOwnProperty.call(predicate, "equals")
|
|
50
|
+
? [predicate.equals]
|
|
51
|
+
: []),
|
|
52
|
+
...(Object.prototype.hasOwnProperty.call(predicate, "notEquals")
|
|
53
|
+
? [predicate.notEquals]
|
|
54
|
+
: []),
|
|
55
|
+
...(predicate.oneOf ?? []),
|
|
56
|
+
...(predicate.notOneOf ?? []),
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getDiscreteSliderDomain(
|
|
61
|
+
control: ResolvedToolcraftControlSchema,
|
|
62
|
+
predicates: readonly ToolcraftControlPredicateSchema[],
|
|
63
|
+
): ToolcraftApplicabilitySelectorDomain {
|
|
64
|
+
if (
|
|
65
|
+
control.type !== "slider" ||
|
|
66
|
+
control.variant !== "discrete" ||
|
|
67
|
+
typeof control.min !== "number" ||
|
|
68
|
+
typeof control.max !== "number" ||
|
|
69
|
+
typeof control.step !== "number" ||
|
|
70
|
+
!Number.isFinite(control.min) ||
|
|
71
|
+
!Number.isFinite(control.max) ||
|
|
72
|
+
!Number.isFinite(control.step) ||
|
|
73
|
+
control.max < control.min ||
|
|
74
|
+
control.step <= 0
|
|
75
|
+
) {
|
|
76
|
+
return {
|
|
77
|
+
reason: `${control.target} is not a bounded discrete scalar slider`,
|
|
78
|
+
status: "unsupported",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { max, min, step } = control;
|
|
83
|
+
const candidates: number[] = [];
|
|
84
|
+
const clampAligned = (value: number): number => {
|
|
85
|
+
const clamped = Math.min(max, Math.max(min, value));
|
|
86
|
+
const stepIndex = Math.round((clamped - min) / step);
|
|
87
|
+
const aligned = Math.min(max, Math.max(min, min + stepIndex * step));
|
|
88
|
+
return Number(aligned.toFixed(12));
|
|
89
|
+
};
|
|
90
|
+
const addCandidate = (value: unknown): void => {
|
|
91
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const candidate of [value - step, value, value + step]) {
|
|
96
|
+
const aligned = clampAligned(candidate);
|
|
97
|
+
|
|
98
|
+
if (!candidates.includes(aligned)) {
|
|
99
|
+
candidates.push(aligned);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
addCandidate(min);
|
|
105
|
+
addCandidate(max);
|
|
106
|
+
addCandidate(control.defaultValue);
|
|
107
|
+
|
|
108
|
+
for (const predicate of predicates) {
|
|
109
|
+
for (const value of getPredicateFiniteValues(predicate)) {
|
|
110
|
+
addCandidate(value);
|
|
111
|
+
}
|
|
112
|
+
for (const operator of numericOperators) {
|
|
113
|
+
addCandidate(predicate[operator]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
status: "supported",
|
|
119
|
+
values: candidates.sort((left, right) => left - right),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function getToolcraftApplicabilitySelectorDomain(
|
|
124
|
+
control: ResolvedToolcraftControlSchema,
|
|
125
|
+
predicates: readonly ToolcraftControlPredicateSchema[],
|
|
126
|
+
): ToolcraftApplicabilitySelectorDomain {
|
|
127
|
+
if (
|
|
128
|
+
control.type === "select" ||
|
|
129
|
+
control.type === "segmented" ||
|
|
130
|
+
control.type === "tabs"
|
|
131
|
+
) {
|
|
132
|
+
const values: ToolcraftApplicabilitySelectorValue[] = [];
|
|
133
|
+
|
|
134
|
+
for (const option of control.options ?? []) {
|
|
135
|
+
addUniqueValue(values, option.value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return values.length > 0
|
|
139
|
+
? { status: "supported", values }
|
|
140
|
+
: {
|
|
141
|
+
reason: `${control.target} has no declared selector options`,
|
|
142
|
+
status: "unsupported",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (control.type === "imagePicker") {
|
|
147
|
+
const values: ToolcraftApplicabilitySelectorValue[] = [];
|
|
148
|
+
|
|
149
|
+
for (const item of control.items ?? []) {
|
|
150
|
+
addUniqueValue(values, item.value);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return values.length > 0
|
|
154
|
+
? { status: "supported", values }
|
|
155
|
+
: {
|
|
156
|
+
reason: `${control.target} has no declared image picker items`,
|
|
157
|
+
status: "unsupported",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (control.type === "switch" || control.type === "checkbox") {
|
|
162
|
+
return { status: "supported", values: [false, true] };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return getDiscreteSliderDomain(control, predicates);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getInvalidFinitePredicateValues(
|
|
169
|
+
predicates: readonly ToolcraftControlPredicateSchema[],
|
|
170
|
+
domain: readonly ToolcraftApplicabilitySelectorValue[],
|
|
171
|
+
): unknown[] {
|
|
172
|
+
const values: unknown[] = [];
|
|
173
|
+
|
|
174
|
+
for (const predicate of predicates) {
|
|
175
|
+
for (const value of getPredicateFiniteValues(predicate)) {
|
|
176
|
+
if (
|
|
177
|
+
!domain.some((candidate) =>
|
|
178
|
+
areToolcraftValuesEqual(candidate, value),
|
|
179
|
+
) &&
|
|
180
|
+
!values.some((candidate) =>
|
|
181
|
+
areToolcraftValuesEqual(candidate, value),
|
|
182
|
+
)
|
|
183
|
+
) {
|
|
184
|
+
values.push(value);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return values;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function predicatesArePairwiseExclusive(
|
|
193
|
+
predicates: readonly ToolcraftControlPredicateSchema[],
|
|
194
|
+
): boolean {
|
|
195
|
+
for (let leftIndex = 0; leftIndex < predicates.length; leftIndex += 1) {
|
|
196
|
+
for (
|
|
197
|
+
let rightIndex = leftIndex + 1;
|
|
198
|
+
rightIndex < predicates.length;
|
|
199
|
+
rightIndex += 1
|
|
200
|
+
) {
|
|
201
|
+
const left = predicates[leftIndex];
|
|
202
|
+
const right = predicates[rightIndex];
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
left &&
|
|
206
|
+
right &&
|
|
207
|
+
areToolcraftPredicatesProvablyExclusive(left, right)
|
|
208
|
+
) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function getToolcraftControlApplicabilityErrors({
|
|
218
|
+
controls,
|
|
219
|
+
productReadiness,
|
|
220
|
+
}: Pick<
|
|
221
|
+
ToolcraftAcceptanceValidationContext,
|
|
222
|
+
"controls" | "productReadiness"
|
|
223
|
+
>): string[] {
|
|
224
|
+
if (productReadiness.mode !== "product") {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const errors: string[] = [];
|
|
229
|
+
const controlsByTarget = new Map(
|
|
230
|
+
controls.map(({ control }) => [control.target, control] as const),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
for (const { control } of controls) {
|
|
234
|
+
const { applicability } = control;
|
|
235
|
+
|
|
236
|
+
if (applicability.origin === "implicit") {
|
|
237
|
+
errors.push(
|
|
238
|
+
`${control.target} must declare explicit applicability as always or conditional.`,
|
|
239
|
+
);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (applicability.origin === "legacy") {
|
|
244
|
+
errors.push(
|
|
245
|
+
`${control.target} uses legacy visibleWhen; product controls must declare explicit applicability.`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const predicates = getToolcraftApplicabilityPredicates(applicability);
|
|
250
|
+
const predicatesByTarget = new Map<
|
|
251
|
+
string,
|
|
252
|
+
ToolcraftControlPredicateSchema[]
|
|
253
|
+
>();
|
|
254
|
+
|
|
255
|
+
for (const predicate of predicates) {
|
|
256
|
+
const selector = controlsByTarget.get(predicate.target);
|
|
257
|
+
|
|
258
|
+
if (!selector) {
|
|
259
|
+
errors.push(
|
|
260
|
+
`${control.target} predicate target "${predicate.target}" does not exist.`,
|
|
261
|
+
);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (selector.target === control.target) {
|
|
266
|
+
errors.push(`${control.target} applicability cannot gate itself.`);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const targetPredicates = predicatesByTarget.get(predicate.target) ?? [];
|
|
271
|
+
targetPredicates.push(predicate);
|
|
272
|
+
predicatesByTarget.set(predicate.target, targetPredicates);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const [selectorTarget, targetPredicates] of predicatesByTarget) {
|
|
276
|
+
const selector = controlsByTarget.get(selectorTarget);
|
|
277
|
+
|
|
278
|
+
if (!selector) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const domain = getToolcraftApplicabilitySelectorDomain(
|
|
283
|
+
selector,
|
|
284
|
+
targetPredicates,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if (domain.status === "unsupported") {
|
|
288
|
+
errors.push(
|
|
289
|
+
`${selector.target} is not a supported applicability selector: ${domain.reason}.`,
|
|
290
|
+
);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
selector.type === "select" ||
|
|
296
|
+
selector.type === "segmented" ||
|
|
297
|
+
selector.type === "tabs" ||
|
|
298
|
+
selector.type === "imagePicker" ||
|
|
299
|
+
selector.type === "switch" ||
|
|
300
|
+
selector.type === "checkbox"
|
|
301
|
+
) {
|
|
302
|
+
for (const invalidValue of getInvalidFinitePredicateValues(
|
|
303
|
+
targetPredicates,
|
|
304
|
+
domain.values,
|
|
305
|
+
)) {
|
|
306
|
+
errors.push(
|
|
307
|
+
`${control.target} applicability value ${JSON.stringify(invalidValue)} is not an option of ${selector.target}.`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (predicatesArePairwiseExclusive(targetPredicates)) {
|
|
313
|
+
errors.push(`${control.target} applicability is unsatisfiable.`);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
!domain.values.some((value) =>
|
|
319
|
+
targetPredicates.every((predicate) =>
|
|
320
|
+
doesToolcraftPredicateMatchValue(predicate, value),
|
|
321
|
+
),
|
|
322
|
+
)
|
|
323
|
+
) {
|
|
324
|
+
errors.push(
|
|
325
|
+
`${control.target} has no satisfying value in ${selector.target}.`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return errors;
|
|
332
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ToolcraftControlConditionSchema,
|
|
3
|
-
|
|
3
|
+
ResolvedToolcraftControlSchema,
|
|
4
|
+
} from "@repo/toolcraft-runtime";
|
|
5
|
+
import {
|
|
6
|
+
getToolcraftApplicabilityPredicates,
|
|
4
7
|
} from "@repo/toolcraft-runtime";
|
|
5
8
|
|
|
6
9
|
import type { ToolcraftControlLayoutFacts } from "./control-layout-model";
|
|
@@ -17,7 +20,7 @@ function normalizeToolcraftConditionValue(value: unknown): string | null {
|
|
|
17
20
|
|
|
18
21
|
function getToolcraftConditionBranchTexts(
|
|
19
22
|
condition: ToolcraftControlConditionSchema,
|
|
20
|
-
gateControl:
|
|
23
|
+
gateControl: ResolvedToolcraftControlSchema,
|
|
21
24
|
): string[] {
|
|
22
25
|
const rawValues =
|
|
23
26
|
"equals" in condition
|
|
@@ -44,7 +47,7 @@ function sectionTitleLooksLikeDependencyBranch({
|
|
|
44
47
|
sectionTitle,
|
|
45
48
|
}: {
|
|
46
49
|
condition: ToolcraftControlConditionSchema;
|
|
47
|
-
gateControl:
|
|
50
|
+
gateControl: ResolvedToolcraftControlSchema;
|
|
48
51
|
sectionTitle: string | undefined;
|
|
49
52
|
}): boolean {
|
|
50
53
|
const sectionText = normalizeToolcraftSemanticText(sectionTitle);
|
|
@@ -68,10 +71,18 @@ export function getToolcraftControlDependencyGroupingErrors(
|
|
|
68
71
|
const errors: string[] = [];
|
|
69
72
|
|
|
70
73
|
for (const item of facts.visibleControls) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const conditions = [
|
|
75
|
+
...getToolcraftApplicabilityPredicates(
|
|
76
|
+
item.control.applicability,
|
|
77
|
+
).map(
|
|
78
|
+
(condition) => ["applicability", condition] as const,
|
|
79
|
+
),
|
|
80
|
+
...(item.control.disabledWhen
|
|
81
|
+
? [["disabledWhen", item.control.disabledWhen] as const]
|
|
82
|
+
: []),
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const [conditionName, condition] of conditions) {
|
|
75
86
|
if (!condition || !item.loosePrefix) {
|
|
76
87
|
continue;
|
|
77
88
|
}
|
|
@@ -86,7 +97,7 @@ export function getToolcraftControlDependencyGroupingErrors(
|
|
|
86
97
|
gateControl.loosePrefix !== null &&
|
|
87
98
|
gateControl.loosePrefix === item.loosePrefix;
|
|
88
99
|
const looksLikeBranchSection =
|
|
89
|
-
conditionName === "
|
|
100
|
+
conditionName === "applicability" &&
|
|
90
101
|
sectionTitleLooksLikeDependencyBranch({
|
|
91
102
|
condition,
|
|
92
103
|
gateControl: gateControl.control,
|
|
@@ -98,7 +109,7 @@ export function getToolcraftControlDependencyGroupingErrors(
|
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
errors.push(
|
|
101
|
-
`${item.sectionLabel} / ${item.controlId} is gated by ${conditionName} target "${condition.target}" in ${gateControl.sectionLabel}, but it belongs to the same dependency group. Keep selectors and their dependent controls in one semantic section when they describe one product entity or branch; use
|
|
112
|
+
`${item.sectionLabel} / ${item.controlId} is gated by ${conditionName} target "${condition.target}" in ${gateControl.sectionLabel}, but it belongs to the same dependency group. Keep selectors and their dependent controls in one semantic section when they describe one product entity or branch; use conditional applicability for branch-specific controls inside that section instead of splitting branch controls into their own section. Do not use disabledWhen for product controls.`,
|
|
102
113
|
);
|
|
103
114
|
}
|
|
104
115
|
}
|
|
@@ -2,10 +2,8 @@ import {
|
|
|
2
2
|
getToolcraftFontPickerOwnedTypographyPart,
|
|
3
3
|
} from "./control-labels";
|
|
4
4
|
import type { ToolcraftControlLayoutFacts } from "./control-layout-model";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
hasToolcraftInventorySplitEvidence,
|
|
8
|
-
} from "./control-section-inventory";
|
|
5
|
+
import { hasToolcraftInventoryEntityAgreement } from "./control-section-entity-cohesion";
|
|
6
|
+
import { getToolcraftSectionInventoryById } from "./control-section-inventory";
|
|
9
7
|
import { getControlLabelText } from "./controls";
|
|
10
8
|
import type { ToolcraftControlSectionInventoryEntry } from "./types";
|
|
11
9
|
|
|
@@ -60,13 +58,13 @@ function getToolcraftProductEntitySplitErrors({
|
|
|
60
58
|
for (const [prefix, sections] of facts.strictPrefixSections) {
|
|
61
59
|
if (
|
|
62
60
|
sections.size > 1 &&
|
|
63
|
-
!
|
|
61
|
+
!hasToolcraftInventoryEntityAgreement({ sectionInventoryById, sections })
|
|
64
62
|
) {
|
|
65
63
|
const sectionLabels = [...sections].map(
|
|
66
64
|
(sectionId) => facts.sectionLabelsById.get(sectionId) ?? sectionId,
|
|
67
65
|
);
|
|
68
66
|
errors.push(
|
|
69
|
-
`Controls for
|
|
67
|
+
`Controls for target family "${prefix}" are split across sections: ${sectionLabels.join(", ")}. Assign one shared entityId when the sections edit one logical entity; the entity cohesion validator decides whether that split is allowed.`,
|
|
70
68
|
);
|
|
71
69
|
}
|
|
72
70
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ResolvedToolcraftAppSchema,
|
|
3
|
-
|
|
3
|
+
ResolvedToolcraftControlSchema,
|
|
4
4
|
} from "@repo/toolcraft-runtime";
|
|
5
5
|
|
|
6
6
|
import { isToolcraftProductSectionControl } from "./controls";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./semantic";
|
|
13
13
|
|
|
14
14
|
export type ToolcraftControlLayoutControlEntry = {
|
|
15
|
-
control:
|
|
15
|
+
control: ResolvedToolcraftControlSchema;
|
|
16
16
|
controlId: string;
|
|
17
17
|
loosePrefix: string | null;
|
|
18
18
|
sectionLabel: string;
|
|
@@ -21,7 +21,7 @@ export type ToolcraftControlLayoutControlEntry = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export type ToolcraftControlLayoutSectionFact = {
|
|
24
|
-
controls: readonly [string,
|
|
24
|
+
controls: readonly [string, ResolvedToolcraftControlSchema][];
|
|
25
25
|
sectionLabel: string;
|
|
26
26
|
sectionId: string;
|
|
27
27
|
sectionLoosePrefixes: ReadonlySet<string>;
|
|
@@ -41,7 +41,7 @@ export type ToolcraftControlLayoutFacts = {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
function getToolcraftControlSemanticCluster(
|
|
44
|
-
control:
|
|
44
|
+
control: ResolvedToolcraftControlSchema,
|
|
45
45
|
): string {
|
|
46
46
|
return (
|
|
47
47
|
control.semanticGroup?.trim() ||
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getToolcraftGenericControlLabelError,
|
|
8
8
|
} from "./control-labels";
|
|
9
9
|
import type { ToolcraftControlLayoutFacts } from "./control-layout-model";
|
|
10
|
+
import { TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS } from "./control-section-entity-cohesion";
|
|
10
11
|
import {
|
|
11
12
|
controlTypeSectionTitlePattern,
|
|
12
13
|
genericControlSectionTitlePattern,
|
|
@@ -15,9 +16,6 @@ import {
|
|
|
15
16
|
const broadControlSectionTitlePattern =
|
|
16
17
|
/^(animation|export|flow|icon|logo|motion|output|scene|shape|shapes|text|typography|visual|visuals)$/i;
|
|
17
18
|
|
|
18
|
-
const maxPreferredControlsPerSection = 7;
|
|
19
|
-
const maxHardControlsPerSection = 10;
|
|
20
|
-
|
|
21
19
|
export function getToolcraftControlLayoutSectionInvariantErrors(
|
|
22
20
|
facts: ToolcraftControlLayoutFacts,
|
|
23
21
|
): string[] {
|
|
@@ -55,7 +53,7 @@ export function getToolcraftControlLayoutSectionInvariantErrors(
|
|
|
55
53
|
.map(([controlId]) => controlId);
|
|
56
54
|
|
|
57
55
|
if (
|
|
58
|
-
controls.length
|
|
56
|
+
controls.length >= TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS &&
|
|
59
57
|
missingSemanticGroups.length > 0
|
|
60
58
|
) {
|
|
61
59
|
errors.push(
|
|
@@ -135,7 +133,7 @@ export function getToolcraftControlLayoutSectionHeuristicErrors(
|
|
|
135
133
|
sectionTitle !== undefined && broadControlSectionTitlePattern.test(sectionTitle);
|
|
136
134
|
|
|
137
135
|
if (
|
|
138
|
-
controls.length
|
|
136
|
+
controls.length >= TOOLCRAFT_DENSE_SECTION_MIN_CONTROLS &&
|
|
139
137
|
hasBroadSectionTitle &&
|
|
140
138
|
semanticClusters.size >= 3
|
|
141
139
|
) {
|
|
@@ -144,11 +142,6 @@ export function getToolcraftControlLayoutSectionHeuristicErrors(
|
|
|
144
142
|
);
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
if (controls.length > maxHardControlsPerSection && semanticClusters.size > 1) {
|
|
148
|
-
errors.push(
|
|
149
|
-
`${sectionLabel} has ${controls.length} controls across ${semanticClusters.size} semantic clusters (${clusterList}). Controls-panel sections should stay discrete; split sections that grow past ${maxHardControlsPerSection} controls unless every control edits one tightly scoped entity.`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
145
|
}
|
|
153
146
|
|
|
154
147
|
return errors;
|
|
@@ -10,6 +10,12 @@ export function getRequiredToolcraftControlPartCoverage(
|
|
|
10
10
|
return ["anchorGrid.position"];
|
|
11
11
|
case "channelMixer":
|
|
12
12
|
return ["channelMixer.activeChannel", "channelMixer.values"];
|
|
13
|
+
case "collectionActions":
|
|
14
|
+
return [
|
|
15
|
+
"collectionActions.add",
|
|
16
|
+
"collectionActions.remove",
|
|
17
|
+
"collectionActions.items",
|
|
18
|
+
];
|
|
13
19
|
case "curves":
|
|
14
20
|
return control.variant === "single"
|
|
15
21
|
? ["curves.points"]
|
|
@@ -39,6 +45,8 @@ export function getRequiredToolcraftControlPartCoverage(
|
|
|
39
45
|
return ["rangeInput.start", "rangeInput.end"];
|
|
40
46
|
case "rangeSlider":
|
|
41
47
|
return ["rangeSlider.lower", "rangeSlider.upper"];
|
|
48
|
+
case "sourceCollection":
|
|
49
|
+
return ["sourceCollection.items"];
|
|
42
50
|
case "vector":
|
|
43
51
|
return ["vector.x", "vector.y"];
|
|
44
52
|
default:
|