@yahoo/uds-create-config 3.2.0 → 3.4.0
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/dist/configs/react-native-system.d.ts +2 -0
- package/dist/configs/react-native-system.js +2 -0
- package/dist/configs/system.d.ts +2 -0
- package/dist/configs/system.js +2 -0
- package/dist/entities/system/Component.js +34 -7
- package/dist/entities/system/File.d.ts +1 -0
- package/dist/entities/system/File.js +27 -2
- package/dist/entities/system/style-bag.js +24 -5
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/anatomy-check.d.ts +99 -0
- package/dist/framework/anatomy-check.js +337 -0
- package/dist/framework/class-names.js +6 -2
- package/dist/framework/defineEntity.d.ts +1 -0
- package/dist/framework/ref-integrity.js +17 -3
- package/dist/framework/registry.d.ts +1 -0
- package/dist/framework/render-spec.d.ts +12 -1
- package/dist/framework/render-spec.js +122 -28
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/migrations/2.0.0/v1-artifact.d.ts +1 -0
- package/dist/migrations/20260914181845_anatomy_harness.d.ts +23 -0
- package/dist/migrations/20260914181845_anatomy_harness.js +10 -0
- package/dist/renderer/assetEntries.js +2 -3
- package/dist/renderer/spec-content.js +1 -1
- package/dist/renderer/wrappers/inline-styles.js +21 -1
- package/dist/spec/index.d.ts +1 -1
- package/dist/spec/specToJsx.js +26 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1109,6 +1109,7 @@ declare const ReactNativeSystem: ConfigClass<{
|
|
|
1109
1109
|
target: _$zod.ZodType<Ref, unknown, _$zod_v4_core0.$ZodTypeInternals<Ref, unknown>>;
|
|
1110
1110
|
names: _$zod.ZodOptional<_$zod.ZodArray<_$zod.ZodString>>;
|
|
1111
1111
|
}, _$zod_v4_core0.$strip>>>;
|
|
1112
|
+
exports: _$zod.ZodOptional<SubEntityClass<_$zod.ZodObject<{}, _$zod_v4_core0.$strict>>>;
|
|
1112
1113
|
}, _$zod_v4_core0.$strict>, _$zod.ZodObject<Record<never, never>, _$zod_v4_core0.$strip>, "file", Record<never, never>, {
|
|
1113
1114
|
readonly binary: ({
|
|
1114
1115
|
path
|
|
@@ -2633,6 +2634,7 @@ declare const ReactNativeSystem: ConfigClass<{
|
|
|
2633
2634
|
target: _$zod.ZodType<Ref, unknown, _$zod_v4_core0.$ZodTypeInternals<Ref, unknown>>;
|
|
2634
2635
|
names: _$zod.ZodOptional<_$zod.ZodArray<_$zod.ZodString>>;
|
|
2635
2636
|
}, _$zod_v4_core0.$strip>>>;
|
|
2637
|
+
exports: _$zod.ZodOptional<SubEntityClass<_$zod.ZodObject<{}, _$zod_v4_core0.$strict>>>;
|
|
2636
2638
|
}, _$zod_v4_core0.$strict>, _$zod.ZodObject<Record<never, never>, _$zod_v4_core0.$strip>, "file", Record<never, never>, {
|
|
2637
2639
|
readonly binary: ({
|
|
2638
2640
|
path
|
|
@@ -18,6 +18,7 @@ import { visibleStatePointerMigration } from "../migrations/20260908194245_visib
|
|
|
18
18
|
import { iconKnockoutVariantsMigration } from "../migrations/20260909183001_icon_knockout_variants.js";
|
|
19
19
|
import { linkedSystemLinkOpsMigration } from "../migrations/20260910212453_linked_system_link_ops.js";
|
|
20
20
|
import { componentCreateBlankOpMigration } from "../migrations/20260914155048_component_create_blank_op.js";
|
|
21
|
+
import { anatomyHarnessMigration } from "../migrations/20260914181845_anatomy_harness.js";
|
|
21
22
|
import { NativeModifier } from "../entities/native/NativeModifier.js";
|
|
22
23
|
import { NativeSettings } from "../entities/native/NativeSettings.js";
|
|
23
24
|
import { NativeStyleProperty } from "../entities/native/NativeStyleProperty.js";
|
|
@@ -62,5 +63,6 @@ if (!registeredSchemaMigrations().has(20260908194245)) registerSchemaMigrations(
|
|
|
62
63
|
if (!registeredSchemaMigrations().has(20260909183001)) registerSchemaMigrations(iconKnockoutVariantsMigration);
|
|
63
64
|
if (!registeredSchemaMigrations().has(20260910212453)) registerSchemaMigrations(linkedSystemLinkOpsMigration);
|
|
64
65
|
if (!registeredSchemaMigrations().has(20260914155048)) registerSchemaMigrations(componentCreateBlankOpMigration);
|
|
66
|
+
if (!registeredSchemaMigrations().has(20260914181845)) registerSchemaMigrations(anatomyHarnessMigration);
|
|
65
67
|
//#endregion
|
|
66
68
|
export { ReactNativeSystem };
|
package/dist/configs/system.d.ts
CHANGED
|
@@ -973,6 +973,7 @@ declare const System: ConfigClass<{
|
|
|
973
973
|
target: _$zod.ZodType<Ref, unknown, _$zod_v4_core0.$ZodTypeInternals<Ref, unknown>>;
|
|
974
974
|
names: _$zod.ZodOptional<_$zod.ZodArray<_$zod.ZodString>>;
|
|
975
975
|
}, _$zod_v4_core0.$strip>>>;
|
|
976
|
+
exports: _$zod.ZodOptional<SubEntityClass<_$zod.ZodObject<{}, _$zod_v4_core0.$strict>>>;
|
|
976
977
|
}, _$zod_v4_core0.$strict>, _$zod.ZodObject<Record<never, never>, _$zod_v4_core0.$strip>, "file", Record<never, never>, {
|
|
977
978
|
readonly binary: ({
|
|
978
979
|
path
|
|
@@ -2381,6 +2382,7 @@ declare const System: ConfigClass<{
|
|
|
2381
2382
|
target: _$zod.ZodType<Ref, unknown, _$zod_v4_core0.$ZodTypeInternals<Ref, unknown>>;
|
|
2382
2383
|
names: _$zod.ZodOptional<_$zod.ZodArray<_$zod.ZodString>>;
|
|
2383
2384
|
}, _$zod_v4_core0.$strip>>>;
|
|
2385
|
+
exports: _$zod.ZodOptional<SubEntityClass<_$zod.ZodObject<{}, _$zod_v4_core0.$strict>>>;
|
|
2384
2386
|
}, _$zod_v4_core0.$strict>, _$zod.ZodObject<Record<never, never>, _$zod_v4_core0.$strip>, "file", Record<never, never>, {
|
|
2385
2387
|
readonly binary: ({
|
|
2386
2388
|
path
|
package/dist/configs/system.js
CHANGED
|
@@ -28,6 +28,7 @@ import { linkedSystemLinkOpsMigration } from "../migrations/20260910212453_linke
|
|
|
28
28
|
import { previewConditionsMigration } from "../migrations/20260912161840_preview_conditions.js";
|
|
29
29
|
import { fontVariableMigration } from "../migrations/20260912164927_font_variable.js";
|
|
30
30
|
import { componentCreateBlankOpMigration } from "../migrations/20260914155048_component_create_blank_op.js";
|
|
31
|
+
import { anatomyHarnessMigration } from "../migrations/20260914181845_anatomy_harness.js";
|
|
31
32
|
//#region src/configs/system.ts
|
|
32
33
|
/**
|
|
33
34
|
* `System` — the design-system config TYPE. Declares the exact kinds a design system owns (tokens,
|
|
@@ -76,5 +77,6 @@ if (!registeredSchemaMigrations().has(20260910212453)) registerSchemaMigrations(
|
|
|
76
77
|
if (!registeredSchemaMigrations().has(20260912161840)) registerSchemaMigrations(previewConditionsMigration);
|
|
77
78
|
if (!registeredSchemaMigrations().has(20260912164927)) registerSchemaMigrations(fontVariableMigration);
|
|
78
79
|
if (!registeredSchemaMigrations().has(20260914155048)) registerSchemaMigrations(componentCreateBlankOpMigration);
|
|
80
|
+
if (!registeredSchemaMigrations().has(20260914181845)) registerSchemaMigrations(anatomyHarnessMigration);
|
|
79
81
|
//#endregion
|
|
80
82
|
export { System };
|
|
@@ -8,7 +8,7 @@ import { derivedColor, gradient } from "./color.js";
|
|
|
8
8
|
import { childrenPolicy } from "./element.js";
|
|
9
9
|
import { listedIn } from "../../framework/utils/enumerated.js";
|
|
10
10
|
import { valueSchemaOf } from "../../framework/value-domain.js";
|
|
11
|
-
import { Style, addBagKeyIssues, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
|
|
11
|
+
import { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
|
|
12
12
|
import { propValueDomain, resolveComponentProps, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
|
|
13
13
|
import { intoName, routedPropIn, routesContent } from "../../framework/prop-surface.js";
|
|
14
14
|
import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../framework/render-spec.js";
|
|
@@ -773,6 +773,13 @@ function sameElement(left, right) {
|
|
|
773
773
|
function spellsLayer(element, key, layer) {
|
|
774
774
|
return element === key || sameElement(element, layer);
|
|
775
775
|
}
|
|
776
|
+
/** Whether a node keyed like a layer renders it: the layer's ref, or an element the prop chooses
|
|
777
|
+
* that names the layer it stands in for (`{ "$state": "/type", …, "layer": "root" }`), which wears
|
|
778
|
+
* the layer's markers and classes on whichever component the value picks. */
|
|
779
|
+
function rendersLayer(element, key) {
|
|
780
|
+
if (isRef(element)) return element.__ref === `layer:${key}`;
|
|
781
|
+
return typeof element === "object" && element !== null && "$state" in element && element.layer === key;
|
|
782
|
+
}
|
|
776
783
|
/** The shape of a rule, for a bag written where the rule belongs or a rule with no `layers`. */
|
|
777
784
|
const ruleShapeError = (issue) => issue.code === "invalid_type" || issue.code === "unrecognized_keys" ? "a style rule is `{ \"when\": null, \"layers\": { \"<layer>\": { \"<styleProp>\": \"<value>\" } } }` under a key of your own (`*` for the unconditional rule), or with `\"when\": { \"<prop>\": \"<value>\" }` for a variant; the layer bags sit under `layers`, never directly under the rule" : void 0;
|
|
778
785
|
const StyleRule = defineSubEntity({
|
|
@@ -845,7 +852,20 @@ const ruleSchemas = /* @__PURE__ */ new WeakMap();
|
|
|
845
852
|
const nodeElement = z.union([
|
|
846
853
|
refSchema.describe("A ref. `layer:<name>` renders through one of this component's layers, which is the only way a node can be styled; `component:<path>`, `icon:<path>` or `package:<export>` renders that directly, unstyled."),
|
|
847
854
|
z.string().describe("An intrinsic tag such as `div`, rendered unstyled, or `Slot`: the position where an untargeted slot prop's content lands, with the prop named in `props.name`, listed in the `children` of a layer that takes nodes. A text layer shows a prop through a `forward` declared on the prop instead."),
|
|
848
|
-
z.strictObject({
|
|
855
|
+
z.strictObject({
|
|
856
|
+
$state: z.string(),
|
|
857
|
+
/** The render's own value → element table, for a root that mounts a different component per
|
|
858
|
+
* value of one prop (`if (mode === 'link') return <ChipLink/>`). Absent for a domain lookup,
|
|
859
|
+
* where the prop's declaration knows the values. */
|
|
860
|
+
map: z.record(z.string(), z.union([z.string(), refSchema])).optional(),
|
|
861
|
+
/** What mounts when no map entry matches — the render's final return. */
|
|
862
|
+
else: z.union([z.string(), refSchema]).optional(),
|
|
863
|
+
/** The layer the chosen element stands in for, so it keeps that layer's markers and classes. */
|
|
864
|
+
layer: z.string().optional()
|
|
865
|
+
}).refine((element) => element.map === void 0 || element.else !== void 0, {
|
|
866
|
+
message: "a `$state` element with a `map` needs an `else`: the element that mounts for a value the map does not name",
|
|
867
|
+
path: ["else"]
|
|
868
|
+
}).describe("An element the instance chooses: the value of the prop `$state` names. With no `map`, the value is resolved in that prop's own domain (an icon prop whose value is a glyph). With a `map` (value → `{ \"__ref\": \"component:<path>\" }`) and an `else`, it is the component the render mounts for that value — a root swapped by a variant — and `layer` names the layer it stands in for.")
|
|
849
869
|
], { error: "an element is `{ \"__ref\": \"layer:<name>\" }` for one of this component's layers, `{ \"__ref\": \"icon:<library>/<Name>\" }` for a glyph, `{ \"__ref\": \"component:<path>\" }` for a component rendered unstyled, an intrinsic tag such as `div`, `Slot` for where a slot prop's content lands, or `{ \"$state\": \"<prop>\" }` for an element the prop chooses" }).describe("What this node renders. Name a layer (`{ \"__ref\": \"layer:<name>\" }`) for anything a style rule should reach.");
|
|
850
870
|
/** The operand each comparison accepts. Checked against the framework's operator set both ways, so
|
|
851
871
|
* the schema can neither drop an operator the readers handle nor accept one they don't. */
|
|
@@ -1028,8 +1048,16 @@ const AnatomyNode = defineSubEntity({
|
|
|
1028
1048
|
const body = parent;
|
|
1029
1049
|
const shape = {
|
|
1030
1050
|
element: nodeElement,
|
|
1051
|
+
/** A package factory the element renders THROUGH — `{ "__ref": "package:motion/react#motion" }`
|
|
1052
|
+
* for a layer the render wrapped as `motion.create(Box)`. The registry holds an entry for each
|
|
1053
|
+
* (wrapper, element) pair the anatomies name, so the wrapped layer paints as the module does,
|
|
1054
|
+
* reading the same motion props off this node. */
|
|
1055
|
+
wrapper: refSchema.optional().describe("A package factory this node renders through, such as `{ \"__ref\": \"package:motion/react#motion\" }` for a motion-wrapped layer. The node's props then include the wrapper's (`initial`, `animate`, `variants`, `transition`, …)."),
|
|
1031
1056
|
visible: visibility.optional().describe("When the node renders: a condition on a prop value (`{ \"$state\": \"<prop>\", \"eq\": <value> }`), an `$or` of them, or a list that must all hold. Omit for a node that always renders."),
|
|
1032
|
-
props: (config ? openBag(config, {
|
|
1057
|
+
props: (config ? openBag(config, {
|
|
1058
|
+
cssBehind: false,
|
|
1059
|
+
values: anatomyPropValue
|
|
1060
|
+
}) : z.record(z.string(), anatomyPropValue)).optional().describe("Props passed to what the node renders, keyed by prop or style-property name, as literal values. Content is not set here: a `slot` prop's content lands where a `{ \"element\": \"Slot\", \"props\": { \"name\": \"<prop>\" } }` node sits among a parent's `children` (on the root when there is none), and a `forward` prop (`{ \"type\": \"forward\", \"target\": \"label/children\" }`) routes it into that layer with no node entry. `$state` belongs to `visible` and to an `element` a prop chooses."),
|
|
1033
1061
|
text: z.string().optional().describe("Literal text content, for a node that renders a fixed string.")
|
|
1034
1062
|
};
|
|
1035
1063
|
if (!config || !body) return z.object({
|
|
@@ -1063,14 +1091,13 @@ const AnatomyNode = defineSubEntity({
|
|
|
1063
1091
|
const current = key ? body.anatomy?.[key] : void 0;
|
|
1064
1092
|
const element = node.element ?? current?.element;
|
|
1065
1093
|
if (key !== void 0 && body.layers && key in body.layers) {
|
|
1066
|
-
|
|
1067
|
-
if (element !== void 0 && !rendersLayer && !spellsLayer(element, key, body.layers[key])) {
|
|
1094
|
+
if (element !== void 0 && !rendersLayer(element, key) && !spellsLayer(element, key, body.layers[key])) {
|
|
1068
1095
|
const spelled = isRef(element) ? `\`${element.__ref}\`` : JSON.stringify(element);
|
|
1069
1096
|
ctx.issues.push({
|
|
1070
1097
|
code: "custom",
|
|
1071
1098
|
input: node,
|
|
1072
1099
|
path: ["element"],
|
|
1073
|
-
message: `node "${key}" is keyed like the layer "${key}", so it renders that layer: \`{ "__ref": "layer:${key}" }\`. Written as ${spelled} it would paint the element directly, unstyled, outside every style rule that reaches the layer.`
|
|
1100
|
+
message: `node "${key}" is keyed like the layer "${key}", so it renders that layer: \`{ "__ref": "layer:${key}" }\`, or an element the prop chooses with \`"layer": "${key}"\`. Written as ${spelled} it would paint the element directly, unstyled, outside every style rule that reaches the layer.`
|
|
1074
1101
|
});
|
|
1075
1102
|
}
|
|
1076
1103
|
}
|
|
@@ -1287,7 +1314,7 @@ const componentBags = (data) => {
|
|
|
1287
1314
|
if (!isPlainRecord(node)) continue;
|
|
1288
1315
|
collectBag(node.props, (next) => {
|
|
1289
1316
|
node.props = next;
|
|
1290
|
-
}, out);
|
|
1317
|
+
}, out, node.element);
|
|
1291
1318
|
}
|
|
1292
1319
|
collectBag(body?.defaultProps, (next) => {
|
|
1293
1320
|
if (body) body.defaultProps = next;
|
|
@@ -53,6 +53,7 @@ declare const File: EntityClass<z.ZodObject<{
|
|
|
53
53
|
target: z.ZodType<Ref, unknown, z.core.$ZodTypeInternals<Ref, unknown>>;
|
|
54
54
|
names: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
55
55
|
}, z.core.$strip>>>;
|
|
56
|
+
exports: z.ZodOptional<SubEntityClass<z.ZodObject<{}, z.core.$strict>>>;
|
|
56
57
|
}, z.core.$strict>, z.ZodObject<Record<never, never>, z.core.$strip>, "file", Record<never, never>, {
|
|
57
58
|
/**
|
|
58
59
|
* Bytes rather than text, so a restore knows how to write it — derived from the path, because
|
|
@@ -82,6 +82,24 @@ const FileImport = defineSubEntity({
|
|
|
82
82
|
})
|
|
83
83
|
});
|
|
84
84
|
/**
|
|
85
|
+
* One export of a sealed source file that a render places in JSX — `file:components/pagination.tsx#PaginationProvider`.
|
|
86
|
+
*
|
|
87
|
+
* A component file exports more than its components: a context provider the composite wraps its
|
|
88
|
+
* root in, a plain function component two renders share. When a render puts one in the tree, the
|
|
89
|
+
* anatomy has to name it — the same way it names a package's export — and the registry has to
|
|
90
|
+
* import it from the module the file emits to. The member is that name, with the same empty body a
|
|
91
|
+
* `Package` export has: its identity is its name, and the module it lives in is the owning file.
|
|
92
|
+
*
|
|
93
|
+
* Only what a render placed in JSX, for the reason a `Package` lists only that: nobody browses a
|
|
94
|
+
* file's exports, and a hook or a helper no JSX mentions is not a render surface.
|
|
95
|
+
*/
|
|
96
|
+
const FileExport = defineSubEntity({
|
|
97
|
+
name: "exports",
|
|
98
|
+
label: "Export",
|
|
99
|
+
labelPlural: "Exports",
|
|
100
|
+
fields: z.strictObject({})
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
85
103
|
* File (`file:components/card.tsx`) — one artifact the seal owns.
|
|
86
104
|
*
|
|
87
105
|
* A component is a definition plus the React that renders it, and `config.json` cannot hold JSX, so
|
|
@@ -110,13 +128,17 @@ const File = defineEntity({
|
|
|
110
128
|
labelPlural: "Files",
|
|
111
129
|
authoredRefs: false,
|
|
112
130
|
linkable: false,
|
|
131
|
+
bareMember: "exports",
|
|
113
132
|
fields: z.strictObject({
|
|
114
133
|
/** SHA-256 of the exact bytes — what the seal hashed, so a restore verifies rather than trusts. */
|
|
115
134
|
hash: z.string(),
|
|
116
135
|
/** What this file imports — keyed by the text the source wrote, in the order it wrote it. Every
|
|
117
136
|
* local edge, plus each bare specifier a `Package` group already names; one the config names
|
|
118
137
|
* nowhere is left out rather than pointed at a group minted to receive it. */
|
|
119
|
-
imports: FileImport.optional()
|
|
138
|
+
imports: FileImport.optional(),
|
|
139
|
+
/** The exports a render placed in JSX — a same-file provider or function component an anatomy
|
|
140
|
+
* names by `file:<path>#<export>`. Absent for a file no anatomy reaches into. */
|
|
141
|
+
exports: FileExport.optional()
|
|
120
142
|
}),
|
|
121
143
|
computed: {
|
|
122
144
|
/**
|
|
@@ -130,7 +152,10 @@ const File = defineEntity({
|
|
|
130
152
|
* boolean read as a tri-state at every call site.
|
|
131
153
|
*/
|
|
132
154
|
binary: ({ path }) => isBinaryAsset(path) },
|
|
133
|
-
subEntities: {
|
|
155
|
+
subEntities: {
|
|
156
|
+
imports: FileImport,
|
|
157
|
+
exports: FileExport
|
|
158
|
+
}
|
|
134
159
|
});
|
|
135
160
|
//#endregion
|
|
136
161
|
export { File, SCRIPT_EXTENSIONS, isBinaryAsset };
|
|
@@ -17,6 +17,21 @@ const styleValue = z.lazy(() => z.union([
|
|
|
17
17
|
gradient,
|
|
18
18
|
z.record(z.string(), styleValue)
|
|
19
19
|
]));
|
|
20
|
+
const anatomyPropValue = z.lazy(() => z.union([
|
|
21
|
+
z.string(),
|
|
22
|
+
z.number(),
|
|
23
|
+
z.boolean(),
|
|
24
|
+
z.null(),
|
|
25
|
+
refSchema,
|
|
26
|
+
derivedColor,
|
|
27
|
+
gradient,
|
|
28
|
+
z.array(z.union([
|
|
29
|
+
z.string(),
|
|
30
|
+
z.number(),
|
|
31
|
+
z.boolean()
|
|
32
|
+
])),
|
|
33
|
+
z.record(z.string(), anatomyPropValue)
|
|
34
|
+
]));
|
|
20
35
|
/**
|
|
21
36
|
* A style bag as a keyed COLLECTION — style-prop key → {@link styleValue}, with each key a member
|
|
22
37
|
* in its own right.
|
|
@@ -68,11 +83,15 @@ function isPlainRecord(value) {
|
|
|
68
83
|
* written into its parent before that parent is rebuilt. A modifier block holds a bag of the same
|
|
69
84
|
* shape, so this is the only recursion either hook needs.
|
|
70
85
|
*/
|
|
71
|
-
function collectBag(bag, write, out) {
|
|
86
|
+
function collectBag(bag, write, out, element) {
|
|
72
87
|
if (!isPlainRecord(bag)) return;
|
|
73
|
-
out.push({
|
|
88
|
+
out.push(element === void 0 ? {
|
|
74
89
|
bag,
|
|
75
90
|
write
|
|
91
|
+
} : {
|
|
92
|
+
bag,
|
|
93
|
+
write,
|
|
94
|
+
element
|
|
76
95
|
});
|
|
77
96
|
for (const [key, value] of Object.entries(bag)) {
|
|
78
97
|
if (!key.startsWith("_") || !isPlainRecord(value)) continue;
|
|
@@ -234,8 +253,8 @@ function addBagKeyIssues({ config, bag, ctx, nested, declared, cssBehind = true,
|
|
|
234
253
|
}
|
|
235
254
|
/** The open bag — every key a style value, with token names bound to their tokens. `cssBehind`
|
|
236
255
|
* as in {@link bagKeyIssue}: off for a bag that may reach an intrinsic element's attributes. */
|
|
237
|
-
function openBag(config, { cssBehind = true } = {}) {
|
|
238
|
-
return z.record(z.string(),
|
|
256
|
+
function openBag(config, { cssBehind = true, values = styleValue } = {}) {
|
|
257
|
+
return z.record(z.string(), values).superRefine((bag, ctx) => addBagKeyIssues({
|
|
239
258
|
config,
|
|
240
259
|
bag,
|
|
241
260
|
ctx,
|
|
@@ -244,4 +263,4 @@ function openBag(config, { cssBehind = true } = {}) {
|
|
|
244
263
|
})).transform((bag) => bindBagTokens(config, bag));
|
|
245
264
|
}
|
|
246
265
|
//#endregion
|
|
247
|
-
export { Style, addBagKeyIssues, bagNameEdges, bagRewriteName, bindBagTokens, camelCase, collectBag, isPlainRecord, openBag, styleValue };
|
|
266
|
+
export { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, camelCase, collectBag, isPlainRecord, openBag, styleValue };
|
|
@@ -72,7 +72,7 @@ interface RecordedSource {
|
|
|
72
72
|
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
73
73
|
* that replaced it (see `detectedWireVersion`).
|
|
74
74
|
*/
|
|
75
|
-
declare const SERIALIZED_CONFIG_VERSION =
|
|
75
|
+
declare const SERIALIZED_CONFIG_VERSION = 20260914181845;
|
|
76
76
|
interface SerializedConfig {
|
|
77
77
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
78
78
|
readonly version: number;
|
package/dist/framework/Config.js
CHANGED
|
@@ -4715,7 +4715,7 @@ var Config = class Config {
|
|
|
4715
4715
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
4716
4716
|
hydrateFrom(stored, options) {
|
|
4717
4717
|
const claimed = schemaVersionOf(stored);
|
|
4718
|
-
if (claimed >
|
|
4718
|
+
if (claimed > 20260914181845) throw new SchemaVersionTooNew(claimed);
|
|
4719
4719
|
const wire = upgradeSerializedConfig(stored);
|
|
4720
4720
|
if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating, or port the repo once with \`uds migrate\`.`);
|
|
4721
4721
|
const leftover = wire.options;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//#region src/framework/anatomy-check.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Anatomy check — does a component's ANATOMY, expanded by the config, paint what the component's
|
|
4
|
+
* MODULE paints?
|
|
5
|
+
*
|
|
6
|
+
* The two are one component seen two ways: the editor paints a matrix cell from the stored anatomy
|
|
7
|
+
* (`expandSpec`), a canvas paints a dropped instance from the module. Every place they differ is a
|
|
8
|
+
* bug in the anatomy reader, the expansion, or a render the anatomy grammar cannot express. This
|
|
9
|
+
* module is the comparison: both renderings reduced to the same flat shape ({@link RenderedNode}),
|
|
10
|
+
* aligned, and their differences reported by kind, so a check can fail on them and a person can read
|
|
11
|
+
* them.
|
|
12
|
+
*
|
|
13
|
+
* Two ways in. {@link snapshotHtml} reads the markup `react-dom/server` produces, which is what
|
|
14
|
+
* `uds anatomy` compares in a Bun process — structure, text and classes, since no layout runs there.
|
|
15
|
+
* A browser surface snapshots live DOM instead (the studio's `?anatomyCheck=true`), and adds computed
|
|
16
|
+
* style to each node; {@link diffRendered} compares whatever a node carries.
|
|
17
|
+
*/
|
|
18
|
+
/** One rendered element, reduced to what anatomy cares about. */
|
|
19
|
+
interface RenderedNode {
|
|
20
|
+
/** The component whose layer this element is, when it is one (`data-uds-component`). */
|
|
21
|
+
readonly component?: string;
|
|
22
|
+
/** The layer (`data-uds-layer`), when the element is one. */
|
|
23
|
+
readonly layer?: string;
|
|
24
|
+
readonly tag: string;
|
|
25
|
+
/** The element's OWN text — its direct text nodes, whitespace collapsed — never its children's. */
|
|
26
|
+
readonly text: string;
|
|
27
|
+
readonly classes: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* Style, as the snapshot could read it. From live DOM, the computed subset {@link STYLE_PROPERTIES},
|
|
30
|
+
* compared where both sides carry a property. From markup, the element's inline `style`
|
|
31
|
+
* declarations — the only style markup has — compared as a whole: a declaration one side writes
|
|
32
|
+
* and the other does not is a difference, since inline is what a render's literal becomes.
|
|
33
|
+
*/
|
|
34
|
+
readonly style: Readonly<Record<string, string>>;
|
|
35
|
+
/** `true` when `style` holds inline declarations rather than a computed subset. */
|
|
36
|
+
readonly inlineStyle?: boolean;
|
|
37
|
+
/** What the alignment matches on — see {@link renderedSignatureOf}. */
|
|
38
|
+
readonly signature: string;
|
|
39
|
+
}
|
|
40
|
+
type AnatomyIssueKind = /** The module renders this element and the anatomy does not. */'missing' /** The anatomy renders this element and the module does not. */ | 'extra' /** Both render it — aligned as the same layer — as different elements (`div` against `span`). */ | 'tag' /** Both render it, with different own text. */ | 'text' /** Both render it, with a computed style that differs beyond tolerance. */ | 'style'
|
|
41
|
+
/** Both render it, with different class sets. Classes are how a style arrives, so where a
|
|
42
|
+
* computed style is available this is informational; where none is (markup only) it is the
|
|
43
|
+
* paint difference itself — see `diffRendered`'s `classesFail`. */
|
|
44
|
+
| 'classes';
|
|
45
|
+
interface AnatomyIssue {
|
|
46
|
+
readonly kind: AnatomyIssueKind;
|
|
47
|
+
/** The element, by signature, plus its ordinal among same-signature elements (`Modal/divider#2`). */
|
|
48
|
+
readonly node: string;
|
|
49
|
+
readonly property?: string;
|
|
50
|
+
readonly anatomy?: string;
|
|
51
|
+
readonly instance?: string;
|
|
52
|
+
}
|
|
53
|
+
interface AnatomyReport {
|
|
54
|
+
/** `true` when nothing that counts as a paint difference was found. */
|
|
55
|
+
readonly ok: boolean;
|
|
56
|
+
readonly issues: readonly AnatomyIssue[];
|
|
57
|
+
/** How many elements each side rendered under the component root. */
|
|
58
|
+
readonly counts: {
|
|
59
|
+
readonly anatomy: number;
|
|
60
|
+
readonly instance: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The computed properties compared on aligned elements. Paint, box, type — what a designer would
|
|
65
|
+
* notice. `width`/`height`/`line-height` compare with a pixel tolerance; everything else exactly,
|
|
66
|
+
* after {@link normalizeStyleValue}.
|
|
67
|
+
*/
|
|
68
|
+
declare const STYLE_PROPERTIES: readonly string[];
|
|
69
|
+
/** Pixel slack for the size comparisons — sub-pixel layout and font rounding. */
|
|
70
|
+
declare const SIZE_TOLERANCE_PX = 1.5;
|
|
71
|
+
/** The alignment key: a layer is `Component/layer`; anything else is its tag. */
|
|
72
|
+
declare function renderedSignatureOf(tag: string, component: string | undefined, layer: string | undefined): string;
|
|
73
|
+
/**
|
|
74
|
+
* Every element under the component root of `html`, in document order, as {@link RenderedNode}s
|
|
75
|
+
* without computed style. The root is the first element carrying `data-uds-component`; hosts and
|
|
76
|
+
* shells above it are scaffolding. `data-anatomy-check-ignore` subtrees are skipped.
|
|
77
|
+
*/
|
|
78
|
+
declare function snapshotHtml(html: string): RenderedNode[];
|
|
79
|
+
/** `rgb(…)` spacing and a trailing `px` zero are the browser's spelling, not a difference. */
|
|
80
|
+
declare function normalizeStyleValue(value: string): string;
|
|
81
|
+
interface DiffOptions {
|
|
82
|
+
/**
|
|
83
|
+
* Count a class-set difference as a failure. Off where computed styles are compared (the class is
|
|
84
|
+
* only how the style arrived); on where only markup is (the class IS the paint).
|
|
85
|
+
*/
|
|
86
|
+
readonly classesFail?: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compare the anatomy's rendering with the module's.
|
|
90
|
+
*
|
|
91
|
+
* Elements align by signature in document order; what the module renders and the anatomy does not
|
|
92
|
+
* is `missing`, the reverse is `extra`. Aligned elements then compare own text, computed style (when
|
|
93
|
+
* both sides carry it) and class set.
|
|
94
|
+
*/
|
|
95
|
+
declare function diffRendered(anatomy: readonly RenderedNode[], instance: readonly RenderedNode[], options?: DiffOptions): AnatomyReport;
|
|
96
|
+
/** The allowlist spelling of an issue: kind, node, and the property for a style difference. */
|
|
97
|
+
declare function anatomyIssueKey(issue: AnatomyIssue): string;
|
|
98
|
+
//#endregion
|
|
99
|
+
export { AnatomyIssue, AnatomyIssueKind, AnatomyReport, DiffOptions, RenderedNode, SIZE_TOLERANCE_PX, STYLE_PROPERTIES, anatomyIssueKey, diffRendered, normalizeStyleValue, renderedSignatureOf, snapshotHtml };
|