@yahoo/uds-create-config 4.6.0 → 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 (71) 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 +2 -2
  17. package/dist/framework/Config.js +59 -7
  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/utils/refs.js +10 -1
  34. package/dist/framework/validate-spec.d.ts +2 -0
  35. package/dist/framework/validate-spec.js +13 -0
  36. package/dist/index.d.ts +14 -11
  37. package/dist/index.js +6 -5
  38. package/dist/migrations/2.0.0/v1-artifact.d.ts +305 -29
  39. package/dist/migrations/20260919051027_prop_conditions_and_anatomy_positions_at_rest.d.ts +22 -0
  40. package/dist/migrations/20260919051027_prop_conditions_and_anatomy_positions_at_rest.js +10 -0
  41. package/dist/migrations/20260920004019_preview_conditions_keyed.d.ts +27 -0
  42. package/dist/migrations/20260920004019_preview_conditions_keyed.js +148 -0
  43. package/dist/migrations/20260920161309_prefixed_token_group.d.ts +12 -0
  44. package/dist/migrations/20260920161309_prefixed_token_group.js +10 -0
  45. package/dist/renderer/wrapRegistry.js +8 -2
  46. package/dist/renderer/wrappers/component-refs.d.ts +14 -0
  47. package/dist/renderer/wrappers/component-refs.js +79 -0
  48. package/dist/renderer/wrappers/hex-normalize.d.ts +5 -7
  49. package/dist/renderer/wrappers/hex-normalize.js +11 -10
  50. package/dist/spec/cell.d.ts +171 -0
  51. package/dist/spec/cell.js +361 -0
  52. package/dist/spec/detach.d.ts +125 -0
  53. package/dist/spec/detach.js +511 -0
  54. package/dist/spec/empty-node-slots.d.ts +11 -23
  55. package/dist/spec/empty-node-slots.js +12 -39
  56. package/dist/spec/index.d.ts +5 -3
  57. package/dist/spec/index.js +3 -3
  58. package/dist/spec/instance.d.ts +77 -0
  59. package/dist/spec/instance.js +80 -0
  60. package/dist/spec/preview-content.d.ts +95 -0
  61. package/dist/spec/preview-content.js +149 -0
  62. package/dist/spec/preview-elements.d.ts +54 -15
  63. package/dist/spec/preview-elements.js +109 -29
  64. package/dist/spec/prop-conditions.d.ts +28 -0
  65. package/dist/spec/prop-conditions.js +39 -0
  66. package/dist/spec/slot.d.ts +206 -0
  67. package/dist/spec/slot.js +542 -0
  68. package/dist/spec/visibility.d.ts +82 -0
  69. package/dist/spec/visibility.js +109 -0
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +18 -2
@@ -1,5 +1,5 @@
1
1
  import { reject, rejectData } from "../../framework/rejection.js";
2
- import { isRef, kindOf, memberOf, pathOf, ref, refLeaf, refSchema, splitRef } from "../../framework/utils/refs.js";
2
+ import { canonicalCondition, isRef, kindOf, memberOf, pathOf, ref, refLeaf, refSchema, splitRef } from "../../framework/utils/refs.js";
3
3
  import { isPlainObject } from "../../framework/utils/field-path.js";
4
4
  import { pathInput } from "../../framework/schemas.js";
5
5
  import { defineEntity } from "../../framework/defineEntity.js";
@@ -9,9 +9,13 @@ import { childrenPolicy } from "./element.js";
9
9
  import { listedIn } from "../../framework/utils/enumerated.js";
10
10
  import { valueSchemaOf } from "../../framework/value-domain.js";
11
11
  import { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
12
+ import { storedPreview } from "../../framework/preview-store.js";
12
13
  import { propValueDomain, resolveComponentProps, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
13
14
  import { intoName, routedPropIn, routesContent } from "../../framework/prop-surface.js";
14
15
  import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../framework/render-spec.js";
16
+ import { listSlots, namedSlots, slotHostedBy, slotOf } from "../../spec/slot.js";
17
+ import { cellAt } from "../../spec/cell.js";
18
+ import { instanceAt } from "../../spec/instance.js";
15
19
  import { Package } from "./Package.js";
16
20
  import { File } from "./File.js";
17
21
  import { z } from "zod";
@@ -303,8 +307,8 @@ const VariantValue = defineSubEntity({
303
307
  * which takes its styling with it. */
304
308
  deprecated: z.boolean().optional()
305
309
  }),
306
- namedValuesIn: ["defaultProps", "previewProps"],
307
- orphans: ["styles"]
310
+ namedValuesIn: ["defaultProps", "preview/*/props"],
311
+ orphans: ["styles", "preview"]
308
312
  });
309
313
  /**
310
314
  * A variant's domain: the values it owns, or a ref that delegates to whatever owns them.
@@ -347,60 +351,87 @@ function routedTarget(decl) {
347
351
  into: decl.target.slice(separator + 1)
348
352
  };
349
353
  }
350
- const propShape = z.discriminatedUnion("type", [
351
- z.strictObject({
352
- type: z.literal("slot"),
353
- text: z.boolean().optional(),
354
- accepts: z.union([refSchema, z.array(refSchema)]).optional(),
355
- required: z.boolean().optional()
356
- }),
357
- z.strictObject({
358
- type: z.literal("variant"),
359
- value: variantDomain,
360
- required: z.boolean().optional()
361
- }),
362
- z.strictObject({
363
- type: z.literal("forward"),
364
- target: z.union([layerPath, refSchema]),
365
- into: z.union([z.string(), refSchema]).optional(),
366
- accepts: z.union([refSchema, z.array(refSchema)]).optional(),
367
- text: z.boolean().optional(),
368
- required: z.boolean().optional()
369
- }).transform(routedTarget),
370
- z.strictObject({
371
- type: z.literal("styleProperty"),
372
- value: refSchema
373
- }),
374
- z.strictObject({
375
- type: z.literal("composite"),
376
- value: refSchema
377
- }),
378
- z.strictObject({
379
- type: z.literal("motion"),
380
- value: refSchema.default({ __ref: "motion:" })
381
- }),
382
- z.strictObject({
383
- type: z.literal("string"),
384
- value: z.string().optional(),
385
- required: z.boolean().optional()
386
- }),
387
- z.strictObject({
388
- type: z.literal("number"),
389
- value: z.number().optional(),
390
- required: z.boolean().optional()
391
- }),
392
- z.strictObject({
393
- type: z.literal("boolean"),
394
- value: z.boolean().optional(),
395
- required: z.boolean().optional()
396
- })
397
- ], { error: (issue) => {
398
- const type = issue.input?.type;
399
- if (typeof type !== "string") return void 0;
400
- const types = PROP_TYPES.join(", ");
401
- if (/^style-?property$/i.test(type)) return `\`${type}\` is not a prop type; a style property is exposed as \`{ "type": "styleProperty", "value": { "__ref": "style-property:<name>" } }\`. The types are ${types}.`;
402
- return `\`${type}\` is not a prop type. The types are ${types}; a style property is \`{ "type": "styleProperty", "value": { "__ref": "style-property:<name>" } }\`, content is \`{ "type": "slot", "text": true }\`.`;
403
- } });
354
+ /** A prop that applies only under a condition on its siblings — the record a style rule's `when`
355
+ * holds. Open here, where no owner is known; `propSchemaFor` narrows it to the owner's props. */
356
+ const conditionLeaf = z.union([
357
+ z.string(),
358
+ z.number(),
359
+ z.boolean(),
360
+ refSchema
361
+ ]);
362
+ const propCondition = z.record(z.string(), z.union([conditionLeaf, z.array(conditionLeaf)])).optional();
363
+ /**
364
+ * The declaration shape over a given condition schema: the generic one with `when` open, and one
365
+ * per owner with `when` narrowed to that component's props. A factory rather than a per-owner
366
+ * transform over the generic shape, because a field-path write (`sub-update` at `value.warning`)
367
+ * derives its schema from the arms' object shapes, which a transform would hide.
368
+ */
369
+ function propShapeWith(when) {
370
+ return z.discriminatedUnion("type", [
371
+ z.strictObject({
372
+ type: z.literal("slot"),
373
+ text: z.boolean().optional(),
374
+ accepts: z.union([refSchema, z.array(refSchema)]).optional(),
375
+ required: z.boolean().optional(),
376
+ when
377
+ }),
378
+ z.strictObject({
379
+ type: z.literal("variant"),
380
+ value: variantDomain,
381
+ required: z.boolean().optional(),
382
+ when
383
+ }),
384
+ z.strictObject({
385
+ type: z.literal("forward"),
386
+ target: z.union([layerPath, refSchema]),
387
+ into: z.union([z.string(), refSchema]).optional(),
388
+ accepts: z.union([refSchema, z.array(refSchema)]).optional(),
389
+ text: z.boolean().optional(),
390
+ required: z.boolean().optional(),
391
+ when
392
+ }).transform(routedTarget),
393
+ z.strictObject({
394
+ type: z.literal("styleProperty"),
395
+ value: refSchema,
396
+ when
397
+ }),
398
+ z.strictObject({
399
+ type: z.literal("composite"),
400
+ value: refSchema,
401
+ when
402
+ }),
403
+ z.strictObject({
404
+ type: z.literal("motion"),
405
+ value: refSchema.default({ __ref: "motion:" }),
406
+ when
407
+ }),
408
+ z.strictObject({
409
+ type: z.literal("string"),
410
+ value: z.string().optional(),
411
+ required: z.boolean().optional(),
412
+ when
413
+ }),
414
+ z.strictObject({
415
+ type: z.literal("number"),
416
+ value: z.number().optional(),
417
+ required: z.boolean().optional(),
418
+ when
419
+ }),
420
+ z.strictObject({
421
+ type: z.literal("boolean"),
422
+ value: z.boolean().optional(),
423
+ required: z.boolean().optional(),
424
+ when
425
+ })
426
+ ], { error: (issue) => {
427
+ const type = issue.input?.type;
428
+ if (typeof type !== "string") return void 0;
429
+ const types = PROP_TYPES.join(", ");
430
+ if (/^style-?property$/i.test(type)) return `\`${type}\` is not a prop type; a style property is exposed as \`{ "type": "styleProperty", "value": { "__ref": "style-property:<name>" } }\`. The types are ${types}.`;
431
+ return `\`${type}\` is not a prop type. The types are ${types}; a style property is \`{ "type": "styleProperty", "value": { "__ref": "style-property:<name>" } }\`, content is \`{ "type": "slot", "text": true }\`.`;
432
+ } });
433
+ }
434
+ const propShape = propShapeWith(propCondition);
404
435
  /** The prop types, for the refusal that names them. */
405
436
  const PROP_TYPES = [
406
437
  "slot",
@@ -413,7 +444,7 @@ const PROP_TYPES = [
413
444
  "number",
414
445
  "boolean"
415
446
  ];
416
- const PROP_DESCRIPTION = "What the prop is. A style property the component exposes, `{ \"type\": \"styleProperty\", \"value\": { \"__ref\": \"style-property:<name>\" } }`, which is what a primitive's props mostly are and what makes a style rule's bag key valid on it; a closed set of names a style rule keys on, `{ \"type\": \"variant\", \"value\": [\"sm\", \"lg\"] }`; content, `{ \"type\": \"slot\", \"text\": true }`; or a plain `string`, `number` or `boolean`. `null` hides a prop an inherited layer would expose.";
447
+ const PROP_DESCRIPTION = "What the prop is. A style property the component exposes, `{ \"type\": \"styleProperty\", \"value\": { \"__ref\": \"style-property:<name>\" } }`, which is what a primitive's props mostly are and what makes a style rule's bag key valid on it; a closed set of names a style rule keys on, `{ \"type\": \"variant\", \"value\": [\"sm\", \"lg\"] }`; content, `{ \"type\": \"slot\", \"text\": true }`; or a plain `string`, `number` or `boolean`. `null` hides a prop an inherited layer would expose. A prop that applies only under a condition on a sibling carries `when`, the record a style rule uses: `{ \"type\": \"string\", \"when\": { \"type\": \"color\" } }` shows it for one value, `{ \"when\": { \"type\": [\"default\", \"header\"] } }` for several.";
417
448
  /** Nullable because `null` is the omit tombstone: a component that `inherits` its element's props
418
449
  * drops one by declaring it `null`, rather than needing a second "don't expose" mechanism. */
419
450
  const Prop = defineSubEntity({
@@ -439,14 +470,15 @@ const Prop = defineSubEntity({
439
470
  });
440
471
  },
441
472
  namespaces: "value",
473
+ namesMembersOf: { when: "props" },
442
474
  subEntities: { value: VariantValue },
443
- keyedByNameIn: ["defaultProps", "previewProps"],
444
- orphans: ["styles"]
475
+ keyedByNameIn: ["defaultProps", "preview/*/props"],
476
+ orphans: ["styles", "preview"]
445
477
  });
446
478
  /** {@link Prop}'s per-owner schema. Reached only through the memo in its `fields`. */
447
479
  function propSchemaFor(config, owner) {
448
480
  const layers = owner.layers;
449
- return propShape.nullable().check((ctx) => {
481
+ return propShapeWith(ownerCondition(config, owner)).nullable().check((ctx) => {
450
482
  const decl = ctx.value;
451
483
  if (decl === null || !isPlainObject(decl) || decl.type !== "forward" || decl.accepts === void 0 || routesContent(config, layers, decl)) return;
452
484
  const layer = isRef(decl.target) ? pathOf(decl.target) : String(decl.target ?? "?");
@@ -460,13 +492,30 @@ function propSchemaFor(config, owner) {
460
492
  });
461
493
  });
462
494
  }
495
+ /**
496
+ * A condition narrowed to one owner's props: each key a sibling, each value one of that sibling's
497
+ * values or a list of them. Values are stored as refs the way a style rule's condition is
498
+ * (`conditionValue`), so a value rename cascades into the condition and a delete finds it.
499
+ */
500
+ function ownerCondition(config, owner) {
501
+ const shape = {};
502
+ for (const [name, decl] of Object.entries(owner.props ?? {})) {
503
+ if (decl === null || decl === void 0) continue;
504
+ const value = conditionValue(name, decl, config);
505
+ shape[name] = z.union([value, z.array(value).min(1)]).optional();
506
+ }
507
+ return z.strictObject(shape, { error: (issue) => {
508
+ const { code, keys } = issue;
509
+ if (code !== "unrecognized_keys" || !keys) return void 0;
510
+ return `\`when\` names ${keys.map((key) => `\`${key}\``).join(", ")}, which is not a prop of this component. A condition names a sibling prop and one of its values, \`{ "when": { "type": "color" } }\`, or a list of them.`;
511
+ } }).optional();
512
+ }
463
513
  const propSchemas = /* @__PURE__ */ new WeakMap();
464
514
  /** A rule's identity is its condition, as a canonical query string (`*`, `size=xs`,
465
515
  * `size=xs&variant=tertiary`) — sorted, so the same condition always yields the same key however
466
516
  * the editor collected it. */
467
517
  function canonicalWhen(when) {
468
- const entries = Object.entries(when ?? {}).filter(([, v]) => v !== void 0 && v !== null).sort(([a], [b]) => a.localeCompare(b));
469
- return entries.length === 0 ? "*" : entries.map(([k, v]) => `${k}=${String(refLeaf(v))}`).join("&");
518
+ return canonicalCondition(when);
470
519
  }
471
520
  /**
472
521
  * What a rule may set one prop to: the prop's own value domain, plus — for a prop that writes CSS —
@@ -752,11 +801,14 @@ function composedCreateExample(config, path) {
752
801
  when: null,
753
802
  layers: styleLayers
754
803
  } } } : {},
755
- previewProps: {
756
- icon: true,
757
- label: "Example",
758
- children: "Body text"
759
- }
804
+ preview: { ["*"]: {
805
+ when: null,
806
+ props: {
807
+ icon: true,
808
+ label: "Example",
809
+ children: "Body text"
810
+ }
811
+ } }
760
812
  }
761
813
  };
762
814
  }
@@ -859,6 +911,56 @@ function perOwner({ cache, config, owner, build }) {
859
911
  return schema;
860
912
  }
861
913
  const ruleSchemas = /* @__PURE__ */ new WeakMap();
914
+ const PREVIEW_DESCRIPTION = "What the catalogue tile and the matrix render with, keyed by condition the way `styles` is. The `*` entry, `{ \"when\": null, \"props\": { ... } }`, is what every cell starts from; an entry with a condition, `{ \"when\": { \"type\": \"image\" }, \"props\": { \"src\": \"…\" } }`, adds its props to the cells whose values match, in the grammar a style rule uses. A slot prop previews with a stored element, `{ \"element\": { \"__ref\": \"component:<Name>\" } }`. A condition on a prop no rule styles makes that prop a preview axis with the value named. A component whose root takes children renders empty until an entry or `defaultProps` carries some.";
915
+ const previewShapeError = "a preview entry is `{ \"when\": null | { \"<prop>\": \"<value>\" }, \"props\": { \"<prop>\": <value> } }`: `when` names this component's own props at one of their values (`null` for the entry every cell reads) and `props` the props the matching cells preview with.";
916
+ const previewBase = z.object({
917
+ when: z.record(z.string(), z.unknown()).nullable().default(null),
918
+ props: z.record(z.string(), z.unknown())
919
+ });
920
+ const previewSchemas = /* @__PURE__ */ new WeakMap();
921
+ /** Whether a preview entry previews nothing: its `props` are empty. */
922
+ function previewShowsNothing(entry) {
923
+ const props = entry?.props;
924
+ return !props || Object.keys(props).length === 0;
925
+ }
926
+ /**
927
+ * The props a cell previews with, keyed by the condition they hold under the way a style rule is:
928
+ * `*` for every cell, a condition on the component's own props for some. `when` names those props,
929
+ * so a prop rename moves it and a value rename moves the ref it stores; `props` is keyed by prop
930
+ * too, which `Prop.keyedByNameIn` and `VariantValue.namedValuesIn` reach through `preview/*\/props`.
931
+ * Deleting a prop or a value drops the entries conditioned on it.
932
+ */
933
+ const Preview = defineSubEntity({
934
+ name: "preview",
935
+ label: "Preview",
936
+ labelPlural: "Previews",
937
+ key: (entry) => canonicalWhen(entry?.when),
938
+ vacant: previewShowsNothing,
939
+ namesMembersOf: { when: "props" },
940
+ base: previewBase,
941
+ fields: ({ config, parent }) => {
942
+ const body = parent;
943
+ if (!config || !body) return previewBase;
944
+ return perOwner({
945
+ cache: previewSchemas,
946
+ config,
947
+ owner: body,
948
+ build: () => {
949
+ const when = {};
950
+ for (const [name, decl] of Object.entries(body.props ?? {})) when[name] = conditionValue(name, decl, config).optional();
951
+ return z.strictObject({
952
+ when: z.strictObject(when, { error: previewShapeError }).nullable().default(null),
953
+ props: z.record(z.string(), z.unknown())
954
+ }, { error: previewShapeError });
955
+ }
956
+ });
957
+ }
958
+ });
959
+ /** Why a slot refuses an element, for a write the config turns away. */
960
+ function slotRefusal({ slot, element }) {
961
+ const named = isRef(element) ? element.__ref : String(element);
962
+ return `\`${slot.from}\` takes ${slot.accepts.join(", ")}; \`${named}\` is outside it.`;
963
+ }
862
964
  /** What one node renders. The order is the resolution order the build reads a tag by: one of this
863
965
  * Component's layers, then something the config names, then a package export — plus `$state` for an
864
966
  * element the instance chooses (`GLYPHS[name]`), whose legal values are the prop's own domain
@@ -906,7 +1008,7 @@ const stateCondition = z.strictObject({
906
1008
  ...test,
907
1009
  $state: test.$state.startsWith("/") ? test.$state : `/${test.$state}`
908
1010
  }));
909
- const orCondition = z.strictObject({ $or: z.array(stateCondition) });
1011
+ const orCondition = z.strictObject({ $or: z.array(z.union([stateCondition, z.array(stateCondition)])) });
910
1012
  /**
911
1013
  * The flat condition an editor collects — `{ variant: 'solid' }` — compiled to the grammar above.
912
1014
  *
@@ -948,8 +1050,9 @@ const visibility = z.union([
948
1050
  stateCondition,
949
1051
  orCondition,
950
1052
  z.array(z.union([stateCondition, orCondition])),
1053
+ z.literal(false),
951
1054
  flatCondition
952
- ], { error: "a condition is `{ \"$state\": \"<prop>\" }` for a prop that is set, `{ \"$state\": \"<prop>\", \"eq\": \"<value>\" }` for one value of it (`neq`, `gt`, `gte`, `lt`, `lte` and `not: true` also work), `{ \"$or\": [ … ] }` for any of several, or a list of conditions that must all hold" });
1055
+ ], { error: "a condition is `{ \"$state\": \"<prop>\" }` for a prop that is set, `{ \"$state\": \"<prop>\", \"eq\": \"<value>\" }` for one value of it (`neq`, `gt`, `gte`, `lt`, `lte` and `not: true` also work), `{ \"$or\": [ … ] }` for any of several, a list of conditions that must all hold, or `false` for a position only an interaction shows" });
953
1056
  /** What an event on a node does to the state model: json-render's own `setState`, its path and the
954
1057
  * value to write, which may itself be a choice on the state (`{ $cond, $then, $else }`). */
955
1058
  const stateAction = z.strictObject({
@@ -1005,7 +1108,7 @@ function nodeComponent(body, element) {
1005
1108
  * Owned by the op rather than by each fill surface, so one gesture stays one patch per node and
1006
1109
  * every caller agrees the fill will actually paint.
1007
1110
  */
1008
- function slotFillHostWrites({ config, path, parent, key, slotProp, node, layers }) {
1111
+ function slotFillHostWrites({ config, path, parent, key, slotProp, node, layers, element }) {
1009
1112
  const host = nodeComponent({ layers }, node?.element ?? layers?.[parent]);
1010
1113
  if (host && config.resolve("component", host)) {
1011
1114
  const declared = resolveComponentProps(config, host);
@@ -1020,6 +1123,16 @@ function slotFillHostWrites({ config, path, parent, key, slotProp, node, layers
1020
1123
  const parentLayer = isRef(parentElement) && kindOf(parentElement.__ref) === "layer" ? pathOf(parentElement.__ref) : parent;
1021
1124
  const route = slotTargetsOf(config, path).find((t) => t.layer === parentLayer && t.into === slotProp);
1022
1125
  if (!route) return { slots };
1126
+ const filled = nodeTypeOf(element, layers);
1127
+ const slot = filled !== void 0 ? slotOf({
1128
+ config,
1129
+ path,
1130
+ slot: route.from
1131
+ }) : void 0;
1132
+ if (slot && filled !== void 0 && !slot.takes(filled)) rejectData(slotRefusal({
1133
+ slot,
1134
+ element: filled
1135
+ }), ["slots", slotProp]);
1023
1136
  return {
1024
1137
  slots,
1025
1138
  visible: withoutTruthyTerm({
@@ -1028,6 +1141,14 @@ function slotFillHostWrites({ config, path, parent, key, slotProp, node, layers
1028
1141
  })
1029
1142
  };
1030
1143
  }
1144
+ /** What a node renders, as a type `Slot.takes` reads: a layer ref through the layer's element. */
1145
+ function nodeTypeOf(element, layers) {
1146
+ if (isRef(element) && kindOf(element.__ref) === "layer") {
1147
+ const layer = layers?.[pathOf(element.__ref)];
1148
+ return typeof layer === "string" || isRef(layer) ? layer : void 0;
1149
+ }
1150
+ if (typeof element === "string" || isRef(element)) return element;
1151
+ }
1031
1152
  /**
1032
1153
  * The `slots` entries of `node` naming any of `doomed` — each answered with what survives, so a
1033
1154
  * caller writes the filtered list back at the entry's own address (or `null` when nothing does)
@@ -1135,13 +1256,16 @@ const AnatomyNode = defineSubEntity({
1135
1256
  path: ["text"],
1136
1257
  message: `\`text\` is a fixed string, and "${node.text}" is one of this component's props. To show the prop's value here, declare it \`{ "type": "forward", "target": "${isRef(element) && kindOf(element.__ref) === "layer" ? pathOf(element.__ref) : "<layer>"}/children" }\` and give this node no \`text\`.`
1137
1258
  });
1259
+ let placed = element;
1260
+ if (isRef(placed) && kindOf(placed.__ref) === "layer") placed = body.layers?.[pathOf(placed.__ref)];
1261
+ const instanceProps = isRef(placed) && kindOf(placed.__ref) === "component" ? resolveComponentProps(config, pathOf(placed.__ref)) : void 0;
1138
1262
  for (const [prop, value] of Object.entries(node.props ?? {})) {
1139
1263
  if (isRef(value) && [
1140
1264
  "icon",
1141
1265
  "component",
1142
1266
  "package",
1143
1267
  "layer"
1144
- ].includes(kindOf(value.__ref))) {
1268
+ ].includes(kindOf(value.__ref)) && !(instanceProps && prop in instanceProps)) {
1145
1269
  ctx.issues.push({
1146
1270
  code: "custom",
1147
1271
  input: value,
@@ -1256,18 +1380,16 @@ const componentFields = z.strictObject({
1256
1380
  */
1257
1381
  forwards: Forward.optional(),
1258
1382
  styles: StyleRule.optional(),
1383
+ /** The props the render reads that the component neither declares nor forwards — its render
1384
+ * extras (`.render<{ swatchColor?: string }>()`). Read off the render at build like `anatomy`,
1385
+ * never authored as data. The module consumes them; a component painted out of its anatomy has
1386
+ * no module, so the expansion keeps them off the root rather than wearing them as attributes. */
1387
+ renderProps: z.array(z.string()).optional().describe("Props the render reads beyond those declared, read off the source at build. Not authored: declare a prop in `props` instead."),
1259
1388
  anatomy: AnatomyNode.optional().describe("The tree of what this component renders, keyed by node. Each node names what it renders (a layer, for anything a style rule reaches) and lists its `children`; the node keyed `root` is the top. A one-layer component may omit it."),
1260
1389
  defaultProps: Style.optional(),
1261
- /** The state a person chose to show one catalogue tile in. Not anatomy: the capture bakes these
1262
- * into the tree by invoking a render with them, so a tile's example icon ends up stored as
1263
- * part of the component itself. */
1264
- previewProps: z.record(z.string(), z.unknown()).optional().describe("The props the catalogue tile and the variant matrix render with. A component whose root takes children renders empty until this or `defaultProps` carries some."),
1265
- /** Preview props that hold under a condition on the cell — see `preview({ when })`. Like
1266
- * `previewProps`, read off `.preview()` at build and never authored as data. */
1267
- previewWhen: z.array(z.object({
1268
- when: z.record(z.string(), z.unknown()),
1269
- props: z.record(z.string(), z.unknown())
1270
- })).optional().describe("Preview props that hold only when the cell matches `when`, in the grammar a style rule uses: `{ \"when\": { \"type\": \"image\" }, \"props\": { \"src\": \"…\" } }`. A condition on a prop no rule styles makes that prop a preview axis with the value named."),
1390
+ /** What the tile and the matrix render with — see `preview({ defaultProps, when })`. Read off
1391
+ * `.preview()` at build, and written per cell from the matrix. */
1392
+ preview: Preview.optional().describe(PREVIEW_DESCRIPTION),
1271
1393
  /**
1272
1394
  * The source that implements this component — a ref, because the file is an entity the seal owns.
1273
1395
  *
@@ -1289,17 +1411,43 @@ const componentFields = z.strictObject({
1289
1411
  export: z.string().optional()
1290
1412
  }).check((ctx) => {
1291
1413
  const body = ctx.value;
1414
+ if (isPlainObject(body.props)) for (const [name, decl] of Object.entries(body.props)) {
1415
+ const when = decl?.when;
1416
+ if (!isPlainObject(when) || !(name in when)) continue;
1417
+ ctx.issues.push({
1418
+ code: "custom",
1419
+ input: decl,
1420
+ path: [
1421
+ "props",
1422
+ name,
1423
+ "when",
1424
+ name
1425
+ ],
1426
+ message: `\`${name}\` conditions on itself. \`when\` names a sibling prop and one of its values.`
1427
+ });
1428
+ }
1292
1429
  if (body.forwards || body.file || isRef(body.props)) return;
1293
1430
  const declared = body.props ?? {};
1294
- for (const field of ["previewProps", "defaultProps"]) {
1295
- const values = body[field];
1431
+ if (Object.hasOwn(declared, "children") && declared.children !== null) return;
1432
+ const content = [{
1433
+ values: body.defaultProps,
1434
+ path: ["defaultProps", "children"]
1435
+ }, ...Object.entries(storedPreview(body)).map(([key, entry]) => ({
1436
+ values: entry?.props,
1437
+ path: [
1438
+ "preview",
1439
+ key,
1440
+ "props",
1441
+ "children"
1442
+ ]
1443
+ }))];
1444
+ for (const { values, path } of content) {
1296
1445
  if (!isPlainObject(values) || !("children" in values)) continue;
1297
- if (Object.hasOwn(declared, "children") && declared.children !== null) continue;
1298
1446
  ctx.issues.push({
1299
1447
  code: "custom",
1300
1448
  input: values,
1301
- path: [field, "children"],
1302
- message: `\`${field}.children\` has nowhere to land — this component declares no \`children\` prop, so the content is dropped and the component renders empty. Declare \`"children": { "type": "slot", "text": true }\` in \`props\` — or, to land it in a nested layer, \`"children": { "type": "forward", "target": "<layer>/children" }\`.`
1449
+ path,
1450
+ message: `\`${path.join(".")}\` has nowhere to land — this component declares no \`children\` prop, so the content is dropped and the component renders empty. Declare \`"children": { "type": "slot", "text": true }\` in \`props\` — or, to land it in a nested layer, \`"children": { "type": "forward", "target": "<layer>/children" }\`.`
1303
1451
  });
1304
1452
  }
1305
1453
  });
@@ -1358,6 +1506,7 @@ const ComponentEntity = defineEntity({
1358
1506
  props: Prop,
1359
1507
  forwards: Forward,
1360
1508
  styles: StyleRule,
1509
+ preview: Preview,
1361
1510
  anatomy: AnatomyNode,
1362
1511
  defaultProps: Style
1363
1512
  },
@@ -1385,7 +1534,7 @@ slots: ({ config, path, member }) => {
1385
1534
  schemas: {
1386
1535
  create: {
1387
1536
  data: componentFields,
1388
- description: "Create a component at a new qualified `path`. For a component with nothing in it yet, one to design on the canvas before it has props or variants, use `create-blank` with only a `path` instead of authoring a minimal body here. When its complete body is known, include all layers, anatomy, props, defaults, and styles in this one operation. `data.layers` maps each stable layer name to the element it renders and must include `root` — a tag for a primitive (`{ \"layers\": { \"root\": \"div\" } }`), and for anything built on top, a ref to a primitive (`{ \"root\": { \"__ref\": \"component:Box\" }, \"label\": { \"__ref\": \"component:Text\" } }`), which is what lets the layer take that primitive's style props. A primitive exposes style props as `{ \"type\": \"styleProperty\", \"value\": { \"__ref\": \"style-property:bg\" } }` and declares `children: { \"type\": \"slot\", \"text\": true }` when it holds content — without the slot, nothing nested inside it renders. A composed component exposes a layer's props at its own top level through `forwards`, keyed by layer: `{ \"forwards\": { \"root\": \"*\" } }` passes every prop the root's element takes (so `<Button width=\"full\">` reaches the root's `width`), and a list of member refs (`[{ \"__ref\": \"component:Box#props/width\" }]`, each naming the component that declares the prop) passes only those. A prop a layer already exposes is forwarded, never redeclared in `props`; a `props` entry of `{ \"type\": \"forward\" }` is the reverse route, one of this component's props into a layer's. `anatomy` is the tree the build would read off a render: each node renders a layer (`{ \"__ref\": \"layer:<name>\" }`), an intrinsic tag, or `Slot` (`{ \"element\": \"Slot\", \"props\": { \"name\": \"children\" } }`, where a slot prop's content lands — but a declared slot fills `root` by default, so a primitive still declares its `children` slot prop yet needs no `Slot` node or `children` entry for it, and a `Slot` node is only for landing that content in a nested layer); `children` lists sibling node keys from the `root` node, never a prop name; `text` is a fixed string; `visible` is a condition on a prop (`{ \"$state\": \"<prop>\" }`, or with `eq`); content shown in a nested layer is a route, `children: { \"type\": \"forward\", \"target\": \"label/children\" }`. `$state` appears only in `visible` and in an `element` a prop chooses, never as a prop value. A glyph is a node of its own, `{ \"element\": { \"__ref\": \"icon:<library>/<Name>\" } }`, listed in the `children` of the layer node that holds it, never a prop value. Text a prop supplies to a text layer is never a node either: declare the prop `{ \"type\": \"forward\", \"target\": \"<layer>/children\" }` and the layer shows it; a `Slot` node goes only under a layer that takes nodes. A style rule is `\"styles\": { \"*\": { \"when\": null, \"layers\": { \"root\": { \"bg\": \"surface\" } } } }`, keyed by a name, with each layer's bag under `layers`. A composed component carries its own styles: give it an unconditional (`\"*\"`) rule that lays out and surfaces it through the system's style properties — spacing, alignment and gap on the container, a background and color where it has one — rather than leaning on the bare primitives, which paint flat. Style a layer only with the style props the primitive it renders exposes, set to the values those props accept: `component/get` on the primitive lists both, so read it and choose from that rather than reaching for a CSS name or value the system has no style property for. Set `previewProps` (or `defaultProps`) so the component previews with content; a root that takes children renders empty otherwise. A style rule's layer bag is keyed by style-prop name (`fontWeight`, `color`), never by CSS property name, and each value is one of that prop's values.",
1537
+ description: "Create a component at a new qualified `path`. For a component with nothing in it yet, one to design on the canvas before it has props or variants, use `create-blank` with only a `path` instead of authoring a minimal body here. When its complete body is known, include all layers, anatomy, props, defaults, and styles in this one operation. `data.layers` maps each stable layer name to the element it renders and must include `root` — a tag for a primitive (`{ \"layers\": { \"root\": \"div\" } }`), and for anything built on top, a ref to a primitive (`{ \"root\": { \"__ref\": \"component:Box\" }, \"label\": { \"__ref\": \"component:Text\" } }`), which is what lets the layer take that primitive's style props. A primitive exposes style props as `{ \"type\": \"styleProperty\", \"value\": { \"__ref\": \"style-property:bg\" } }` and declares `children: { \"type\": \"slot\", \"text\": true }` when it holds content — without the slot, nothing nested inside it renders. A composed component exposes a layer's props at its own top level through `forwards`, keyed by layer: `{ \"forwards\": { \"root\": \"*\" } }` passes every prop the root's element takes (so `<Button width=\"full\">` reaches the root's `width`), and a list of member refs (`[{ \"__ref\": \"component:Box#props/width\" }]`, each naming the component that declares the prop) passes only those. A prop a layer already exposes is forwarded, never redeclared in `props`; a `props` entry of `{ \"type\": \"forward\" }` is the reverse route, one of this component's props into a layer's. `anatomy` is the tree the build would read off a render: each node renders a layer (`{ \"__ref\": \"layer:<name>\" }`), an intrinsic tag, or `Slot` (`{ \"element\": \"Slot\", \"props\": { \"name\": \"children\" } }`, where a slot prop's content lands — but a declared slot fills `root` by default, so a primitive still declares its `children` slot prop yet needs no `Slot` node or `children` entry for it, and a `Slot` node is only for landing that content in a nested layer); `children` lists sibling node keys from the `root` node, never a prop name; `text` is a fixed string; `visible` is a condition on a prop (`{ \"$state\": \"<prop>\" }`, or with `eq`); content shown in a nested layer is a route, `children: { \"type\": \"forward\", \"target\": \"label/children\" }`. `$state` appears only in `visible` and in an `element` a prop chooses, never as a prop value. A glyph is a node of its own, `{ \"element\": { \"__ref\": \"icon:<library>/<Name>\" } }`, listed in the `children` of the layer node that holds it, never a prop value. Text a prop supplies to a text layer is never a node either: declare the prop `{ \"type\": \"forward\", \"target\": \"<layer>/children\" }` and the layer shows it; a `Slot` node goes only under a layer that takes nodes. A style rule is `\"styles\": { \"*\": { \"when\": null, \"layers\": { \"root\": { \"bg\": \"surface\" } } } }`, keyed by a name, with each layer's bag under `layers`. A composed component carries its own styles: give it an unconditional (`\"*\"`) rule that lays out and surfaces it through the system's style properties — spacing, alignment and gap on the container, a background and color where it has one — rather than leaning on the bare primitives, which paint flat. Style a layer only with the style props the primitive it renders exposes, set to the values those props accept: `component/get` on the primitive lists both, so read it and choose from that rather than reaching for a CSS name or value the system has no style property for. Set `preview` (`{ \"*\": { \"when\": null, \"props\": { \"children\": \"Example\" } } }`) or `defaultProps` so the component previews with content; a root that takes children renders empty otherwise. A style rule's layer bag is keyed by style-prop name (`fontWeight`, `color`), never by CSS property name, and each value is one of that prop's values.",
1389
1538
  example: ({ config }) => {
1390
1539
  const path = freeExamplePath(config);
1391
1540
  const composed = composedCreateExample(config, path);
@@ -1421,7 +1570,10 @@ slots: ({ config, path, member }) => {
1421
1570
  children: ["icon", "content"]
1422
1571
  }
1423
1572
  },
1424
- previewProps: { children: "Example" }
1573
+ preview: { ["*"]: {
1574
+ when: null,
1575
+ props: { children: "Example" }
1576
+ } }
1425
1577
  }
1426
1578
  };
1427
1579
  }
@@ -1625,7 +1777,8 @@ slots: ({ config, path, member }) => {
1625
1777
  key,
1626
1778
  slotProp,
1627
1779
  node: body?.anatomy?.[parent],
1628
- layers: body?.layers
1780
+ layers: body?.layers,
1781
+ element
1629
1782
  }) : void 0;
1630
1783
  return next.apply({
1631
1784
  kind: "component",
@@ -1922,9 +2075,27 @@ function componentRef(path) {
1922
2075
  });
1923
2076
  return value;
1924
2077
  }
1925
- /** Component keeps the generic entity surface; only its ref authoring result knows about the
1926
- * source-only `.render()` step. */
1927
- const Component = Object.assign(ComponentEntity, { ref: componentRef });
2078
+ /**
2079
+ * Component keeps the generic entity surface, plus the questions only a component answers: its
2080
+ * slots (`Slot`, in `spec/slot`), what one of its matrix cells shows (`Cell`, in `spec/cell`) and
2081
+ * what a design file put in a placed instance's slots (`Instance`, in `spec/instance`).
2082
+ * Only its ref authoring result knows about the source-only `.render()` step.
2083
+ */
2084
+ const Component = Object.assign(ComponentEntity, {
2085
+ ref: componentRef,
2086
+ /** Every slot content can be placed in, with what each takes. */
2087
+ slots: listSlots,
2088
+ /** One slot by the prop it arrives as. */
2089
+ slot: slotOf,
2090
+ /** The slot whose content lands in a layer. */
2091
+ slotHostedBy,
2092
+ /** The named node slots alone, without a positioned `children`. */
2093
+ namedSlots,
2094
+ /** The component under one coordinate of its matrix. */
2095
+ cell: cellAt,
2096
+ /** The component a design file's spec element places, with what the file put in its slots. */
2097
+ instance: instanceAt
2098
+ });
1928
2099
  /** A valid example prop bag — the first variant prop's first value, else empty. Lives here because
1929
2100
  * it reads a component's stored props; the generic example filler only knows address fields.
1930
2101
  * Resolved through the domain (not `ownValues`): a variant that delegates its values to a group
@@ -1,7 +1,7 @@
1
1
  import { Ref } from "../../framework/utils/refs.js";
2
2
  import { SubEntityClass } from "../../framework/defineSubEntity.js";
3
- import { EntityClass } from "../../framework/defineEntity.js";
4
3
  import { ComputedInput } from "../../framework/registry.js";
4
+ import { EntityClass } from "../../framework/defineEntity.js";
5
5
  import { z } from "zod";
6
6
 
7
7
  //#region src/entities/system/File.d.ts
@@ -1,11 +1,11 @@
1
1
  import { CopyPlan } from "./copy-plan.js";
2
- import { RegisteredEntities } from "../../framework/registered.js";
3
2
  import { SourceResolver, SourceState } from "../../framework/sources.js";
3
+ import { ComputedInput } from "../../framework/registry.js";
4
+ import { RegisteredEntities } from "../../framework/registered.js";
4
5
  import { EntityClass, ItemOf } from "../../framework/defineEntity.js";
5
6
  import { PartialBodyOf } from "../../framework/defineConfig.js";
6
7
  import { BreakPlan, LinkPlan, SourceRowState, UnlinkPlan } from "./link-plan.js";
7
8
  import { Config } from "../../framework/Config.js";
8
- import { ComputedInput } from "../../framework/registry.js";
9
9
  import { z } from "zod";
10
10
 
11
11
  //#region src/entities/system/LinkedSystem.d.ts
@@ -1,6 +1,6 @@
1
+ import { ComputedInput } from "../../framework/registry.js";
1
2
  import { DerivedEntityClass } from "../../framework/defineDerivedEntity.js";
2
3
  import { Config } from "../../framework/Config.js";
3
- import { ComputedInput } from "../../framework/registry.js";
4
4
  import { z } from "zod";
5
5
 
6
6
  //#region src/entities/system/Operation.d.ts
@@ -25,6 +25,14 @@ declare const STYLE_PROP_VALUE_FORMS: {
25
25
  }?: StylePropValueCtx) => z.ZodObject<{
26
26
  __ref: z.ZodString;
27
27
  }, z.core.$strip>;
28
+ readonly tokenGroupPrefixed: ({
29
+ config
30
+ }?: StylePropValueCtx) => z.ZodObject<{
31
+ value: z.ZodObject<{
32
+ __ref: z.ZodString;
33
+ }, z.core.$strip>;
34
+ prefix: z.ZodString;
35
+ }, z.core.$strict>;
28
36
  readonly flag: (_ctx?: StylePropValueCtx) => z.ZodObject<{
29
37
  alias: z.ZodBoolean;
30
38
  value: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
@@ -71,7 +71,7 @@ const namesItem = (config, wanted) => (ctx) => {
71
71
  * of one string too, so `aliasToken` comes before `aliasDeclarations`; a boolean alias is a flag
72
72
  * whatever its value holds, so `flag` comes before the named aliases. `token` and `tokenGroup` are
73
73
  * one stored shape; with a config each refuses what the other admits, and without one a ref reads
74
- * as `token`.
74
+ * as `token`. A prefixed group is a group ref beside the prefix its leaves carry.
75
75
  */
76
76
  /** A declaration bag's key. A ref is a record of one string too, so the ref marker is not a key here,
77
77
  * or a ref no ref form admits would read as a bag. */
@@ -93,6 +93,13 @@ const STYLE_PROP_VALUE_FORMS = {
93
93
  title: "Token group",
94
94
  description: "A ref to a token group, which offers every token in it and follows the group as it changes."
95
95
  }).check(namesItem(config, false)),
96
+ tokenGroupPrefixed: ({ config } = {}) => z.strictObject({
97
+ value: refSchema.check(namesItem(config, false)),
98
+ prefix: z.string().min(1)
99
+ }).meta({
100
+ title: "Prefixed token group",
101
+ description: "A ref to a token group whose every token is offered under `<prefix>/<name>`, so a group bound beside others reads as its own: `{ value: <ref to canvas>, prefix: \"canvas\" }` offers `canvas/slot`."
102
+ }),
96
103
  flag: (_ctx = {}) => z.strictObject({
97
104
  alias: z.boolean(),
98
105
  value: z.union([
@@ -159,6 +166,10 @@ const STYLE_PROP_VALUE_LEAVES = {
159
166
  leaf,
160
167
  value: path
161
168
  })),
169
+ tokenGroupPrefixed: (entry, members) => members(entry.value).map(({ leaf, path }) => ({
170
+ leaf: `${entry.prefix}/${leaf}`,
171
+ value: path
172
+ })),
162
173
  flag: (entry, members) => entry.alias ? flagValue(entry.value, members).map((rest) => ({
163
174
  leaf: "",
164
175
  ...rest
@@ -1,6 +1,6 @@
1
+ import { ComputedInput } from "../../framework/registry.js";
1
2
  import { DerivedEntityClass } from "../../framework/defineDerivedEntity.js";
2
3
  import { Config } from "../../framework/Config.js";
3
- import { ComputedInput } from "../../framework/registry.js";
4
4
  import { z } from "zod";
5
5
 
6
6
  //#region src/entities/system/Tool.d.ts