@yahoo/uds-create-config 4.49.1 → 4.50.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.
@@ -15,7 +15,7 @@ const Composite = defineEntity({
15
15
  kind: "composite",
16
16
  label: "Composite style",
17
17
  labelPlural: "Composite styles",
18
- fields: z.object({ styles: Style }),
18
+ fields: z.object({ styles: Style.describe("The style values the composite bundles. Each declaration is a member, so an editor writes one key at a time.") }),
19
19
  subEntities: { styles: Style },
20
20
  nameEdges: bagNameEdges(compositeBags),
21
21
  styleBags: compositeBags,
@@ -3,9 +3,9 @@ import { z } from "zod";
3
3
  const Device = defineEntity({
4
4
  kind: "device",
5
5
  fields: z.object({
6
- width: z.number(),
7
- height: z.number(),
8
- name: z.string().optional()
6
+ width: z.number().describe("The viewport width a preview renders at."),
7
+ height: z.number().describe("The viewport height a preview renders at."),
8
+ name: z.string().optional().describe("The name the device picker shows.")
9
9
  })
10
10
  });
11
11
  //#endregion
@@ -20,11 +20,11 @@ const Font = defineEntity({
20
20
  labelPlural: "Fonts",
21
21
  fields: z.strictObject({
22
22
  /** The CSS-facing family name (`'YA Sans'`) — required; a family without one can't be used. */
23
- fontFamily: z.string(),
23
+ fontFamily: z.string().describe("The CSS family name, such as `'YA Sans'`."),
24
24
  /** One entry per font file. */
25
- files: FontFile.optional(),
25
+ files: FontFile.optional().describe("One entry per font file, keyed by any name (`'400-normal'`)."),
26
26
  /** Families to fall back to after this one, for a `font-family` stack. */
27
- fallbackStack: z.array(z.string()).optional(),
27
+ fallbackStack: z.array(z.string()).optional().describe("Families to fall back to, in order."),
28
28
  /**
29
29
  * A CSS custom property the HOST publishes the loaded face's family under — `next/font`'s
30
30
  * `variable` option (`--uds-font-family-inter`), or any loader that hands the page a variable
@@ -33,11 +33,11 @@ const Font = defineEntity({
33
33
  * is how a token can be `Font.ref(…)` to such a family and still render the host's face, whose
34
34
  * real family name (`__Inter_a1b2c3`) nothing in a config can know.
35
35
  */
36
- variable: z.string().regex(/^--[A-Za-z0-9_-]+$/, "a CSS custom property name: `--name`").optional(),
36
+ variable: z.string().regex(/^--[A-Za-z0-9_-]+$/, "a CSS custom property name: `--name`").optional().describe("A CSS custom property the host publishes the loaded face under, such as `next/font`'s `variable`. Must look like `--name`."),
37
37
  /** Set explicitly; otherwise inferred from a file declaring a weight RANGE. */
38
- isVariableFont: z.boolean().optional()
38
+ isVariableFont: z.boolean().optional().describe("Set explicitly, or inferred from a file that declares a weight range.")
39
39
  }),
40
- groupFields: z.strictObject({ version: z.string().optional() }),
40
+ groupFields: z.strictObject({ version: z.string().optional().describe("The version of the family the group ships.") }),
41
41
  subEntities: { files: FontFile }
42
42
  });
43
43
  /**
@@ -32,15 +32,15 @@ const GlobalStyle = defineEntity({
32
32
  labelPlural: "Global styles",
33
33
  fields: z.strictObject({
34
34
  /** What the rule applies to — `html, body`, `@keyframes bounce`, `.prompt-editor p`. */
35
- selector: z.string(),
35
+ selector: z.string().describe("What the rule applies to: `html, body`, `@keyframes bounce`, `.prompt-editor p`."),
36
36
  /** Property → value, camelCase as it's authored; a nested record is a nested rule. */
37
- declarations: z.record(z.string(), declared),
37
+ declarations: z.record(z.string(), declared).describe("Property to value, in camelCase. A nested record is a nested rule, for an at-rule or grouped selector."),
38
38
  /**
39
39
  * Every declaration in the rule, nested ones included, is emitted `!important`. The sheet is
40
40
  * layered and a third party's stylesheet (React Flow, CodeMirror's injected theme) is not, so a
41
41
  * rule meant to beat one has to say so; the flag lets it while its values stay refs.
42
42
  */
43
- important: z.boolean().optional()
43
+ important: z.boolean().optional().describe("Emit every declaration `!important`, to beat a third party's unlayered stylesheet.")
44
44
  })
45
45
  });
46
46
  /**
@@ -14,7 +14,7 @@ const Guidance = defineEntity({
14
14
  kind: "guidance",
15
15
  label: "Guidance",
16
16
  labelPlural: "Guidance",
17
- fields: z.object({ text: z.array(z.union([z.string(), refSchema])) }),
17
+ fields: z.object({ text: z.array(z.union([z.string(), refSchema])).describe("The rule, as prose runs and `@mention` refs to tokens or components.") }),
18
18
  groupFields: z.strictObject({
19
19
  /** Display name for the topic; falls back to the group's own path segment. */
20
20
  label: z.string().optional(),
@@ -24,7 +24,7 @@ const Guidance = defineEntity({
24
24
  * Which AI lanes this topic is fed to. Absent means every lane. An empty array is a topic
25
25
  * turned off everywhere, which is what unchecking the last box stores.
26
26
  */
27
- appliesTo: z.array(z.enum(GUIDANCE_LANES)).optional()
27
+ appliesTo: z.array(z.enum(GUIDANCE_LANES)).optional().describe("The lanes the topic reaches. Unset means every lane.")
28
28
  })
29
29
  });
30
30
  /** Nothing up the chain scoped anything, so the rules reach every surface. */
@@ -26,22 +26,22 @@ const Icon = defineEntity({
26
26
  /** The package the glyphs come from — a REF to the `Package` item, which owns the module
27
27
  * specifier (its path) and the pinned version. A generated module imports the glyphs it renders
28
28
  * from it, and a render that imports a member of it is placing a glyph of this set. */
29
- package: Package.optional(),
29
+ package: Package.optional().describe("The npm package the set comes from."),
30
30
  /** Where member metadata comes from and how to read it — `{ from, format }`. The build resolves
31
31
  * the source and bakes each member's fields; see `icon-metadata.ts`. */
32
- metadata: iconMetadataDeclaration.optional(),
32
+ metadata: iconMetadataDeclaration.optional().describe("Catalogue metadata read from the package."),
33
33
  /** Named sizes the set ships (`sm: 16`).
34
34
  * @deprecated How a glyph is sized is the icon tool's component's business: the icon tool's component declares its own `size` and `variant` props, and every surface reads those. TODO: remove with a migration once no surface reads the library's copy. */
35
- sizes: z.record(z.string(), z.number()).optional(),
35
+ sizes: z.record(z.string(), z.number()).optional().describe("Deprecated. The icon tool's component declares its own `size` and `variant` props."),
36
36
  /** Weights/styles the set ships (`regular`, `fill`).
37
37
  * @deprecated Which styles a glyph takes is the icon tool's component's business: the icon tool's component declares its own `size` and `variant` props, and every surface reads those. TODO: remove with a migration once no surface reads the library's copy. */
38
- variants: z.array(z.string()).optional(),
38
+ variants: z.array(z.string()).optional().describe("Deprecated. The icon tool's component declares its own `size` and `variant` props."),
39
39
  /** How a variant reaches the glyph; see {@link iconVariantAs}. Absent, a variant is dropped at
40
40
  * render.
41
41
  * @deprecated Only the framework's raw-draw path read it; the icon tool's component passes a
42
42
  * variant the way its package wants, and a set whose styles are separate exports lists them as
43
43
  * members. TODO: remove with `sizes` and `variants`. */
44
- variantAs: iconVariantAs.optional()
44
+ variantAs: iconVariantAs.optional().describe("Deprecated. The icon tool's component passes a variant the way its package wants.")
45
45
  }),
46
46
  groupComputed: {
47
47
  /** The glyphs the set ships, by leaf name. */
@@ -246,7 +246,7 @@ const pinOptions = z.record(z.string(), z.record(z.string(), z.strictObject({
246
246
  override: z.record(z.string(), z.unknown()).optional(),
247
247
  extend: z.record(z.string(), z.unknown()).optional(),
248
248
  hidden: z.boolean().optional()
249
- }).refine((entry) => entry.override !== void 0 || entry.extend !== void 0 || entry.hidden !== void 0, { message: "An `options` entry needs `override`, `extend` or `hidden`." }))).optional();
249
+ }).refine((entry) => entry.override !== void 0 || entry.extend !== void 0 || entry.hidden !== void 0, { message: "An `options` entry needs `override`, `extend` or `hidden`." }))).optional().describe("Per-entity adjustments, keyed by accessor, then path, then verb.");
250
250
  const LinkedSystemDefinition = defineEntity({
251
251
  kind: "linked-system",
252
252
  label: "Linked system",
@@ -503,7 +503,7 @@ const LinkedSystemDefinition = defineEntity({
503
503
  * ignores `version`: a repo-local link tracks whatever the sibling currently is, which is the point
504
504
  * in a monorepo, and a built artifact carries no `systemId` or `version` to pin against.
505
505
  */
506
- configPath: z.string().min(1).optional(),
506
+ configPath: z.string().min(1).optional().describe("The sibling config this link reads, as a path to its `uds.config.ts`. Wins for resolution when set."),
507
507
  /**
508
508
  * What this system draws from that source — a standing declaration, and the thing the build turns
509
509
  * into rows.
@@ -544,7 +544,7 @@ const LinkedSystemDefinition = defineEntity({
544
544
  * kind says which it is (`EntityKind.linkable`). Nothing is subtracted: a pin and a `file` aren't
545
545
  * borrowable, so no borrow of one exists for this to cover.
546
546
  */
547
- link: z.union([z.literal("*"), z.record(z.string(), z.union([z.literal("*"), z.array(z.string().min(1)).min(1)]))]).optional(),
547
+ link: z.union([z.literal("*"), z.record(z.string(), z.union([z.literal("*"), z.array(z.string().min(1)).min(1)]))]).optional().describe("What this system draws from the source, standing: `'*'` for everything, or a record by kind."),
548
548
  /**
549
549
  * What this system does to individual entities of the source, keyed by accessor, then by the path
550
550
  * the source spells, then by verb.
@@ -604,7 +604,7 @@ const LinkedSystemDefinition = defineEntity({
604
604
  * and the lockfile already decide it, so a second copy here would be a version nobody resolves
605
605
  * against. `version` stays observed for this origin, as it is for a sibling.
606
606
  */
607
- package: z.string().min(1).optional(),
607
+ package: z.string().min(1).optional().describe("An npm package that ships a built system."),
608
608
  /**
609
609
  * The source's database id — durable identity, which survives a rename on their side.
610
610
  *
@@ -613,9 +613,9 @@ const LinkedSystemDefinition = defineEntity({
613
613
  * So push derives it for a `configPath` link and writes it into the artifact, and nothing local asks
614
614
  * an author to hand-type an unverifiable id.
615
615
  */
616
- systemId: z.string().min(1).optional(),
616
+ systemId: z.string().min(1).optional().describe("The source's durable id in Create. Push fills it for a repo-local link."),
617
617
  /** Display only, never identity: what to call this source in the UI. Defaults to the slug. */
618
- name: z.string().optional(),
618
+ name: z.string().optional().describe("Display only. Defaults to the slug."),
619
619
  /**
620
620
  * The exact published version every ref through this link resolves against.
621
621
  *
@@ -623,7 +623,7 @@ const LinkedSystemDefinition = defineEntity({
623
623
  * at push time so Studio can say which version it saw, and the local build still tracks current
624
624
  * state. Read as a pin, a repo-local link would go stale the moment the sibling moved.
625
625
  */
626
- version: z.string().regex(EXACT_VERSION, "A link is pinned to an exact version (2.4.0), not a range").optional()
626
+ version: z.string().regex(EXACT_VERSION, "A link is pinned to an exact version (2.4.0), not a range").optional().describe("An exact version (`2.4.0`), never a range.")
627
627
  }),
628
628
  computed: {
629
629
  /**
@@ -29,14 +29,14 @@ const modifierRef = refTo("modifier");
29
29
  * is the base values and no option is default.
30
30
  */
31
31
  const modifierGroupFields = z.object({
32
- type: z.enum(MODIFIER_CATEGORIES).optional(),
33
- default: modifierRef.optional()
32
+ type: z.enum(MODIFIER_CATEGORIES).optional().describe("Makes the group a mode or a state axis. A group with no type is a plain folder."),
33
+ default: modifierRef.optional().describe("The option a viewer starts in. The native theme reads it.")
34
34
  });
35
35
  const Modifier = defineEntity({
36
36
  kind: "modifier",
37
37
  fields: z.object({
38
- type: z.enum(WEB_ACTIVATIONS),
39
- value: z.union([z.string(), z.lazy(() => Composite)])
38
+ type: z.enum(WEB_ACTIVATIONS).describe("How the modifier activates, such as `selector` or `media`."),
39
+ value: z.union([z.string(), z.lazy(() => Composite)]).describe("The selector or query it activates on, or a composite ref.")
40
40
  }).check((ctx) => {
41
41
  const { type, value } = ctx.value;
42
42
  const wantsRef = type === "composite";
@@ -15,28 +15,29 @@ import { z } from "zod";
15
15
  * lost `whileHover`/`whileTap`/`drag` on the way in and animated nothing.
16
16
  */
17
17
  const gesture = z.record(z.string(), z.unknown()).optional();
18
+ const RUNTIME = "Which runtime plays the preset, and so which fields apply.";
18
19
  const CssMotion = z.strictObject({
19
- runtime: z.literal("css"),
20
- keyframes: z.record(z.string(), z.record(z.string(), z.unknown())),
21
- duration: z.string().optional(),
22
- delay: z.string().optional(),
23
- timingFunction: z.string().optional(),
24
- iterationCount: z.union([z.string(), z.number()]).optional(),
25
- direction: z.string().optional(),
26
- fillMode: z.string().optional()
20
+ runtime: z.literal("css").describe(RUNTIME),
21
+ keyframes: z.record(z.string(), z.record(z.string(), z.unknown())).describe("The CSS keyframes, keyed by offset (`from`, `50%`, `to`)."),
22
+ duration: z.string().optional().describe("The CSS `animation-duration`, such as `'4s'`."),
23
+ delay: z.string().optional().describe("The CSS `animation-delay`."),
24
+ timingFunction: z.string().optional().describe("The CSS `animation-timing-function`."),
25
+ iterationCount: z.union([z.string(), z.number()]).optional().describe("The CSS `animation-iteration-count`, or `'infinite'`."),
26
+ direction: z.string().optional().describe("The CSS `animation-direction`."),
27
+ fillMode: z.string().optional().describe("The CSS `animation-fill-mode`.")
27
28
  });
28
29
  const JsMotion = z.strictObject({
29
- runtime: z.literal("js"),
30
- initial: gesture,
31
- animate: gesture,
32
- transition: gesture,
33
- whileHover: gesture,
34
- whileTap: gesture,
35
- whileFocus: gesture,
36
- whileDrag: gesture,
37
- drag: z.union([z.boolean(), z.string()]).optional(),
38
- dragConstraints: z.record(z.string(), z.number()).optional(),
39
- dragElastic: z.number().optional()
30
+ runtime: z.literal("js").describe(RUNTIME),
31
+ initial: gesture.describe("The starting values, as `motion/react` reads them."),
32
+ animate: gesture.describe("The values to animate to."),
33
+ transition: gesture.describe("How the animation moves, such as a spring."),
34
+ whileHover: gesture.describe("Values held while the element is hovered."),
35
+ whileTap: gesture.describe("Values held while the element is pressed."),
36
+ whileFocus: gesture.describe("Values held while the element has focus."),
37
+ whileDrag: gesture.describe("Values held while the element is dragged."),
38
+ drag: z.union([z.boolean(), z.string()]).optional().describe("Whether, and along which axis, the element can be dragged."),
39
+ dragConstraints: z.record(z.string(), z.number()).optional().describe("The box a drag stays within."),
40
+ dragElastic: z.number().optional().describe("How far past its constraints a drag can stretch.")
40
41
  });
41
42
  const Motion = defineEntity({
42
43
  kind: "motion",
@@ -48,9 +48,10 @@ interface PackageExportClass extends Named<typeof PackageExportDefinition> {}
48
48
  * makes the portable config the source of truth for consumer install requirements.
49
49
  *
50
50
  * Ordinary component authoring does not maintain this graph: the build derives modules and exports
51
- * from the source it walked. A human may still author one — to carry a label, or to pin a `version` —
52
- * and build collection updates rather than replaces it, so that metadata survives. What authoring
53
- * cannot do is choose the path.
51
+ * from the source it walked. A human may still author one, to carry a label or a description, and
52
+ * build collection updates rather than replaces it, so that metadata survives. `version` is always
53
+ * the installed package's, which the build writes over an authored one. What authoring cannot do is
54
+ * choose the path.
54
55
  */
55
56
  declare const PackageDefinition: EntityClass<z.ZodObject<{
56
57
  version: z.ZodOptional<z.ZodString>;
@@ -18,10 +18,10 @@ const Package = defineEntity({
18
18
  fields: z.strictObject({
19
19
  /** Pinned at build from the package's own `package.json`, like an icon library's already is.
20
20
  * Read from the PACKAGE (`motion`), since that is what has a manifest — a subpath has none. */
21
- version: z.string().optional(),
21
+ version: z.string().optional().describe("The pinned version, read at build from the package's manifest."),
22
22
  /** The exports a render placed in JSX. Absent for a module nothing names by export: a runtime
23
23
  * dependency, or a catalogue read for its data. */
24
- exports: PackageExport.optional()
24
+ exports: PackageExport.optional().describe("The exports a render placed in JSX, each keyed by the name the target spells (`View`, `default`, `Animated.View`).")
25
25
  }),
26
26
  subEntities: { exports: PackageExport }
27
27
  });
@@ -368,7 +368,7 @@ const styleFields = ({ config, body, linked } = {}) => z.object({
368
368
  body,
369
369
  linked
370
370
  }).optional().describe("What the prop accepts. A ref to a token GROUP (`{ \"__ref\": \"token:color\" }`) makes every member a value, named by its path under the group, and is how a prop takes tokens; a string or number is a keyword; `{ \"alias\": \"full\", \"value\": \"9999px\" }` names a literal; `{ \"alias\": \"brand\", \"value\": { \"__ref\": \"token:color/green\" } }` names one token under another name. One group ref usually replaces listing tokens one by one."),
371
- responsive: z.boolean().default(false),
371
+ responsive: z.boolean().default(false).describe("Whether the prop takes responsive values. Defaults to `false`."),
372
372
  /**
373
373
  * The class-name segment this prop contributes — `borderStyle` emits `.uds-border-style-solid`,
374
374
  * and a prop whose values are already self-describing sets `''` so `position: absolute` emits
@@ -378,8 +378,8 @@ const styleFields = ({ config, body, linked } = {}) => z.object({
378
378
  * decision, and the same decision decides the class a consumer's markup already ships. Defaults
379
379
  * to the prop's own path, which is the common case.
380
380
  */
381
- classPrefix: z.string().optional(),
382
- arbitrary: z.boolean().default(false),
381
+ classPrefix: z.string().optional().describe("The class-name segment the prop contributes. Defaults to the prop's own path; `''` for self-describing values."),
382
+ arbitrary: z.boolean().default(false).describe("Whether the prop also accepts a bracketed literal such as `[13px]`. Defaults to `false`."),
383
383
  /**
384
384
  * The prop applies the NEGATION of its value — `offsetX="3"` paints `margin-inline: calc(var(…) *
385
385
  * -1)`.
@@ -395,7 +395,7 @@ const styleFields = ({ config, body, linked } = {}) => z.object({
395
395
  * name conventionally spells it (`-mx`), but a name is not a rule — reading the leading dash as
396
396
  * one would make a rename silently change what a prop paints.
397
397
  */
398
- negates: z.boolean().default(false),
398
+ negates: z.boolean().default(false).describe("Apply the negation of the value, so one scale works in both directions. Defaults to `false`."),
399
399
  /**
400
400
  * The style prop whose alpha this one controls — `bgOpacity` is the opacity of `bg`.
401
401
  *
@@ -409,7 +409,7 @@ const styleFields = ({ config, body, linked } = {}) => z.object({
409
409
  * instead of an implied name. Such a prop sets no CSS property of its own, hence an empty
410
410
  * `properties`: it modulates its target rather than declaring anything.
411
411
  */
412
- opacityOf: z.lazy(() => StyleProperty).optional()
412
+ opacityOf: z.lazy(() => StyleProperty).optional().describe("The style property whose alpha this one controls, as `bgOpacity` does for `bg`.")
413
413
  });
414
414
  /** `path` if no style property holds it, else the first free numbered variant of it. */
415
415
  function freePath(config, path) {
@@ -117,11 +117,11 @@ const Override = defineOverride(tokenValue);
117
117
  const TokenEntity = defineEntity({
118
118
  kind: "token",
119
119
  fields: z.object({
120
- value: tokenValue,
121
- type: z.string().optional(),
122
- overrides: Override.optional()
120
+ value: tokenValue.describe("The base value: a literal, a ref to another token, or a composed value such as a gradient."),
121
+ type: z.string().optional().describe("The CSS value type, when the value alone does not say it."),
122
+ overrides: Override.optional().describe("Per-mode values, keyed by the condition they apply under.")
123
123
  }),
124
- groupFields: z.object({ cssPrefix: z.string().optional() }),
124
+ groupFields: z.object({ cssPrefix: z.string().optional().describe("The prefix the group emits its custom properties under. Local to the config that holds the group.") }),
125
125
  localGroupFields: ["cssPrefix"],
126
126
  subEntities: { overrides: Override },
127
127
  schemas: { create: { description: `Create a token at \`<group>/<name>\` — \`data.value\` is the CSS value. The first path segment is its group, and a style property takes a whole group as one ref (\`${JSON.stringify(ref("token", "<group>"))}\`), so put the tokens one prop should offer in one group rather than listing values on the prop.` } }
@@ -28,20 +28,20 @@ const iconMemberMetadata = z.strictObject({
28
28
  /** Grouping, PLURAL — every real catalogue lets a glyph belong to several (most of Phosphor's
29
29
  * do), or none. Stored RAW as the source spells them (`'health & wellness'`); casing and
30
30
  * which one a section heading shows are display policy, not stored facts. */
31
- categories: z.array(z.string().min(1)).optional(),
31
+ categories: z.array(z.string().min(1)).optional().describe("Groupings, stored as the catalogue spells them. A glyph can be in several."),
32
32
  /** Search terms beyond the name. Order carries no meaning; a reorder is not an edit. */
33
- tags: z.array(z.string()).optional(),
33
+ tags: z.array(z.string()).optional().describe("Search terms beyond the name."),
34
34
  /** The glyph's code point in a font-backed set — what a native glyphmap reads. */
35
- codepoint: z.number().optional(),
35
+ codepoint: z.number().optional().describe("The code point in a font-backed set."),
36
36
  /** The subset of the set's `variants` this glyph ships, for a ragged set (Tabler draws `filled`
37
37
  * for a third of its glyphs). Absent means every variant the set declares. */
38
- variants: z.array(z.string().min(1)).optional(),
38
+ variants: z.array(z.string().min(1)).optional().describe("The styles this glyph ships, for a set where not every glyph has every style."),
39
39
  /** Variants whose glyph paints a literal white fill by design (a "knockout" meant for dark or
40
40
  * branded surfaces) rather than `currentColor`. Such a glyph is invisible on a light surface,
41
41
  * so pickers and catalogs preview these variants on a dark one. Rendering is untouched — the
42
42
  * field is presentation advice for browsing UIs, and member-owned like the set's `variants`:
43
43
  * only a source that knows its own knockouts can flag them. */
44
- knockoutVariants: z.array(z.string().min(1)).optional()
44
+ knockoutVariants: z.array(z.string().min(1)).optional().describe("Styles that paint a literal white fill, which pickers preview on a dark surface.")
45
45
  });
46
46
  /**
47
47
  * The `metadata` group field: where a set's member facts come from (`from`, a ref to the source's
@@ -66,8 +66,13 @@ function partializeShape(shape, keep) {
66
66
  continue;
67
67
  }
68
68
  let inner = field;
69
- while (inner instanceof z.ZodOptional || inner instanceof z.ZodDefault) inner = inner.unwrap();
70
- out[key] = inner.nullable().optional();
69
+ let description = inner.description;
70
+ while (inner instanceof z.ZodOptional || inner instanceof z.ZodDefault) {
71
+ inner = inner.unwrap();
72
+ description ??= inner.description;
73
+ }
74
+ const delta = inner.nullable().optional();
75
+ out[key] = description ? delta.describe(description) : delta;
71
76
  }
72
77
  return out;
73
78
  }