@yahoo/uds-create-config 4.27.0 → 4.28.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.
@@ -41,6 +41,7 @@ import { transitionTokenValueMigration } from "../migrations/20260923144958_tran
41
41
  import { renderTokenRefsMigration } from "../migrations/20260923160425_render_token_refs.js";
42
42
  import { globalStyleDerivedColorsMigration } from "../migrations/20260923163604_global_style_derived_colors.js";
43
43
  import { slotCodeFormsMigration } from "../migrations/20260924044539_slot_code_forms.js";
44
+ import { canvasRoleSlotMigration } from "../migrations/20260925143319_canvas_role_slot.js";
44
45
  //#region src/configs/system.ts
45
46
  /**
46
47
  * `System` — the design-system config TYPE. Declares the exact kinds a design system owns (tokens,
@@ -102,5 +103,6 @@ if (!registeredSchemaMigrations().has(20260923144958)) registerSchemaMigrations(
102
103
  if (!registeredSchemaMigrations().has(20260923160425)) registerSchemaMigrations(renderTokenRefsMigration);
103
104
  if (!registeredSchemaMigrations().has(20260923163604)) registerSchemaMigrations(globalStyleDerivedColorsMigration);
104
105
  if (!registeredSchemaMigrations().has(20260924044539)) registerSchemaMigrations(slotCodeFormsMigration);
106
+ if (!registeredSchemaMigrations().has(20260925143319)) registerSchemaMigrations(canvasRoleSlotMigration);
105
107
  //#endregion
106
108
  export { System };
@@ -3,19 +3,14 @@ import { z } from "zod";
3
3
 
4
4
  //#region src/entities/system/CanvasRole.d.ts
5
5
  /**
6
- * The roles a canvas has a tool for — the vocabulary a design system fills in.
6
+ * The roles a canvas has a tool for, the vocabulary a design system fills in. A role a config leaves
7
+ * unregistered has no entries, and its tool is disabled rather than falling back to a guess; what
8
+ * fills a role is whichever component the config points at, never a name the editor chose.
7
9
  *
8
- * This is the set of tools an editor offers, and each one is a SLOT: a role a config leaves
9
- * unregistered has no entries, and its tool is disabled rather than falling back to a guess. So
10
- * naming a role here offers the slot without asserting anything fills it, and what fills it is
11
- * never a name the editor chose — it is whichever component the config pointed at.
12
- *
13
- * The names live here, with the entity, because the entity is what they are names OF. They were
14
- * previously kept in `@yahoo/uds-create-platform-types` as a hand-maintained copy, annotated as
15
- * one ("mirrors `@yahoo/uds-create-config`'s `CanvasRole` union without importing it") — a second
16
- * source of truth wearing a comment admitting it.
10
+ * `slot` is the one role no toolbar draws with. Its entries are the host layers a component's new
11
+ * slot property can start as, and an entry's `defaultProps` are the props that layer starts with.
17
12
  */
18
- declare const CANVAS_ROLES: readonly ["frame", "text", "image", "icon"];
13
+ declare const CANVAS_ROLES: readonly ["frame", "text", "image", "icon", "slot"];
19
14
  type CanvasRoleName = (typeof CANVAS_ROLES)[number];
20
15
  declare const CanvasRole: EntityClass<z.ZodObject<{
21
16
  entries: z.ZodArray<z.ZodObject<{
@@ -15,23 +15,19 @@ import { z } from "zod";
15
15
  * });
16
16
  */
17
17
  /**
18
- * The roles a canvas has a tool for — the vocabulary a design system fills in.
18
+ * The roles a canvas has a tool for, the vocabulary a design system fills in. A role a config leaves
19
+ * unregistered has no entries, and its tool is disabled rather than falling back to a guess; what
20
+ * fills a role is whichever component the config points at, never a name the editor chose.
19
21
  *
20
- * This is the set of tools an editor offers, and each one is a SLOT: a role a config leaves
21
- * unregistered has no entries, and its tool is disabled rather than falling back to a guess. So
22
- * naming a role here offers the slot without asserting anything fills it, and what fills it is
23
- * never a name the editor chose — it is whichever component the config pointed at.
24
- *
25
- * The names live here, with the entity, because the entity is what they are names OF. They were
26
- * previously kept in `@yahoo/uds-create-platform-types` as a hand-maintained copy, annotated as
27
- * one ("mirrors `@yahoo/uds-create-config`'s `CanvasRole` union without importing it") — a second
28
- * source of truth wearing a comment admitting it.
22
+ * `slot` is the one role no toolbar draws with. Its entries are the host layers a component's new
23
+ * slot property can start as, and an entry's `defaultProps` are the props that layer starts with.
29
24
  */
30
25
  const CANVAS_ROLES = [
31
26
  "frame",
32
27
  "text",
33
28
  "image",
34
- "icon"
29
+ "icon",
30
+ "slot"
35
31
  ];
36
32
  const CanvasRole = defineEntity({
37
33
  kind: "canvas-role",
@@ -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 = 20260924044539;
75
+ declare const SERIALIZED_CONFIG_VERSION = 20260925143319;
76
76
  interface SerializedConfig {
77
77
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
78
78
  readonly version: number;
@@ -4883,7 +4883,7 @@ var Config = class Config {
4883
4883
  * links from the provided sources. A declared `linkedKind` with no provided source throws. */
4884
4884
  hydrateFrom(stored, options) {
4885
4885
  const claimed = schemaVersionOf(stored);
4886
- if (claimed > 20260924044539) throw new SchemaVersionTooNew(claimed);
4886
+ if (claimed > 20260925143319) throw new SchemaVersionTooNew(claimed);
4887
4887
  const wire = upgradeSerializedConfig(stored);
4888
4888
  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\`.`);
4889
4889
  const leftover = wire.options;
@@ -15,7 +15,7 @@ import { Patch, SerializedConfig } from "./Config.js";
15
15
  * sets this constant to its own older timestamp; the merge ref then carries a registered migration
16
16
  * newer than the constant, and the assertion fails until the author re-mints.
17
17
  */
18
- declare const CURRENT_SCHEMA_VERSION = 20260924044539;
18
+ declare const CURRENT_SCHEMA_VERSION = 20260925143319;
19
19
  /** What the walkers need to see of an envelope — loose on purpose, so a typed `SerializedConfig`
20
20
  * and a raw old-shape record both flow in without casts. */
21
21
  interface VersionStamped {
@@ -13,7 +13,7 @@ import { ConfigFormatError } from "./rejection.js";
13
13
  * sets this constant to its own older timestamp; the merge ref then carries a registered migration
14
14
  * newer than the constant, and the assertion fails until the author re-mints.
15
15
  */
16
- const CURRENT_SCHEMA_VERSION = 20260924044539;
16
+ const CURRENT_SCHEMA_VERSION = 20260925143319;
17
17
  /** The mint that moved `options` into the `settings` kind — restated here, rather than imported
18
18
  * from the migration that owns it, because that migration imports this module. */
19
19
  const SETTINGS_AS_KIND_VERSION = 20260908171448;
@@ -45,7 +45,7 @@ function registerSchemaMigrations(...migrations) {
45
45
  for (const migration of migrations) {
46
46
  if (migration.version !== 1 && !isMintTimestamp(migration.version)) throw new Error(`Schema migration version ${migration.version} is not a mint timestamp — mint one with \`date +%Y%m%d%H%M%S\`, like a supabase migration.`);
47
47
  if (REGISTERED.has(migration.version)) throw new Error(`A schema migration at version ${migration.version} is already registered.`);
48
- if (migration.version > 20260924044539) throw new Error(`Schema migration ${migration.version} is newer than CURRENT_SCHEMA_VERSION (${CURRENT_SCHEMA_VERSION}) — a stale branch merged, or the mint forgot to bump the constant. Re-mint: set CURRENT_SCHEMA_VERSION to the newest migration version.`);
48
+ if (migration.version > 20260925143319) throw new Error(`Schema migration ${migration.version} is newer than CURRENT_SCHEMA_VERSION (${CURRENT_SCHEMA_VERSION}) — a stale branch merged, or the mint forgot to bump the constant. Re-mint: set CURRENT_SCHEMA_VERSION to the newest migration version.`);
49
49
  REGISTERED.set(migration.version, migration);
50
50
  }
51
51
  }
@@ -102,7 +102,7 @@ function pendingFor(version, target, options) {
102
102
  * Already-current input passes through untouched — same reference, so hot paths pay nothing.
103
103
  */
104
104
  function upgradeSerializedConfig(json, options) {
105
- const target = options?.target ?? 20260924044539;
105
+ const target = options?.target ?? 20260925143319;
106
106
  const version = detectedWireVersion(json);
107
107
  if (version > target) throw new SchemaVersionTooNew(version, target);
108
108
  const raw = json;
@@ -160,7 +160,7 @@ function upgradedSources(json, options) {
160
160
  * meaning under the current schema and replay skips it — reported by the caller, not swallowed here.
161
161
  */
162
162
  function upgradePatch(patch, writtenAt, options) {
163
- const target = options?.target ?? 20260924044539;
163
+ const target = options?.target ?? 20260925143319;
164
164
  if (writtenAt > target) throw new SchemaVersionTooNew(writtenAt, target);
165
165
  let current = patch;
166
166
  for (const migration of pendingFor(writtenAt, target, options)) {
@@ -179,7 +179,7 @@ function upgradePatch(patch, writtenAt, options) {
179
179
  * stamp would cause.
180
180
  */
181
181
  function upgradeDraftEntries(draft, options) {
182
- const target = options?.target ?? 20260924044539;
182
+ const target = options?.target ?? 20260925143319;
183
183
  const entries = [];
184
184
  const held = [];
185
185
  const retired = [];
@@ -0,0 +1,12 @@
1
+ /**
2
+ * An additive fence: `slot` joins the canvas roles, naming the host layer a new slot property starts
3
+ * as. No stored byte moves.
4
+ *
5
+ * Identity, so a stamp below this version means "may lack these fields" and one at it means they
6
+ * are understood, which is what lets the pull gate keep an older client from stripping them.
7
+ *
8
+ * Deletable once no stored config artifact is below this version.
9
+ */
10
+ import type { SchemaMigration } from '../framework/schema-version';
11
+ export declare const CANVAS_ROLE_SLOT_VERSION = 20260925143319;
12
+ export declare const canvasRoleSlotMigration: SchemaMigration;
@@ -0,0 +1,8 @@
1
+ //#region src/migrations/20260925143319_canvas_role_slot.ts
2
+ const CANVAS_ROLE_SLOT_VERSION = 20260925143319;
3
+ const canvasRoleSlotMigration = {
4
+ version: CANVAS_ROLE_SLOT_VERSION,
5
+ up: (json) => json
6
+ };
7
+ //#endregion
8
+ export { CANVAS_ROLE_SLOT_VERSION, canvasRoleSlotMigration };
@@ -38,7 +38,8 @@ declare class Instance {
38
38
  get slots(): readonly InstanceSlot[];
39
39
  /** The slot a prop names, or `undefined` when the prop is no slot. */
40
40
  slot(prop: string): InstanceSlot | undefined;
41
- /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none. */
41
+ /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none; a slot on
42
+ * the root is hosted by no layer, as {@link slotHostedBy} says. */
42
43
  slotHostedBy(layer: string): InstanceSlot | undefined;
43
44
  /** The slot holding the element `childKey`, or `undefined` when it holds none of this instance's. */
44
45
  slotHolding(childKey: string): InstanceSlot | undefined;
@@ -58,8 +59,8 @@ declare function instanceAt({
58
59
  }): Instance | undefined;
59
60
  /**
60
61
  * The instance and slot holding the element `childKey`, or `undefined` when no instance in the spec
61
- * holds it in a slot. A container's children are its children, not a slot's content: a component
62
- * that takes children on its root lists no `children` slot, so its children resolve to nothing here.
62
+ * holds it in a slot. A component that takes children on its root holds them in its `children`
63
+ * slot, whose `onRoot` says the slot is no level of its own.
63
64
  */
64
65
  declare function instanceHolding({
65
66
  config,
@@ -37,9 +37,10 @@ var Instance = class {
37
37
  slot(prop) {
38
38
  return this.slots.find((entry) => entry.slot.from === prop);
39
39
  }
40
- /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none. */
40
+ /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none; a slot on
41
+ * the root is hosted by no layer, as {@link slotHostedBy} says. */
41
42
  slotHostedBy(layer) {
42
- return this.slots.find((entry) => entry.slot.layer === layer);
43
+ return this.slots.find((entry) => entry.slot.layer === layer && !entry.slot.onRoot);
43
44
  }
44
45
  /** The slot holding the element `childKey`, or `undefined` when it holds none of this instance's. */
45
46
  slotHolding(childKey) {
@@ -58,8 +59,8 @@ function instanceAt({ config, spec, elementKey }) {
58
59
  }
59
60
  /**
60
61
  * The instance and slot holding the element `childKey`, or `undefined` when no instance in the spec
61
- * holds it in a slot. A container's children are its children, not a slot's content: a component
62
- * that takes children on its root lists no `children` slot, so its children resolve to nothing here.
62
+ * holds it in a slot. A component that takes children on its root holds them in its `children`
63
+ * slot, whose `onRoot` says the slot is no level of its own.
63
64
  */
64
65
  function instanceHolding({ config, spec, childKey }) {
65
66
  if (!spec) return void 0;
@@ -26,8 +26,11 @@ declare class Slot implements SlotTarget {
26
26
  readonly accepts: readonly string[];
27
27
  /** Built by {@link listSlots}; a surface reaches one through `Component.slots` or `Component.slot`. */
28
28
  constructor(config: Config, component: string, facts: SlotFacts);
29
- /** Whether this is the component's `children`, positioned inside one of its layers. */
29
+ /** Whether this is the component's `children`. */
30
30
  get isChildren(): boolean;
31
+ /** Whether the content lands on the component's root layer, so an instance holds it directly
32
+ * rather than in a layer of its own. */
33
+ get onRoot(): boolean;
31
34
  /**
32
35
  * Whether the slot takes an element: anything when it accepts nothing in particular, else an
33
36
  * element whose ref one of the accepted refs names, exactly or as a member of a group the ref
@@ -162,10 +165,9 @@ declare class Slot implements SlotTarget {
162
165
  private writeSource;
163
166
  }
164
167
  /**
165
- * Every slot of a component that content can be placed in: its named node slots, and its `children`
166
- * when a layer of several positions them. A text slot is typed in place rather than filled, so it is
167
- * not one; nor is `children` landing on the root, which is the open hole every container has. The
168
- * same array comes back for the same config.
168
+ * Every slot of a component that content can be placed in, `children` included wherever it lands.
169
+ * A text slot is typed in place rather than filled, so it is not one. The same array comes back for
170
+ * the same config.
169
171
  */
170
172
  declare function listSlots({
171
173
  config,
@@ -192,7 +194,8 @@ declare function slotOf({
192
194
  path: string;
193
195
  slot: string;
194
196
  }): Slot | undefined;
195
- /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none. */
197
+ /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none. A root
198
+ * layer stands for the instance itself, so a slot on the root is listed but hosted by no layer. */
196
199
  declare function slotHostedBy({
197
200
  config,
198
201
  path,
package/dist/spec/slot.js CHANGED
@@ -47,10 +47,16 @@ var Slot = class {
47
47
  this.into = facts.into;
48
48
  this.accepts = facts.accepts;
49
49
  }
50
- /** Whether this is the component's `children`, positioned inside one of its layers. */
50
+ /** Whether this is the component's `children`. */
51
51
  get isChildren() {
52
52
  return this.from === "children";
53
53
  }
54
+ /** Whether the content lands on the component's root layer, so an instance holds it directly
55
+ * rather than in a layer of its own. */
56
+ get onRoot() {
57
+ const layers = (this.config.resolve("component", this.component)?.body)?.layers;
58
+ return layers !== void 0 && rootLayerOf(layers)[0] === this.layer;
59
+ }
54
60
  /**
55
61
  * Whether the slot takes an element: anything when it accepts nothing in particular, else an
56
62
  * element whose ref one of the accepted refs names, exactly or as a member of a group the ref
@@ -282,10 +288,9 @@ var Slot = class {
282
288
  }
283
289
  };
284
290
  /**
285
- * Every slot of a component that content can be placed in: its named node slots, and its `children`
286
- * when a layer of several positions them. A text slot is typed in place rather than filled, so it is
287
- * not one; nor is `children` landing on the root, which is the open hole every container has. The
288
- * same array comes back for the same config.
291
+ * Every slot of a component that content can be placed in, `children` included wherever it lands.
292
+ * A text slot is typed in place rather than filled, so it is not one. The same array comes back for
293
+ * the same config.
289
294
  */
290
295
  function listSlots({ config, path }) {
291
296
  let byPath = listsByConfig.get(config);
@@ -313,12 +318,13 @@ function slotOf({ config, path, slot }) {
313
318
  path
314
319
  }).find((entry) => entry.from === slot);
315
320
  }
316
- /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none. */
321
+ /** The slot whose content lands in `layer`, or `undefined` when the layer hosts none. A root
322
+ * layer stands for the instance itself, so a slot on the root is listed but hosted by no layer. */
317
323
  function slotHostedBy({ config, path, layer }) {
318
324
  return listSlots({
319
325
  config,
320
326
  path
321
- }).find((entry) => entry.layer === layer);
327
+ }).find((entry) => entry.layer === layer && !entry.onRoot);
322
328
  }
323
329
  /** A list stored back the way content is authored: one element bare, several as the list. */
324
330
  function storedContent(list) {
@@ -483,13 +489,10 @@ function slotFacts(config, path) {
483
489
  function factsOfBody({ config, body, routes }) {
484
490
  const props = body.props;
485
491
  const layers = body.layers;
486
- const fillable = routes.filter((route) => {
492
+ return routes.filter((route) => {
487
493
  if ((props?.[route.from])?.text === true) return false;
488
494
  return !routesToText(config, layers, route);
489
- });
490
- const root = layers ? rootLayerOf(layers)[0] : void 0;
491
- const positionedChildren = layers !== void 0 && Object.keys(layers).length >= 2;
492
- return fillable.filter((route) => route.from !== "children" || positionedChildren && route.layer !== root).map((route) => ({
495
+ }).map((route) => ({
493
496
  ...route,
494
497
  accepts: acceptsOf({
495
498
  config,