@yahoo/uds-create-config 5.33.0 → 5.34.1

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.
@@ -242,15 +242,41 @@ function remove(config, input) {
242
242
  path
243
243
  }))];
244
244
  }
245
+ /** The annotations on `paths`, oldest first, as a layer lists them. */
246
+ function annotationsOn({ config, paths }) {
247
+ return config.list("annotation").map((note) => ({
248
+ ...note.body,
249
+ path: note.path
250
+ })).filter((note) => paths.has(pathOf(note.target))).sort((left, right) => (left.authoredAt ?? "").localeCompare(right.authoredAt ?? "") || left.path.localeCompare(right.path));
251
+ }
252
+ /**
253
+ * Each copy of a duplicated element carries copies of the annotations on it. A copy names no author: the
254
+ * writer who duplicates is its author, and stamps it where the write lands.
255
+ */
245
256
  function duplicate(config, input) {
246
257
  const place = placeOf(config, input.path, ["path"]);
247
258
  const fresh = freshPaths(config, rootOf(input.path));
248
259
  const subtree = subtreeOf(config, input.path);
260
+ const notes = annotationsOn({
261
+ config,
262
+ paths: new Set(subtree)
263
+ });
249
264
  const count = input.data?.count ?? 1;
250
265
  const patches = [];
266
+ const annotationPatches = [];
251
267
  const copies = [];
252
268
  for (let n = 0; n < count; n++) {
253
269
  const pathFor = new Map(subtree.map((path) => [path, fresh(path.split("/").at(-1) ?? path)]));
270
+ for (const note of notes) annotationPatches.push({
271
+ kind: "annotation",
272
+ operation: "create",
273
+ path: crypto.randomUUID(),
274
+ data: {
275
+ target: ref("node", pathFor.get(pathOf(note.target))),
276
+ ...note.layer ? { layer: note.layer } : {},
277
+ content: note.content
278
+ }
279
+ });
254
280
  const remap = (entry) => {
255
281
  const to = pathFor.get(pathOf(entry));
256
282
  return to ? ref("node", to) : entry;
@@ -272,7 +298,7 @@ function duplicate(config, input) {
272
298
  }
273
299
  const list = [...place.list];
274
300
  list.splice(place.index + 1, 0, ...copies);
275
- patches.push(placeUpdate(place, list));
301
+ patches.push(placeUpdate(place, list), ...annotationPatches);
276
302
  return patches;
277
303
  }
278
304
  function unwrap(config, input) {
@@ -537,7 +563,7 @@ const nodeGestureOps = {
537
563
  input: duplicateInput,
538
564
  readOnly: false,
539
565
  label: "Duplicate an element",
540
- description: "Copy this element and everything inside it (\"duplicate\", \"make 3 more of these\"): each copy lands right after it among its siblings, `count` of them.",
566
+ description: "Copy this element and everything inside it (\"duplicate\", \"make 3 more of these\"): each copy lands right after it among its siblings, `count` of them, with a copy of each annotation on the copied elements.",
541
567
  scope: "item",
542
568
  title: (entity) => `Duplicate ${entity}`,
543
569
  example: () => ({
@@ -12,10 +12,10 @@ import { translateLinkedStyleBags } from "./linked-style-values.js";
12
12
  import { listedIn } from "../../framework/utils/enumerated.js";
13
13
  import { valueSchemaOf } from "../../framework/value-domain.js";
14
14
  import { Package } from "./Package.js";
15
- import { bindPreviewGlyphs, glyphLeafOf } from "./glyph-identity.js";
15
+ import { bindPreviewGlyphs, glyphMemberRefIn } from "./glyph-identity.js";
16
16
  import { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
17
17
  import { storedPreview } from "../../framework/preview-store.js";
18
- import { glyphPropsOf, propOwner, propValueDomain, refListOf, resolveComponentProps, stylePropNumberUnit, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
18
+ import { glyphPropsOf, glyphPropsOfBody, propOwner, propValueDomain, refListOf, resolveComponentProps, stylePropNumberUnit, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
19
19
  import { carriesContent, intoName, isValueSlot, renderBoundProp, routedPropIn, routesContent } from "../../framework/prop-surface.js";
20
20
  import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../framework/render-spec.js";
21
21
  import { listSlots, namedSlots, slotHostedBy, slotOf, slotsFor } from "../../spec/slot.js";
@@ -664,7 +664,7 @@ function canonicalWhen(when) {
664
664
  function styleBagValue({ config, declaration, name = "<prop>", glyphLibraries }) {
665
665
  const domain = valueSchemaOf(declaration, config);
666
666
  const bound = declaration?.value;
667
- if (glyphLibraries && glyphLibraries.length > 0) return z.union([glyphLeafOf({
667
+ if (glyphLibraries && glyphLibraries.length > 0) return z.union([glyphMemberRefIn({
668
668
  config,
669
669
  libraries: glyphLibraries
670
670
  }), domain]);
@@ -1131,6 +1131,10 @@ const Preview = defineSubEntity({
1131
1131
  config,
1132
1132
  owner: body,
1133
1133
  build: () => {
1134
+ const glyphProps = glyphPropsOfBody({
1135
+ config,
1136
+ body
1137
+ });
1134
1138
  const when = {};
1135
1139
  for (const [name, decl] of Object.entries(body.props ?? {})) when[name] = conditionValue(name, decl, config).optional();
1136
1140
  for (const [name, decl] of Object.entries(body.states ?? {})) when[name] = stateConditionValue(decl).optional();
@@ -1138,7 +1142,8 @@ const Preview = defineSubEntity({
1138
1142
  when: z.strictObject(when, { error: previewShapeError }).nullable().default(null),
1139
1143
  props: z.record(z.string(), z.unknown()).transform((props) => bindPreviewGlyphs({
1140
1144
  config,
1141
- props
1145
+ props,
1146
+ glyphProps
1142
1147
  }))
1143
1148
  }, { error: previewShapeError });
1144
1149
  }
@@ -175,16 +175,30 @@ function glyphOfLibrary({ library, module, leaves }) {
175
175
  }
176
176
  /**
177
177
  * The glyph form for a key that reaches a library through a forward (`icon` → `icon/name`): the
178
- * component or its `package:` ref, answered as the bare member name. The forwarded key stores the
179
- * leaf rather than a member ref, and the emitter and the renderer resolve it through the same
180
- * libraries (`glyphPropsOf`), taking the first that holds it. Each library's arm refuses by cause,
181
- * so a glyph of a library the key does not reach says so.
178
+ * component, its `package:` ref, the member's name a surface writes, or the member ref a stored
179
+ * config already holds, answered as the member ref `icon:<library>/<name>`. The ref names its library, so a key reaching several resolves
180
+ * without a lookup. Each library's arm refuses by cause, so a glyph of a library the key does not
181
+ * reach says so.
182
182
  */
183
- function glyphLeafOf({ config, libraries }) {
184
- const arms = libraries.map((library) => glyphOfLibrary(libraryGlyphFacts({
185
- config,
186
- target: `icon:${library}`
187
- })));
183
+ function glyphMemberRefIn({ config, libraries }) {
184
+ const arms = libraries.map((library) => {
185
+ const facts = libraryGlyphFacts({
186
+ config,
187
+ target: `icon:${library}`
188
+ });
189
+ const toRef = (name) => ({ __ref: `icon:${library}/${name}` });
190
+ const memberOfRef = (value) => {
191
+ if (!isRef(value) || kindOf(value) !== "icon") return void 0;
192
+ const path = pathOf(value);
193
+ const name = path.slice(library.length + 1);
194
+ return path.startsWith(`${library}/`) && facts.leaves.includes(name) ? name : void 0;
195
+ };
196
+ return z.union([
197
+ z.custom((value) => memberOfRef(value) !== void 0).transform((value) => toRef(memberOfRef(value))),
198
+ z.string().refine((name) => facts.leaves.includes(name)).transform(toRef),
199
+ glyphOfLibrary(facts).transform((name) => toRef(name))
200
+ ]);
201
+ });
188
202
  if (arms.length === 1) return arms[0];
189
203
  return z.union(arms);
190
204
  }
@@ -204,9 +218,11 @@ function glyphMemberRefOf(config, identity) {
204
218
  * way `defaultProps` are. Once an owner is known, this pass binds each such ref to
205
219
  * `icon:<library>/<name>` through the library whose package ships it, at the top level and inside a
206
220
  * stored element's props, which is the spelling the build writes when it reads `.preview()` from
207
- * source. A `package:` ref no library claims is another package's export and stays as written.
221
+ * source. A member's name written under one of the owner's glyph props (`glyphProps`) binds through
222
+ * the first of that prop's libraries holding it. A `package:` ref no library claims is another
223
+ * package's export and stays as written.
208
224
  */
209
- function bindPreviewGlyphs({ config, props }) {
225
+ function bindPreviewGlyphs({ config, props, glyphProps = {} }) {
210
226
  const bind = (value) => {
211
227
  if (typeof value !== "object" || value === null) return value;
212
228
  if (Array.isArray(value)) return value.map(bind);
@@ -216,7 +232,14 @@ function bindPreviewGlyphs({ config, props }) {
216
232
  for (const [key, member] of Object.entries(value)) out[key] = bind(member);
217
233
  return out;
218
234
  };
219
- return bind(props);
235
+ const out = bind(props);
236
+ for (const [prop, libraries] of Object.entries(glyphProps)) {
237
+ const name = out[prop];
238
+ if (typeof name !== "string") continue;
239
+ const library = libraries.find((path) => memberLeaves(config, `icon:${path}`).includes(name));
240
+ if (library) out[prop] = { __ref: `icon:${library}/${name}` };
241
+ }
242
+ return out;
220
243
  }
221
244
  //#endregion
222
- export { bindPreviewGlyphs, glyphIdentityOf, glyphLeafOf, glyphMembersOf, glyphOfLibrary, glyphPackageRef, glyphPackageRefOf, isComponentType, libraryGlyphFacts, recordGlyphIdentities };
245
+ export { bindPreviewGlyphs, glyphIdentityOf, glyphMemberRefIn, glyphMembersOf, glyphOfLibrary, glyphPackageRef, glyphPackageRefOf, isComponentType, libraryGlyphFacts, recordGlyphIdentities };
@@ -3,7 +3,7 @@ import { composedValueSchemas } from "./composed-values.js";
3
3
  import { defineSubEntity } from "../../framework/defineSubEntity.js";
4
4
  import { isCssProperty, kebabCase } from "../../css/values.js";
5
5
  import { valueSchemaOf } from "../../framework/value-domain.js";
6
- import { glyphLeafOf, glyphPackageRef } from "./glyph-identity.js";
6
+ import { glyphMemberRefIn, glyphPackageRef } from "./glyph-identity.js";
7
7
  import { glyphPropsOfBody, isCustomPropertyKey, refListOf, stylePropTokenPath } from "../../framework/projections.js";
8
8
  import { z } from "zod";
9
9
  //#region src/entities/system/style-bag.ts
@@ -59,7 +59,7 @@ const Style = defineSubEntity({
59
59
  config,
60
60
  body: parent
61
61
  })[key];
62
- if (libraries && libraries.length > 0) return z.union([glyphLeafOf({
62
+ if (libraries && libraries.length > 0) return z.union([glyphMemberRefIn({
63
63
  config,
64
64
  libraries
65
65
  }), styleValue]);
@@ -5864,7 +5864,7 @@ var Config = class Config {
5864
5864
  const claimed = schemaVersionOf(stored);
5865
5865
  if (claimed > 20261013201548) throw new SchemaVersionTooNew(claimed);
5866
5866
  const wire = upgradeSerializedConfig(stored);
5867
- 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\`.`);
5867
+ 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.`);
5868
5868
  const leftover = wire.options;
5869
5869
  if (isPlainObject(leftover) && Object.keys(leftover).length > 0) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" carries an \`options\` slot holding ${Object.keys(leftover).map((key) => `"${key}"`).join(", ")} after every migration ran — this config type has nowhere to keep them. Rebuild it: \`uds build\` and \`uds push\` for a system with a repo; for a system created in Create, reset the local database that holds it.`);
5870
5870
  const meta = this.metaDraft();
@@ -19,18 +19,16 @@ import { parseRawCss } from "./raw-css.js";
19
19
  import { System } from "../../configs/system.js";
20
20
  //#region src/migrations/2.0.0/v1-artifact.ts
21
21
  /**
22
- * The v1 → config-v2 reader, for `uds migrate` and nothing else.
22
+ * The v1 → config-v2 reader, run by the cutover schema migration in `configs/system.ts`.
23
23
  *
24
24
  * A v1 system's authoring vocabulary is gone, so its `uds.config.ts` cannot be imported — but what
25
- * that config DESCRIBED survives in the flat `config.json` its last build wrote. This reads that
26
- * artifact into a config-v2 `System`, which `uds migrate` then renders back out as v2 source.
25
+ * that config described survives in the flat `config.json` its last build wrote. This reads that
26
+ * artifact into a config-v2 `System`.
27
27
  *
28
- * **Runtime code must not come through here.** Every stored artifact is the config-v2 wire form and
29
- * `Config.fromJSON` is the one door to it; this exists for the one-time port of a repo that never
30
- * built on the new version. It is also best-effort in a way a runtime path must not be: the few old
31
- * shapes config-v2 doesn't model (color expressions, component `preview`) pass through or drop
32
- * rather than failing the load, so a config read through here is the
33
- * starting point for a migration, not a faithful copy of a system.
28
+ * Runtime code doesn't come through here directly. `Config.fromJSON` is the one door to a stored
29
+ * artifact, and it reaches this only for one stamped before config-v2. It is best-effort: the few
30
+ * old shapes config-v2 doesn't model (color expressions, component `preview`) pass through or drop
31
+ * rather than failing the load, so a config read through here is not a faithful copy of a system.
34
32
  */
35
33
  /**
36
34
  * Default authoring guidance seeded onto a system that declares none — the stop-gap that fills