@yahoo/uds-create-config 4.5.2 → 4.7.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.
Files changed (72) hide show
  1. package/dist/configs/CanvasConfig.d.ts +900 -80
  2. package/dist/configs/react-native-system.d.ts +603 -55
  3. package/dist/configs/system.d.ts +603 -55
  4. package/dist/configs/system.js +7 -1
  5. package/dist/entities/system/Component.d.ts +480 -39
  6. package/dist/entities/system/Component.js +264 -93
  7. package/dist/entities/system/File.d.ts +1 -1
  8. package/dist/entities/system/LinkedSystem.d.ts +2 -2
  9. package/dist/entities/system/Operation.d.ts +1 -1
  10. package/dist/entities/system/StyleProperty.d.ts +8 -0
  11. package/dist/entities/system/StyleProperty.js +12 -1
  12. package/dist/entities/system/Tool.d.ts +1 -1
  13. package/dist/entities/system/component-module.js +1 -1
  14. package/dist/entities/system/defineComponent.d.ts +45 -7
  15. package/dist/entities/system/defineComponent.js +52 -25
  16. package/dist/framework/Config.d.ts +8 -2
  17. package/dist/framework/Config.js +97 -23
  18. package/dist/framework/class-names.d.ts +4 -2
  19. package/dist/framework/class-names.js +56 -17
  20. package/dist/framework/defineDerivedEntity.d.ts +1 -1
  21. package/dist/framework/defineEntity.d.ts +2 -2
  22. package/dist/framework/defineSubEntity.d.ts +3 -2
  23. package/dist/framework/preview-store.d.ts +19 -0
  24. package/dist/framework/preview-store.js +18 -0
  25. package/dist/framework/projections.d.ts +28 -8
  26. package/dist/framework/projections.js +50 -16
  27. package/dist/framework/prop-surface.d.ts +21 -1
  28. package/dist/framework/prop-surface.js +99 -21
  29. package/dist/framework/render-spec.d.ts +103 -3
  30. package/dist/framework/render-spec.js +285 -37
  31. package/dist/framework/schema-version.d.ts +1 -1
  32. package/dist/framework/schema-version.js +5 -5
  33. package/dist/framework/source-integrity.d.ts +14 -1
  34. package/dist/framework/utils/refs.js +10 -1
  35. package/dist/framework/validate-spec.d.ts +2 -0
  36. package/dist/framework/validate-spec.js +13 -0
  37. package/dist/index.d.ts +15 -12
  38. package/dist/index.js +7 -6
  39. package/dist/migrations/2.0.0/v1-artifact.d.ts +305 -29
  40. package/dist/migrations/20260919051027_prop_conditions_and_anatomy_positions_at_rest.d.ts +22 -0
  41. package/dist/migrations/20260919051027_prop_conditions_and_anatomy_positions_at_rest.js +10 -0
  42. package/dist/migrations/20260920004019_preview_conditions_keyed.d.ts +27 -0
  43. package/dist/migrations/20260920004019_preview_conditions_keyed.js +148 -0
  44. package/dist/migrations/20260920161309_prefixed_token_group.d.ts +12 -0
  45. package/dist/migrations/20260920161309_prefixed_token_group.js +10 -0
  46. package/dist/renderer/wrapRegistry.js +8 -2
  47. package/dist/renderer/wrappers/component-refs.d.ts +14 -0
  48. package/dist/renderer/wrappers/component-refs.js +79 -0
  49. package/dist/renderer/wrappers/hex-normalize.d.ts +5 -7
  50. package/dist/renderer/wrappers/hex-normalize.js +11 -10
  51. package/dist/spec/cell.d.ts +171 -0
  52. package/dist/spec/cell.js +361 -0
  53. package/dist/spec/detach.d.ts +125 -0
  54. package/dist/spec/detach.js +511 -0
  55. package/dist/spec/empty-node-slots.d.ts +11 -23
  56. package/dist/spec/empty-node-slots.js +12 -39
  57. package/dist/spec/index.d.ts +5 -3
  58. package/dist/spec/index.js +3 -3
  59. package/dist/spec/instance.d.ts +77 -0
  60. package/dist/spec/instance.js +80 -0
  61. package/dist/spec/preview-content.d.ts +95 -0
  62. package/dist/spec/preview-content.js +149 -0
  63. package/dist/spec/preview-elements.d.ts +54 -15
  64. package/dist/spec/preview-elements.js +109 -29
  65. package/dist/spec/prop-conditions.d.ts +28 -0
  66. package/dist/spec/prop-conditions.js +39 -0
  67. package/dist/spec/slot.d.ts +206 -0
  68. package/dist/spec/slot.js +542 -0
  69. package/dist/spec/visibility.d.ts +82 -0
  70. package/dist/spec/visibility.js +109 -0
  71. package/dist/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +18 -2
@@ -38,7 +38,7 @@ const componentsByFileCache = /* @__PURE__ */ new WeakMap();
38
38
  /** Documentation and examples never enter emitted component modules. */
39
39
  function buildEntityBody(kind, body) {
40
40
  if (kind !== "component") return body;
41
- const { previewProps: _preview, description: _description, ...buildBody } = body;
41
+ const { preview: _preview, description: _description, ...buildBody } = body;
42
42
  return buildBody;
43
43
  }
44
44
  /**
@@ -1,5 +1,5 @@
1
1
  import { Ref as Ref$1 } from "../../framework/utils/refs.js";
2
- import { RegisteredModifiers, RegisteredStyleProps } from "../../framework/registered.js";
2
+ import { RegisteredModifiers, RegisteredPaths, RegisteredStyleProps } from "../../framework/registered.js";
3
3
  import { Authored } from "../../framework/authoring.js";
4
4
  import { StyleValue } from "./style-bag.js";
5
5
  import { VariantValues } from "./Component.js";
@@ -73,8 +73,17 @@ type LayerPath<L extends LayerMap = LayerMap> = `${keyof L & string}/${string}`;
73
73
  * from anything else: a widened union left a slot prop inferring as `unknown` where the same prop
74
74
  * written by hand stayed a `ReactNode`.
75
75
  */
76
+ /**
77
+ * When a prop applies — the record a style rule's `when` holds: each key a sibling prop, each value
78
+ * one of that prop's values or a list of them. A prop with no condition always applies. Stored with
79
+ * the values as refs (`value:type/color`), which is what lets a value rename cascade into it.
80
+ */
81
+ type PropCondition = Readonly<Record<string, ConditionValue | ConditionValue[]>>;
82
+ /** A value as authored, or the `value:` ref it is stored as. */
83
+ type ConditionValue = string | number | boolean | Ref$1;
76
84
  interface SlotDecl {
77
85
  type: 'slot';
86
+ when?: PropCondition;
78
87
  /**
79
88
  * What may FILL this hole, as refs. Two shapes, both in this repo:
80
89
  *
@@ -131,6 +140,7 @@ interface ForwardDecl<Path extends string = string> {
131
140
  accepts?: Ref$1 | Ref$1[];
132
141
  text?: boolean;
133
142
  required?: boolean;
143
+ when?: PropCondition;
134
144
  }
135
145
  /**
136
146
  * One prop's declaration, in both the form it is AUTHORED in and the form it is STORED in.
@@ -154,20 +164,25 @@ type PropDecl<Path extends string = string> = SlotDecl | ForwardDecl<Path> | {
154
164
  type: 'variant';
155
165
  value: readonly string[] | VariantValues | Ref$1;
156
166
  required?: boolean;
167
+ when?: PropCondition;
157
168
  } | {
158
169
  type: 'styleProperty';
159
170
  value: Ref$1;
160
171
  required?: boolean;
172
+ when?: PropCondition;
161
173
  } | {
162
174
  type: 'composite';
163
175
  value: Ref$1;
164
176
  required?: boolean;
177
+ when?: PropCondition;
165
178
  } | {
166
179
  type: 'motion';
167
180
  required?: boolean;
181
+ when?: PropCondition;
168
182
  } | {
169
183
  type: 'string' | 'number' | 'boolean';
170
184
  required?: boolean;
185
+ when?: PropCondition;
171
186
  };
172
187
  /**
173
188
  * One prop as AUTHORED — a declaration, or one of the two bare forms that already say what they are:
@@ -228,6 +243,13 @@ type ValueOf<D> = D extends readonly (infer Member)[] ? Member : D extends {
228
243
  __ref: `style-property:${infer Target}`;
229
244
  };
230
245
  } ? StylePropValue<Target> : ValueOfDeclaration<D>;
246
+ /**
247
+ * The members a ref-delegated domain offers, read off the registered paths: a variant whose domain
248
+ * is `icon:phosphor` takes every registered `phosphor/<Name>` as `<Name>`, so a misspelled glyph is
249
+ * a compile error once the system is built. A facet ref (`icon:phosphor#variants`), a kind the build
250
+ * did not register, or an unbuilt consumer stays `string`.
251
+ */
252
+ type RegisteredMembers<Kind extends string, Path extends string> = Path extends `${string}#${string}` ? string : Kind extends keyof RegisteredPaths ? [Extract<RegisteredPaths[Kind], `${Path}/${string}`>] extends [never] ? string : Extract<RegisteredPaths[Kind], `${Path}/${string}`> extends `${Path}/${infer Name}` ? Name : string : string;
231
253
  /**
232
254
  * A style prop's values, read off the registry by the path the declaration points at. By the target
233
255
  * and not the prop's own name, since a component may expose a property under a name of its own —
@@ -240,7 +262,9 @@ type StylePropValue<Target extends string> = Target extends keyof RegisteredStyl
240
262
  type ValueOfDeclaration<D> = D extends {
241
263
  type: 'variant';
242
264
  value: infer V;
243
- } ? V extends readonly (infer Member)[] ? Member : V extends VariantValues ? keyof V & string : string : D extends {
265
+ } ? V extends readonly (infer Member)[] ? Member : V extends VariantValues ? keyof V & string : V extends {
266
+ __ref: `${infer Kind}:${infer Path}`;
267
+ } ? RegisteredMembers<Kind, Path> : string : D extends {
244
268
  type: 'string';
245
269
  } ? string : D extends {
246
270
  type: 'number';
@@ -267,7 +291,21 @@ type ValueOfDeclaration<D> = D extends {
267
291
  type Declared<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K] };
268
292
  /** What a prop map really exposes: each declaration's value type, with the `null` tombstones — the
269
293
  * props it DROPS from what it inherits — gone from the surface entirely. */
270
- type DeclaredProps<P> = { [K in keyof P as P[K] extends null ? never : K]: ValueOf<P[K]> };
294
+ type DeclaredProps<P, L extends LayerMap = LayerMap> = { [K in keyof P as P[K] extends null ? never : K]: RoutedValueOf<L, P[K]> };
295
+ /**
296
+ * A forward's consumer type is the far prop's: a route onto a layer component's `name` takes what
297
+ * that component's `name` takes, and a route onto `<input disabled>` takes a boolean. The layers
298
+ * are in scope here, which is what {@link ValueOf} lacks. A text forward is a string whatever it
299
+ * lands on. A route onto `children`, or onto a layer the map doesn't hold (an open `LayerMap`, as in
300
+ * a defaults bag), keeps `ValueOf`'s answer.
301
+ */
302
+ type RoutedValueOf<L extends LayerMap, D> = D extends {
303
+ type: 'forward';
304
+ text: true;
305
+ } ? string : D extends {
306
+ type: 'forward';
307
+ target: `${infer Layer}/${infer Prop}`;
308
+ } ? Prop extends 'children' ? ReactNode : Layer extends keyof L ? Prop extends keyof ElementPropsOf<L[Layer]> ? ElementPropsOf<L[Layer]>[Prop] : unknown : unknown : ValueOf<D>;
271
309
  /**
272
310
  * The props the rendered ELEMENT contributes, `ref` included.
273
311
  *
@@ -348,7 +386,7 @@ type RequiredKey<P, D> = { [K in keyof P]: P[K] extends {
348
386
  * is checked against its interface by relating two generic signatures, and a name type that reads
349
387
  * `P` is not identical on the two sides of that comparison — a name type reading only `E` is.
350
388
  */
351
- type ConsumerDeclaredProps<P, D, E> = Without<Partial<DeclaredProps<P>>, Redefined<E>> & { [K in Exclude<RequiredKey<P, D>, Redefined<E>>]-?: K extends keyof DeclaredProps<P> ? DeclaredProps<P>[K] : never };
389
+ type ConsumerDeclaredProps<P, D, E, L extends LayerMap = LayerMap> = Without<Partial<DeclaredProps<P, L>>, Redefined<E>> & { [K in Exclude<RequiredKey<P, D>, Redefined<E>>]-?: K extends keyof DeclaredProps<P, L> ? DeclaredProps<P, L>[K] : never };
352
390
  /**
353
391
  * The keys a render extra redefines: the ones it names. An open extra (`extends Record<string,
354
392
  * unknown>`) names nothing through its index signature, but a key it declares beside that signature
@@ -395,7 +433,7 @@ type OpenTail<L extends LayerMap> = Registered extends true ? StyleProps & Modif
395
433
  *
396
434
  * Ends in {@link OpenTail}: the system's own surface, open until the system's build has named it.
397
435
  */
398
- type ComponentPropsOf<L extends LayerMap, P extends PropMap, D = Record<never, never>, E = Record<never, never>, F extends string = never, As = never> = ConsumerDeclaredProps<P, D, E> & Partial<E> & Redeclared<RootSurface<L, D, As>, P, E> & Redeclared<ForwardedPropsOf<L, F>, P, E> & AsPropsOf<As, P, E> & Redeclared<OpenTail<L>, P, E>;
436
+ type ComponentPropsOf<L extends LayerMap, P extends PropMap, D = Record<never, never>, E = Record<never, never>, F extends string = never, As = never> = ConsumerDeclaredProps<P, D, E, L> & Partial<E> & Redeclared<RootSurface<L, D, As>, P, E> & Redeclared<ForwardedPropsOf<L, F>, P, E> & AsPropsOf<As, P, E> & Redeclared<OpenTail<L>, P, E>;
399
437
  /**
400
438
  * `T` less the keys the component declares and the keys its render extra names — each taken out on
401
439
  * its own, because {@link Without} leaves `T` whole when the key set is open, and an open render
@@ -502,7 +540,7 @@ type RenderArgs<L extends LayerMap, P extends PropMap> = {
502
540
  * `<input ref={ref}>` in another — and which element is the render's decision.
503
541
  */
504
542
  readonly ref?: Ref<any>;
505
- } & DeclaredProps<P> & Record<string, unknown>;
543
+ } & DeclaredProps<P, L> & Record<string, unknown>;
506
544
  interface ComponentBuilder<L extends LayerMap, P extends PropMap,
507
545
  /** Props the RENDER takes that the config doesn't describe — React handlers, mostly (`onPress`).
508
546
  * They're part of the component's surface but not of its data, which is why they're declared at
@@ -652,4 +690,4 @@ interface DefineComponent {
652
690
  declare function definePropsFirst<const P extends PropMap<LayerPath>>(props: P & NoInfer<{ [K in keyof P]: Exact<P[K], AuthoredProp<LayerPath>> }>): ComponentBuilder<LayerMap, P>;
653
691
  declare const defineComponent: DefineComponent;
654
692
  //#endregion
655
- export { AsInput, AuthoredComponent, AuthoredProp, ComponentBuilder, ComponentPropsOf, LayerElement, LayerMap, PhantomCallProps, PhantomReadProps, PropDecl, PropMap, RenderArgs, StyleRuleInput, defineComponent };
693
+ export { AsInput, AuthoredComponent, AuthoredProp, ComponentBuilder, ComponentPropsOf, LayerElement, LayerMap, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, StyleRuleInput, defineComponent };
@@ -1,4 +1,5 @@
1
- import { isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
1
+ import { canonicalCondition, isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
2
+ import "../../framework/preview-store.js";
2
3
  import { Component, canonicalWhen, layerRef } from "./Component.js";
3
4
  //#region src/entities/system/defineComponent.ts
4
5
  function build(body) {
@@ -20,10 +21,8 @@ function build(body) {
20
21
  render: () => handle,
21
22
  renderCanvas: () => handle,
22
23
  preview: (spec) => {
23
- const patch = {};
24
- if (spec?.defaultProps !== void 0) patch.previewProps = spec.defaultProps;
25
- if (spec?.when?.length) patch.previewWhen = spec.when;
26
- return Object.keys(patch).length === 0 ? handle : next(patch);
24
+ if (spec?.defaultProps === void 0 && !spec?.when?.length) return handle;
25
+ return next({ preview: spec });
27
26
  },
28
27
  toStored: () => toStored(body),
29
28
  assignPath(path) {
@@ -45,12 +44,15 @@ function build(body) {
45
44
  * only one of them runs for any given config: the build reads `.preview()` from source with ts-morph,
46
45
  * this runs when a config is registered, and a reader can't tell which made the config it holds.
47
46
  *
48
- * What this path CANNOT do is name an element the source could. A tag is its own name and a component
49
- * handle carries its adopted path, but a foreign function (a Phosphor glyph, say) is just a function
50
- * at runtime — its `displayName` is a label, not an identity, and two libraries may both export
51
- * `StarIcon`. Such a prop is DROPPED: an absent default renders nothing, while a guessed ref renders
52
- * the wrong glyph and reads as authored. The build resolves those from the import specifier, which is
53
- * why ~a third of the element-valued preview props in a real system only survive a build.
47
+ * A component handed as a VALUE (`icon: PlusIcon`) stores as the bare ref the registry keys it under,
48
+ * so the receiving component gets the component back rather than a node.
49
+ *
50
+ * What this path CANNOT do is name a component the source could. A tag is its own name and a
51
+ * component handle carries its adopted path, but a foreign function (a Phosphor glyph, say) is just
52
+ * a function at runtime — its `displayName` is a label, not an identity, and two libraries may both
53
+ * export `StarIcon`. Such a prop is DROPPED: an absent default renders nothing, while a guessed ref
54
+ * renders the wrong glyph and reads as authored. The build resolves those from the import specifier,
55
+ * which is why ~a third of the element-valued preview props in a real system only survive a build.
54
56
  */
55
57
  function storableProps(props) {
56
58
  if (props === void 0) return void 0;
@@ -61,9 +63,19 @@ function storableProps(props) {
61
63
  }
62
64
  return Object.keys(out).length === 0 ? void 0 : out;
63
65
  }
64
- /** A live React element, by the marker anything pretending to be one also carries. */
66
+ /** A live React element, by the marker anything pretending to be one also carries. A `forwardRef` or
67
+ * `memo` component carries the marker too and is told apart by having no `props`: it is a type. */
65
68
  function isReactElement(value) {
66
- return typeof value === "object" && value !== null && "$$typeof" in value;
69
+ return typeof value === "object" && value !== null && "$$typeof" in value && "props" in value;
70
+ }
71
+ /** A component TYPE handed as a value: a function, or the object `forwardRef` / `memo` return. */
72
+ function isComponentType(value) {
73
+ if (typeof value === "function") return true;
74
+ return typeof value === "object" && value !== null && "$$typeof" in value && !("props" in value);
75
+ }
76
+ /** A component handed as a value, as the ref that names it — `undefined` for one this path can't name. */
77
+ function storableComponentRef(value) {
78
+ return isComponentElement(value) && value.path ? Component.ref(value.path) : void 0;
67
79
  }
68
80
  const FRAGMENT = Symbol.for("react.fragment");
69
81
  /**
@@ -74,12 +86,20 @@ function storableElementName(type) {
74
86
  if (typeof type === "string") return type;
75
87
  if (isComponentElement(type) && type.path) return Component.ref(type.path);
76
88
  }
77
- /** The scalar half of an element's props — what `PreviewElement.props` may hold. */
89
+ /** The scalar half of an element's props, plus a component handed as one — what
90
+ * `PreviewElement.props` may hold. */
78
91
  function storableAttributes(props) {
79
92
  const out = {};
80
93
  for (const [key, value] of Object.entries(props)) {
81
94
  if (key === "children") continue;
82
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") out[key] = value;
95
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
96
+ out[key] = value;
97
+ continue;
98
+ }
99
+ if (isComponentType(value)) {
100
+ const ref = storableComponentRef(value);
101
+ if (ref) out[key] = ref;
102
+ }
83
103
  }
84
104
  return out;
85
105
  }
@@ -118,13 +138,14 @@ function storableElement(value) {
118
138
  /**
119
139
  * One preview-prop value as the config stores it, or `undefined` to drop the prop.
120
140
  *
121
- * Mirrors the arms the build reads from source: a literal, an element, a fragment (several elements
122
- * under one prop), an array, and a nested record.
141
+ * Mirrors the arms the build reads from source: a literal, a component, an element, a fragment
142
+ * (several elements under one prop), an array, and a nested record.
123
143
  */
124
144
  function storableValue(value) {
125
145
  if (value === null) return null;
126
146
  const type = typeof value;
127
147
  if (type === "string" || type === "number" || type === "boolean") return value;
148
+ if (isComponentType(value)) return storableComponentRef(value);
128
149
  if (type !== "object") return void 0;
129
150
  if (isReactElement(value)) {
130
151
  if (value.type === FRAGMENT) {
@@ -299,15 +320,21 @@ function storedForwards(forwards, layers) {
299
320
  /** The authored body as the entity's stored shape — which differs in exactly four places. */
300
321
  function toStored(body) {
301
322
  const stored = { ...body };
302
- if (body.previewProps !== void 0) {
303
- const previewProps = storableProps(body.previewProps);
304
- if (previewProps === void 0) delete stored.previewProps;
305
- else stored.previewProps = previewProps;
323
+ if (body.preview !== void 0) {
324
+ const authored = body.preview;
325
+ const keyed = {};
326
+ const defaults = authored.defaultProps !== void 0 ? storableProps(authored.defaultProps) : void 0;
327
+ if (defaults !== void 0) keyed["*"] = {
328
+ when: null,
329
+ props: defaults
330
+ };
331
+ for (const { when, props } of authored.when ?? []) keyed[canonicalCondition(when)] = {
332
+ when,
333
+ props: storableProps(props) ?? {}
334
+ };
335
+ if (Object.keys(keyed).length === 0) delete stored.preview;
336
+ else stored.preview = keyed;
306
337
  }
307
- if (Array.isArray(body.previewWhen)) stored.previewWhen = body.previewWhen.map(({ when, props }) => ({
308
- when,
309
- props: storableProps(props) ?? {}
310
- }));
311
338
  if (body.props && typeof body.props === "object") {
312
339
  const props = {};
313
340
  for (const [name, decl] of Object.entries(body.props)) props[name] = storedProp(decl, body.layers);
@@ -8,12 +8,12 @@ import { RefGraph, TraverseOptions } from "./ref-graph.js";
8
8
  import { FieldsSchema } from "./utils/field-schema.js";
9
9
  import { SubEntityClass } from "./defineSubEntity.js";
10
10
  import { LocalOverlay, SourceAnswer, SourceOverlay, SourceResolver, SourceState } from "./sources.js";
11
+ import { EntityKind } from "./registry.js";
11
12
  import { AuthoredEntry } from "./utils/group.js";
12
13
  import { EntityClass } from "./defineEntity.js";
13
14
  import { BreakPlan, LinkPlan, SourceRowState, UnlinkPlan } from "../entities/system/link-plan.js";
14
15
  import { InlineOptions } from "./inline-sources.js";
15
16
  import { Snapshot } from "./snapshot.js";
16
- import { EntityKind } from "./registry.js";
17
17
 
18
18
  //#region src/framework/Config.d.ts
19
19
  /**
@@ -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 = 20260918161049;
75
+ declare const SERIALIZED_CONFIG_VERSION = 20260920161309;
76
76
  interface SerializedConfig {
77
77
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
78
78
  readonly version: number;
@@ -1669,6 +1669,12 @@ declare class Config {
1669
1669
  * same field is a second thing to keep in step.
1670
1670
  */
1671
1671
  private sourceDirective;
1672
+ /**
1673
+ * The source body behind a minted row and the overlay the pin currently lays over it, both as the
1674
+ * source spells them — what an edit to the row is judged against. `undefined` when the source is
1675
+ * not attached or has nothing at the path.
1676
+ */
1677
+ private mintedOverlayBody;
1672
1678
  /**
1673
1679
  * One of a source's entity bodies as it reads from here — their fields with their refs qualified
1674
1680
  * into this namespace, and `undefined` for a path they have nothing at.
@@ -367,31 +367,82 @@ function dropOrphans(sub, def, target, namespace) {
367
367
  }
368
368
  /**
369
369
  * Follow a member's name where the item holds it as a value keyed by that member's owner — a
370
- * Component's `defaultProps` and `previewProps`, which are both `{ [prop]: value }`.
370
+ * Component's `defaultProps`, and the `props` of every `preview` entry, which are all
371
+ * `{ [prop]: value }`.
371
372
  *
372
373
  * The third rename axis, after refs and record keys, and the only one where the name appears as
373
374
  * neither. `to: undefined` is the delete case: a default naming a value that no longer exists isn't
374
375
  * a dangling pointer anyone sees, it's a component that quietly reports a default it hasn't got.
376
+ * A field is a `/` path, and a `*` segment stands for every key at that level, as in
377
+ * {@link rekeyAt}.
375
378
  */
376
379
  function renameNamedValues(sub, def, owner, from, to) {
377
380
  if (!sub?.namedValuesIn?.length || owner === void 0) return def;
378
381
  let out = def;
379
382
  for (const field of sub.namedValuesIn) {
380
- const record = out[field];
381
- if (!isPlainObject(record) || record[owner] !== from) continue;
383
+ const next = renameValueAt(out, field.split("/"), owner, from, to);
384
+ if (isPlainObject(next)) out = next;
385
+ }
386
+ return out;
387
+ }
388
+ /** Rewrite the value under `owner` in the record `path` addresses, from `from` to `to` (or drop it). */
389
+ function renameValueAt(node, path, owner, from, to) {
390
+ if (!isPlainObject(node)) return node;
391
+ const [head, ...rest] = path;
392
+ if (head === void 0) return node;
393
+ if (rest.length === 0) {
394
+ const record = node[head];
395
+ if (!isPlainObject(record) || record[owner] !== from) return node;
382
396
  const next = { ...record };
383
397
  if (to === void 0) delete next[owner];
384
398
  else next[owner] = to;
385
- out = {
399
+ return {
400
+ ...node,
401
+ [head]: next
402
+ };
403
+ }
404
+ if (head === "*") {
405
+ let changed = false;
406
+ const next = {};
407
+ for (const [key, value] of Object.entries(node)) {
408
+ const updated = renameValueAt(value, rest, owner, from, to);
409
+ next[key] = updated;
410
+ if (updated !== value) changed = true;
411
+ }
412
+ return changed ? next : node;
413
+ }
414
+ const updated = renameValueAt(node[head], rest, owner, from, to);
415
+ return updated === node[head] ? node : {
416
+ ...node,
417
+ [head]: updated
418
+ };
419
+ }
420
+ /**
421
+ * Drop the members a name removal left declaring nothing. A `keyedByNameIn` path through another
422
+ * collection (`preview/*\/props`) removes a key from each of that collection's members, and a member
423
+ * that held only that key now says nothing; the collection's own `vacant` decides, the same test a
424
+ * `sub-update` asks before bringing one into being.
425
+ */
426
+ function dropVacated(entry, sub, def) {
427
+ if (!sub?.keyedByNameIn?.length) return def;
428
+ let out = def;
429
+ for (const path of sub.keyedByNameIn) {
430
+ const [field, wildcard] = path.split("/");
431
+ if (wildcard !== "*") continue;
432
+ const collectionSub = entry.subEntities[field];
433
+ const collection = out[field];
434
+ if (!collectionSub?.vacant || !isPlainObject(collection)) continue;
435
+ const kept = Object.fromEntries(Object.entries(collection).filter(([, member]) => collectionSub.vacant?.(member) !== true));
436
+ if (Object.keys(kept).length !== Object.keys(collection).length) out = {
386
437
  ...out,
387
- [field]: next
438
+ [field]: kept
388
439
  };
389
440
  }
390
441
  return out;
391
442
  }
392
443
  /**
393
444
  * Follow a member's name where the item holds it as a record key — a component's `defaultProps` and
394
- * `previewProps` are both `{ [prop]: value }`.
445
+ * every `preview` entry's `props` are `{ [prop]: value }`.
395
446
  *
396
447
  * The other half of {@link renameNamedValues}, which reaches the same two fields from the value side.
397
448
  * `to: undefined` is the delete case, and it matters as much as the rename: a default left under the
@@ -1478,17 +1529,19 @@ var Config = class Config {
1478
1529
  strict: true
1479
1530
  });
1480
1531
  if (!resolved) reject("NOT_FOUND", `Cannot edit ${patch.kind} "${patch.path}" — its source "${extension.__ref}" is unavailable`);
1481
- const written = applyPathDelta(resolved.toJSON(), delta, isField);
1482
- const local = { ...extension.extend };
1532
+ const current = isMintedRow(stored) && sourceOf(extension) !== void 0 ? this.at(snapshot).mintedOverlayBody({
1533
+ slug: sourceOf(extension),
1534
+ kind: patch.kind,
1535
+ path: pathOf(extension)
1536
+ }) : void 0;
1537
+ const written = applyPathDelta(current ? extendBody(current.source, current.overlay) : resolved.toJSON(), delta, isField);
1538
+ const local = { ...current ? current.overlay : extension.extend };
1483
1539
  for (const field of touched) if (Object.hasOwn(delta, field) && delta[field] === null) delete local[field];
1484
1540
  else if (Object.hasOwn(written, field)) local[field] = written[field];
1485
1541
  else delete local[field];
1486
1542
  const sourceBody = this.configFor(extension)?.resolve(patch.kind, pathOf(extension))?.toJSON();
1487
1543
  if (!sourceBody) reject("NOT_FOUND", `Cannot edit ${patch.kind} "${patch.path}" — its source "${extension.__ref}" is unavailable`);
1488
- const candidate = {
1489
- ...sourceBody,
1490
- ...local
1491
- };
1544
+ const candidate = extendBody(sourceBody, local);
1492
1545
  const createSchema = wholeBodySchema(entry, { config: this });
1493
1546
  let merged = candidate;
1494
1547
  if (createSchema) {
@@ -1667,6 +1720,7 @@ var Config = class Config {
1667
1720
  def = store(def, collection);
1668
1721
  def = renameNamedValues(sub, def, patch.owner, patch.key, void 0);
1669
1722
  def = renameKeyedByName(sub, def, patch.key, void 0);
1723
+ def = dropVacated(entry, sub, def);
1670
1724
  def = dropOrphans(sub, def, `${leafOf(patch.field).replace(/s$/, "")}:${refPathOf(patch, patch.key)}`, sub?.namespaces ? {
1671
1725
  kind: sub.namespaces,
1672
1726
  path: patch.key
@@ -2181,20 +2235,12 @@ var Config = class Config {
2181
2235
  if (!base) return void 0;
2182
2236
  const slug = sourceOf(extension);
2183
2237
  const owns = (kindName) => source?.ownedKinds().includes(kindName) ?? false;
2184
- const merged = { ...slug ? qualifyRefs({
2238
+ return extendBody(slug ? qualifyRefs({
2185
2239
  value: base.toJSON(),
2186
2240
  source: slug,
2187
2241
  owns,
2188
2242
  namesItAlready: (target) => this.borrowsExactly(target, slug)
2189
- }) : base.toJSON() };
2190
- for (const [field, value] of Object.entries(extension.extend ?? {})) {
2191
- const base = merged[field];
2192
- merged[field] = isPlainRecord(base) && isPlainRecord(value) ? {
2193
- ...base,
2194
- ...value
2195
- } : value;
2196
- }
2197
- return merged;
2243
+ }) : base.toJSON(), extension.extend ?? {});
2198
2244
  }
2199
2245
  /**
2200
2246
  * Resolve one locally extended linked item: the pinned source body, shallowly overlaid by the fields
@@ -2993,6 +3039,22 @@ var Config = class Config {
2993
3039
  return { claimed: false };
2994
3040
  }
2995
3041
  /**
3042
+ * The source body behind a minted row and the overlay the pin currently lays over it, both as the
3043
+ * source spells them — what an edit to the row is judged against. `undefined` when the source is
3044
+ * not attached or has nothing at the path.
3045
+ */
3046
+ mintedOverlayBody({ slug, kind, path }) {
3047
+ const source = this.sourceFor(slug)?.resolve(kind, path)?.toJSON();
3048
+ if (!source) return void 0;
3049
+ return {
3050
+ source,
3051
+ overlay: overlayBody({
3052
+ overlay: this.sourceDirective(slug, kind, path).overlay,
3053
+ theirs: source
3054
+ }) ?? {}
3055
+ };
3056
+ }
3057
+ /**
2996
3058
  * One of a source's entity bodies as it reads from here — their fields with their refs qualified
2997
3059
  * into this namespace, and `undefined` for a path they have nothing at.
2998
3060
  *
@@ -4729,7 +4791,7 @@ var Config = class Config {
4729
4791
  * links from the provided sources. A declared `linkedKind` with no provided source throws. */
4730
4792
  hydrateFrom(stored, options) {
4731
4793
  const claimed = schemaVersionOf(stored);
4732
- if (claimed > 20260918161049) throw new SchemaVersionTooNew(claimed);
4794
+ if (claimed > 20260920161309) throw new SchemaVersionTooNew(claimed);
4733
4795
  const wire = upgradeSerializedConfig(stored);
4734
4796
  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\`.`);
4735
4797
  const leftover = wire.options;
@@ -4855,6 +4917,18 @@ function loadItemChange(meta, kind, path, raw, phase = "author") {
4855
4917
  * built from a target the closure already resolved.
4856
4918
  */
4857
4919
  /**
4920
+ * `base` with `delta` laid over it: plain records merge member by member at every depth, and anything
4921
+ * else in `delta` — a scalar, an array, a ref — replaces what sits at its key. {@link Config.extensionBody}
4922
+ * and {@link canBorrow} read a linked extension through this, so the one merge decides both.
4923
+ */
4924
+ function extendBody(base, delta) {
4925
+ if (!isPlainRecord(base) || !isPlainRecord(delta)) return delta;
4926
+ if (isRef(base) || isRef(delta)) return delta;
4927
+ const merged = { ...base };
4928
+ for (const [field, value] of Object.entries(delta)) merged[field] = extendBody(merged[field], value);
4929
+ return merged;
4930
+ }
4931
+ /**
4858
4932
  * One stored `extend` from a pin's two verbs.
4859
4933
  *
4860
4934
  * They differ in what they assert rather than in what they store, so both land in one body and
@@ -150,8 +150,10 @@ declare function componentPropClasses(config: Config, path: string, state?: Reco
150
150
  * travels to it as one, whether or not the element declares it: a composite (`variant` on a
151
151
  * `Text`-backed layer, which the receiver resolves to its own class), the receiver's own variant or
152
152
  * scalar prop (`size: 'sm'` on an Icon layer), or an attribute of a plain element (`as: 'div'`,
153
- * `type: 'button'`, `aria-hidden`). A receiver prop with a channel of its own — a style property
154
- * under another name, a slot, a forward, motion — stays out, since that channel already carries it.
153
+ * `type: 'button'`, `aria-hidden`), or a value the rule hands a receiver's forward or slot
154
+ * (`children: 'AI'` on a badge layer, `icon: 'XIcon'` on a button layer), which the emitted module
155
+ * routes the same way. A style property under another name and motion stay out, since the
156
+ * stylesheet and the motion classes already carry them.
155
157
  *
156
158
  * Forwarding rather than resolving is what makes a layer's styling live-editable at all: a class only
157
159
  * exists once a build has emitted the stylesheet, so an edit that makes a NEW style rule paints