@yahoo/uds-create-config 5.65.0 → 5.66.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.
@@ -33,6 +33,7 @@ import { conditionalDefaultsMigration } from "../migrations/20261023152030_condi
33
33
  import { timingValueMigration } from "../migrations/20261026125912_timing_value.js";
34
34
  import { variantValuesListWithBodiesMigration } from "../migrations/20261027184442_variant_values_list_with_bodies.js";
35
35
  import { lintIgnoreMigration } from "../migrations/20261027205147_lint_ignore.js";
36
+ import { componentMapPropsMigration } from "../migrations/20261030134149_component_map_props.js";
36
37
  import { CanvasRole } from "../entities/system/CanvasRole.js";
37
38
  import { GlobalStyle } from "../entities/system/GlobalStyle.js";
38
39
  import { Guidance } from "../entities/system/Guidance.js";
@@ -199,5 +200,6 @@ if (!registeredSchemaMigrations().has(20261023152030)) registerSchemaMigrations(
199
200
  if (!registeredSchemaMigrations().has(20261026125912)) registerSchemaMigrations(timingValueMigration);
200
201
  if (!registeredSchemaMigrations().has(20261027184442)) registerSchemaMigrations(variantValuesListWithBodiesMigration);
201
202
  if (!registeredSchemaMigrations().has(20261027205147)) registerSchemaMigrations(lintIgnoreMigration);
203
+ if (!registeredSchemaMigrations().has(20261030134149)) registerSchemaMigrations(componentMapPropsMigration);
202
204
  //#endregion
203
205
  export { System };
@@ -869,6 +869,31 @@ declare const ComponentEntity: EntityClass<z.ZodObject<{
869
869
  }, z.core.$strip>;
870
870
  }, z.core.$strip>;
871
871
  };
872
+ readonly 'map-props': {
873
+ readonly input: z.ZodObject<{
874
+ path: z.ZodType<string, string | undefined, z.core.$ZodTypeInternals<string, string | undefined>>;
875
+ data: z.ZodObject<{
876
+ rules: z.ZodArray<z.ZodObject<{
877
+ when: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
878
+ set: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
879
+ unset: z.ZodOptional<z.ZodArray<z.ZodString>>;
880
+ }, z.core.$strip>>;
881
+ }, z.core.$strip>;
882
+ }, z.core.$strip>;
883
+ };
884
+ readonly 'map-props-plan': {
885
+ readonly input: z.ZodObject<{
886
+ path: z.ZodType<string, string | undefined, z.core.$ZodTypeInternals<string, string | undefined>>;
887
+ data: z.ZodObject<{
888
+ rules: z.ZodArray<z.ZodObject<{
889
+ when: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
890
+ set: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
891
+ unset: z.ZodOptional<z.ZodArray<z.ZodString>>;
892
+ }, z.core.$strip>>;
893
+ instances: z.ZodDefault<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
894
+ }, z.core.$strip>;
895
+ }, z.core.$strip>;
896
+ };
872
897
  }, {
873
898
  /**
874
899
  * What each layer can forward, for the layers that render a component with props: every prop its
@@ -24,6 +24,7 @@ import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../frame
24
24
  import { listSlots, namedSlots, slotHostedBy, slotOf, slotsFor } from "../../spec/slot.js";
25
25
  import { cellAt } from "../../spec/cell.js";
26
26
  import { instanceAt } from "../../spec/instance.js";
27
+ import { mapComponentProps, propMappingErrors, propMappingRulesSchema } from "../../spec/prop-mapping.js";
27
28
  import { forwardOptions, forwardsWrite } from "./component-forwards.js";
28
29
  import { assertComponentSourceInput } from "./component-source-fields.js";
29
30
  import { checkStyleSet, styleSetEdits } from "./component-style-set.js";
@@ -2924,6 +2925,55 @@ element: refSchema })
2924
2925
  data: { props: exampleProps(config, path) }
2925
2926
  }),
2926
2927
  handler: (input, config) => validateComponentProps(config, String(input.path), input.data.props ?? {})
2928
+ },
2929
+ "map-props": {
2930
+ input: z.object({
2931
+ path: pathInput("component"),
2932
+ data: z.object({ rules: propMappingRulesSchema })
2933
+ }),
2934
+ readOnly: false,
2935
+ approval: "reference-change",
2936
+ label: "Map props",
2937
+ description: "Move a component's instances onto its props as they are now, after its API changed: `variant: 'primary'` becomes `color: 'brand', prominence: 'solid'`. It changes nothing in the config. Where it runs decides what it moves: in Create, the instances in the branch's design files; in a checkout (`uds config edit`, the local UDS MCP), the call sites in the project's source. Refuses a rule that writes a prop or value the component doesn't take. Run it with `plan: true` first: the plan says what it would move, and writes nothing.",
2938
+ scope: "item",
2939
+ title: (entity) => `Map ${entity} props`,
2940
+ example: ({ config, path }) => {
2941
+ const { data, ...rest } = exampleMapping(config, path);
2942
+ return {
2943
+ ...rest,
2944
+ data: { rules: data.rules }
2945
+ };
2946
+ },
2947
+ handler: (input, config) => {
2948
+ const errors = propMappingErrors(config, {
2949
+ component: String(input.path),
2950
+ rules: input.data.rules
2951
+ });
2952
+ if (errors.length > 0) rejectData(errors.join(" "));
2953
+ return config;
2954
+ }
2955
+ },
2956
+ "map-props-plan": {
2957
+ input: z.object({
2958
+ path: pathInput("component"),
2959
+ data: z.object({
2960
+ rules: propMappingRulesSchema,
2961
+ instances: z.array(z.record(z.string(), z.unknown())).default([]).describe("Instances' props as they are stored or written: each comes back with the rules applied.")
2962
+ })
2963
+ }),
2964
+ readOnly: true,
2965
+ label: "Plan a props mapping",
2966
+ description: "Try a mapping on some instances' props before `map-props` applies it: refuses a rule that writes a prop or value the component doesn't take, and returns each given instance's props with the rules applied. Writes nothing.",
2967
+ scope: "item",
2968
+ title: (entity) => `Plan mapping ${entity} props`,
2969
+ example: ({ config, path }) => exampleMapping(config, path),
2970
+ handler: (input, config) => {
2971
+ const data = input.data;
2972
+ return mapComponentProps(config, {
2973
+ component: String(input.path),
2974
+ rules: data.rules
2975
+ }, data.instances ?? []);
2976
+ }
2927
2977
  }
2928
2978
  },
2929
2979
  invariants: (config) => [
@@ -3072,5 +3122,35 @@ function exampleProps(config, componentPath) {
3072
3122
  }
3073
3123
  return {};
3074
3124
  }
3125
+ /** A runnable `map-props-plan` example: an instance holding the first variant prop's first value, and
3126
+ * a rule moving it onto the prop's second value (or the same one, for a prop with a single value).
3127
+ * A component with no variant gets the other common case, dropping a prop it no longer has.
3128
+ * `map-props` takes the same rules, without the instances. */
3129
+ function exampleMapping(config, componentPath) {
3130
+ const instance = exampleProps(config, componentPath);
3131
+ const [name, value] = Object.entries(instance)[0] ?? [];
3132
+ if (name === void 0 || value === void 0) return {
3133
+ path: componentPath,
3134
+ data: {
3135
+ rules: [{
3136
+ when: { variant: "primary" },
3137
+ unset: ["variant"]
3138
+ }],
3139
+ instances: [{ variant: "primary" }]
3140
+ }
3141
+ };
3142
+ const decl = (config.resolve("component", componentPath)?.toJSON())?.props?.[name];
3143
+ const next = (decl ? propValueDomain(config, decl)?.leaves ?? [] : []).find((leaf) => leaf !== value) ?? value;
3144
+ return {
3145
+ path: componentPath,
3146
+ data: {
3147
+ rules: [{
3148
+ when: { [name]: value },
3149
+ set: { [name]: next }
3150
+ }],
3151
+ instances: [instance]
3152
+ }
3153
+ };
3154
+ }
3075
3155
  //#endregion
3076
3156
  export { Component, SLOT_CODE_FORMS, blankComponentBody, canonicalWhen, forwardedNames, layerRef, ownValues, ownsItsValues, storedStyleRules, valueRef };
@@ -74,7 +74,7 @@ interface RecordedSource {
74
74
  * The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
75
75
  * that replaced it (see `detectedWireVersion`).
76
76
  */
77
- declare const SERIALIZED_CONFIG_VERSION = 20261027205147;
77
+ declare const SERIALIZED_CONFIG_VERSION = 20261030134149;
78
78
  interface SerializedConfig {
79
79
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
80
80
  readonly version: number;
@@ -10,8 +10,8 @@ import { borrowedSlug, classifyOverlay, uniteEntries } from "./source-integrity.
10
10
  import { kindBorrowable, titleCase } from "./registry.js";
11
11
  import { isGroupBody } from "./utils/group.js";
12
12
  import { authoredBag, cssVar, declaresCssVariable, pinBodyStyleNumbers, stylePropNumberUnit } from "./projections.js";
13
- import { isAuthoringPhantom, phantomsIn } from "./utils/authoring-phantom.js";
14
13
  import { addressOf, editOp, inputOf, opVerb } from "./config-op.js";
14
+ import { isAuthoringPhantom, phantomsIn } from "./utils/authoring-phantom.js";
15
15
  import { configSchemas } from "./config-ops.js";
16
16
  import { isAuthored } from "./authoring.js";
17
17
  import { Entity } from "./Entity.js";
@@ -6362,7 +6362,7 @@ var Config = class Config {
6362
6362
  * links from the provided sources. A declared `linkedKind` with no provided source throws. */
6363
6363
  hydrateFrom(stored, options) {
6364
6364
  const claimed = schemaVersionOf(stored);
6365
- if (claimed > 20261027205147) throw new SchemaVersionTooNew(claimed);
6365
+ if (claimed > 20261030134149) throw new SchemaVersionTooNew(claimed);
6366
6366
  const wire = upgradeSerializedConfig(stored);
6367
6367
  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.`);
6368
6368
  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 = 20261027205147;
18
+ declare const CURRENT_SCHEMA_VERSION = 20261030134149;
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 = 20261027205147;
16
+ const CURRENT_SCHEMA_VERSION = 20261030134149;
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 > 20261027205147) 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 > 20261030134149) 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 ?? 20261027205147;
105
+ const target = options?.target ?? 20261030134149;
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 ?? 20261027205147;
163
+ const target = options?.target ?? 20261030134149;
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 ?? 20261027205147;
182
+ const target = options?.target ?? 20261030134149;
183
183
  const entries = [];
184
184
  const held = [];
185
185
  const retired = [];
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ import { arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, clea
60
60
  import { ANATOMY_MARKER, MOTION_FACTORY, anatomyElementComponent, anatomyTextProp, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, routesOf, slotTargetsOf, specWithResolvedVisibility, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey } from "./framework/render-spec.js";
61
61
  import { canvasCellAt, canvasDefaultCoordinate, cellAxes } from "./spec/cell.js";
62
62
  import { instanceHolding } from "./spec/instance.js";
63
+ import { addressOf, editOp, inputOf, opOf, opVerb } from "./framework/config-op.js";
63
64
  import { componentSourceDerivedFields } from "./entities/system/component-source-fields.js";
64
65
  import { styleSetEdits } from "./entities/system/component-style-set.js";
65
66
  import { convertLinkedTokenValue, prepareLinkedTokenBody } from "./entities/system/linked-token-values.js";
@@ -73,7 +74,6 @@ import { glyphExportNames, glyphType, iconCategories, iconLibraries, iconLibrary
73
74
  import { glyphSourcesOf, iconResolver, nameIconsInTree } from "./capture/icon-resolver.js";
74
75
  import { PLACED_NODE_KEY, specToNodePatches } from "./entities/canvas/node-patches.js";
75
76
  import { Device } from "./entities/system/Device.js";
76
- import { addressOf, editOp, inputOf, opOf, opVerb } from "./framework/config-op.js";
77
77
  import { renderSignature, signatureOf } from "./framework/signature.js";
78
78
  import { authoringSignatures } from "./framework/config-ops.js";
79
79
  import { defineDerivedEntity } from "./framework/defineDerivedEntity.js";
@@ -0,0 +1,7 @@
1
+ /** `component/map-props` is a new op, with `component/map-props-plan` beside it: a mapping of a
2
+ * component's old props onto its new ones, applied to the documents of whichever host runs it. It
3
+ * writes no patch, so no stored config or draft row carries it, and this is an identity hop. Retire
4
+ * it only when no supported artifact predates this schema. */
5
+ import type { SchemaMigration } from '../framework/schema-version';
6
+ export declare const COMPONENT_MAP_PROPS_VERSION = 20261030134149;
7
+ export declare const componentMapPropsMigration: SchemaMigration;
@@ -0,0 +1,8 @@
1
+ //#region src/migrations/20261030134149_component_map_props.ts
2
+ const COMPONENT_MAP_PROPS_VERSION = 20261030134149;
3
+ const componentMapPropsMigration = {
4
+ version: COMPONENT_MAP_PROPS_VERSION,
5
+ up: (json) => json
6
+ };
7
+ //#endregion
8
+ export { COMPONENT_MAP_PROPS_VERSION, componentMapPropsMigration };
@@ -7,7 +7,7 @@ import { AcceptsChildNodes, ChildNodesClassification, ClassifyChildNodes, buildA
7
7
  import { CollapseTextLabelsResult, CollapsedTextLabel, collapseWrappedTextLabels } from "./collapse-text-labels.js";
8
8
  import { GlyphJsxForm, GlyphNodeResolver, glyphJsxForm, glyphMemberOf, jsxIdentifier, parseGlyphType } from "./glyph-jsx.js";
9
9
  import { JsxToSpecOptions, UNRESOLVED, hasUnresolvedProps, jsxToSpec, stripUnresolvedProps } from "./jsxToSpec.js";
10
- import { MatchedValue, PropMapping, PropMappingConflict, PropMappingResult, PropMappingRule, applyPropMapping, propMappingErrors } from "./prop-mapping.js";
10
+ import { MappedInstance, MappedProps, MatchedValue, PropMapping, PropMappingConflict, PropMappingResult, PropMappingRule, applyPropMapping, mapComponentProps, onlyMapsProps, propMappingErrors, propMappingRuleSchema, propMappingRulesSchema, propMappingsIn } from "./prop-mapping.js";
11
11
  import { SpecToJsxOptions, SpecToJsxResult, specToJsx } from "./specToJsx.js";
12
12
 
13
13
  //#region src/spec/index.d.ts
@@ -29,4 +29,4 @@ import { SpecToJsxOptions, SpecToJsxResult, specToJsx } from "./specToJsx.js";
29
29
  * element, without changing the rendered layer hierarchy. */
30
30
  declare const SYNTHETIC_TEXT_SLOT_PROP = "data-uds-synthetic-text-slot";
31
31
  //#endregion
32
- export { type AcceptsChildNodes, type Cell, type CellSlot, type ChildNodesClassification, type ClassifyChildNodes, type CollapseTextLabelsResult, type CollapsedTextLabel, type DocumentRename, type GlyphJsxForm, type GlyphNodeResolver, type GraftableElement, type GraftableSpec, type JsxToSpecOptions, type MatchedValue, type PreviewElement, type PreviewRef, type PreviewSpecNode, type PreviewValue, type PropMapping, type PropMappingConflict, type PropMappingResult, type PropMappingRule, type RewritableElement, type RewritableSpec, SYNTHETIC_TEXT_SLOT_PROP, type Slot, type SlotCell, type SpecRewrite, type SpecToJsxOptions, type SpecToJsxResult, UNRESOLVED, applyForcedModifiers, applyPropMapping, buildAcceptsChildNodes, buildClassifyChildNodes, collapseWrappedTextLabels, collectForcedModifierProps, composeRenames, documentRenamesFromPatches, documentRenamesFromSource, glyphJsxForm, glyphMemberOf, graftPreviewElements, graftSlotContent, hasPreviewElementDeep, hasUnresolvedProps, isComponentRef, isPreviewElement, isPreviewRef, jsxIdentifier, jsxToSpec, parseGlyphType, previewElementNodes, previewElementType, propMappingErrors, rewriteSpecRefs, specToJsx, stripInjectedSlots, stripUnresolvedProps, styleNumberReadingRenames, withContainerChildSlots, withEmptyNodeSlots, withRenderSlots };
32
+ export { type AcceptsChildNodes, type Cell, type CellSlot, type ChildNodesClassification, type ClassifyChildNodes, type CollapseTextLabelsResult, type CollapsedTextLabel, type DocumentRename, type GlyphJsxForm, type GlyphNodeResolver, type GraftableElement, type GraftableSpec, type JsxToSpecOptions, type MappedInstance, type MappedProps, type MatchedValue, type PreviewElement, type PreviewRef, type PreviewSpecNode, type PreviewValue, type PropMapping, type PropMappingConflict, type PropMappingResult, type PropMappingRule, type RewritableElement, type RewritableSpec, SYNTHETIC_TEXT_SLOT_PROP, type Slot, type SlotCell, type SpecRewrite, type SpecToJsxOptions, type SpecToJsxResult, UNRESOLVED, applyForcedModifiers, applyPropMapping, buildAcceptsChildNodes, buildClassifyChildNodes, collapseWrappedTextLabels, collectForcedModifierProps, composeRenames, documentRenamesFromPatches, documentRenamesFromSource, glyphJsxForm, glyphMemberOf, graftPreviewElements, graftSlotContent, hasPreviewElementDeep, hasUnresolvedProps, isComponentRef, isPreviewElement, isPreviewRef, jsxIdentifier, jsxToSpec, mapComponentProps, onlyMapsProps, parseGlyphType, previewElementNodes, previewElementType, propMappingErrors, propMappingRuleSchema, propMappingRulesSchema, propMappingsIn, rewriteSpecRefs, specToJsx, stripInjectedSlots, stripUnresolvedProps, styleNumberReadingRenames, withContainerChildSlots, withEmptyNodeSlots, withRenderSlots };
@@ -1,11 +1,11 @@
1
1
  import { graftPreviewElements, graftSlotContent, hasPreviewElementDeep, isComponentRef, isPreviewElement, isPreviewRef, previewElementNodes, previewElementType } from "./preview-elements.js";
2
+ import { composeRenames, documentRenamesFromPatches, documentRenamesFromSource, rewriteSpecRefs, styleNumberReadingRenames } from "./rewrite-refs.js";
3
+ import { applyPropMapping, mapComponentProps, onlyMapsProps, propMappingErrors, propMappingRuleSchema, propMappingRulesSchema, propMappingsIn } from "./prop-mapping.js";
2
4
  import { glyphJsxForm, glyphMemberOf, jsxIdentifier, parseGlyphType } from "./glyph-jsx.js";
3
5
  import { applyForcedModifiers, collectForcedModifierProps } from "./apply-forced-modifiers.js";
4
6
  import { collapseWrappedTextLabels } from "./collapse-text-labels.js";
5
7
  import { buildAcceptsChildNodes, buildClassifyChildNodes, stripInjectedSlots, withContainerChildSlots, withEmptyNodeSlots, withRenderSlots } from "./empty-node-slots.js";
6
8
  import { UNRESOLVED, hasUnresolvedProps, jsxToSpec, stripUnresolvedProps } from "./jsxToSpec.js";
7
- import { composeRenames, documentRenamesFromPatches, documentRenamesFromSource, rewriteSpecRefs, styleNumberReadingRenames } from "./rewrite-refs.js";
8
- import { applyPropMapping, propMappingErrors } from "./prop-mapping.js";
9
9
  import { specToJsx } from "./specToJsx.js";
10
10
  //#region src/spec/index.ts
11
11
  /**
@@ -26,4 +26,4 @@ import { specToJsx } from "./specToJsx.js";
26
26
  * element, without changing the rendered layer hierarchy. */
27
27
  const SYNTHETIC_TEXT_SLOT_PROP = "data-uds-synthetic-text-slot";
28
28
  //#endregion
29
- export { SYNTHETIC_TEXT_SLOT_PROP, UNRESOLVED, applyForcedModifiers, applyPropMapping, buildAcceptsChildNodes, buildClassifyChildNodes, collapseWrappedTextLabels, collectForcedModifierProps, composeRenames, documentRenamesFromPatches, documentRenamesFromSource, glyphJsxForm, glyphMemberOf, graftPreviewElements, graftSlotContent, hasPreviewElementDeep, hasUnresolvedProps, isComponentRef, isPreviewElement, isPreviewRef, jsxIdentifier, jsxToSpec, parseGlyphType, previewElementNodes, previewElementType, propMappingErrors, rewriteSpecRefs, specToJsx, stripInjectedSlots, stripUnresolvedProps, styleNumberReadingRenames, withContainerChildSlots, withEmptyNodeSlots, withRenderSlots };
29
+ export { SYNTHETIC_TEXT_SLOT_PROP, UNRESOLVED, applyForcedModifiers, applyPropMapping, buildAcceptsChildNodes, buildClassifyChildNodes, collapseWrappedTextLabels, collectForcedModifierProps, composeRenames, documentRenamesFromPatches, documentRenamesFromSource, glyphJsxForm, glyphMemberOf, graftPreviewElements, graftSlotContent, hasPreviewElementDeep, hasUnresolvedProps, isComponentRef, isPreviewElement, isPreviewRef, jsxIdentifier, jsxToSpec, mapComponentProps, onlyMapsProps, parseGlyphType, previewElementNodes, previewElementType, propMappingErrors, propMappingRuleSchema, propMappingRulesSchema, propMappingsIn, rewriteSpecRefs, specToJsx, stripInjectedSlots, stripUnresolvedProps, styleNumberReadingRenames, withContainerChildSlots, withEmptyNodeSlots, withRenderSlots };
@@ -1,5 +1,7 @@
1
+ import { ConfigEdit } from "../framework/config-op.js";
1
2
  import { Config } from "../framework/Config.js";
2
3
  import { RewritableSpec } from "./rewrite-refs.js";
4
+ import { z } from "zod";
3
5
 
4
6
  //#region src/spec/prop-mapping.d.ts
5
7
  /** A stored value a rule can match: what a design file holds for a variant, string or flag. */
@@ -17,6 +19,31 @@ interface PropMapping {
17
19
  readonly component: string;
18
20
  readonly rules: readonly PropMappingRule[];
19
21
  }
22
+ /** A rule as a tool takes it: the shape every surface that applies a mapping parses its input with. */
23
+ declare const propMappingRuleSchema: z.ZodObject<{
24
+ when: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
25
+ set: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
26
+ unset: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
+ }, z.core.$strip>;
28
+ /** The rules of a mapping, as a tool takes them. */
29
+ declare const propMappingRulesSchema: z.ZodArray<z.ZodObject<{
30
+ when: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
31
+ set: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
32
+ unset: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ }, z.core.$strip>>;
34
+ /**
35
+ * The mappings a batch of edits carries, in order: each `component/map-props` op as the mapping it
36
+ * applies. The op changes nothing in the config, so the host that ran the batch applies these to the
37
+ * documents it holds once the batch lands: Create to a branch's design files, a checkout to its call
38
+ * sites. An op whose rules don't parse is left out; running the batch refused it already.
39
+ */
40
+ declare function propMappingsIn(edits: readonly ConfigEdit[]): PropMapping[];
41
+ /**
42
+ * Whether a batch is mappings alone: every edit a `component/map-props` op. Such a batch changes
43
+ * nothing in the config, so a read-only branch (`main`, a published version) still runs it: its
44
+ * design files take edits, and those are all it moves.
45
+ */
46
+ declare function onlyMapsProps(edits: readonly ConfigEdit[]): boolean;
20
47
  /** A bag two matching rules wrote different values into, left as it was. */
21
48
  interface PropMappingConflict {
22
49
  readonly element: string;
@@ -45,5 +72,26 @@ declare function propMappingErrors(config: Config, mapping: PropMapping): string
45
72
  * first: this applies what it's given.
46
73
  */
47
74
  declare function applyPropMapping(config: Config, spec: RewritableSpec, mapping: PropMapping): PropMappingResult;
75
+ /** One instance's props with a mapping tried on it. */
76
+ interface MappedInstance {
77
+ readonly props: Record<string, unknown>;
78
+ readonly changed: boolean;
79
+ /** The props two matching rules wrote different values to, which leave the instance as it was. */
80
+ readonly conflicts: readonly string[];
81
+ }
82
+ type MappedProps = {
83
+ readonly ok: false;
84
+ readonly errors: readonly string[];
85
+ } | {
86
+ readonly ok: true;
87
+ readonly instances: readonly MappedInstance[];
88
+ };
89
+ /**
90
+ * A mapping tried on some instances' props, writing nothing: the `component/map-props` op. Refused,
91
+ * with each reason, when the component can't take what it writes. Each bag is mapped as a design
92
+ * file's instance is, modifier blocks and layer overrides included, so what it returns is what
93
+ * applying the mapping to a document or a call site would store.
94
+ */
95
+ declare function mapComponentProps(config: Config, mapping: PropMapping, instances: readonly Record<string, unknown>[]): MappedProps;
48
96
  //#endregion
49
- export { MatchedValue, PropMapping, PropMappingConflict, PropMappingResult, PropMappingRule, applyPropMapping, propMappingErrors };
97
+ export { MappedInstance, MappedProps, MatchedValue, PropMapping, PropMappingConflict, PropMappingResult, PropMappingRule, applyPropMapping, mapComponentProps, onlyMapsProps, propMappingErrors, propMappingRuleSchema, propMappingRulesSchema, propMappingsIn };
@@ -1,9 +1,67 @@
1
1
  import { kindOf, pathOf } from "../framework/utils/refs.js";
2
2
  import { propValueDomain, resolveComponentProps } from "../framework/projections.js";
3
3
  import { surfaceProp } from "../framework/prop-surface.js";
4
+ import { editOp } from "../framework/config-op.js";
4
5
  import { layerComponent, modifierBlock, nestedBag } from "./rewrite-refs.js";
6
+ import { z } from "zod";
5
7
  //#region src/spec/prop-mapping.ts
6
8
  /**
9
+ * Moving what instances of a component stored onto its new API: `variant: 'brand-secondary'` →
10
+ * `color: 'brand', prominence: 'outline'`, `size: 'xs'` → `size: 'sm'`.
11
+ *
12
+ * A rename carries a document across a change with one answer per name, so it can be recorded and
13
+ * replayed (`rewrite-refs.ts`). A reshaped API has no one answer: whether `brand-secondary` is an
14
+ * outline or a ghost is a design decision, so the mapping comes from the person (with an agent
15
+ * proposing it) and is applied here once. Nothing stores it.
16
+ *
17
+ * A mapping is a list of rules for one component. Every rule's `when` is matched against the bag
18
+ * as it was stored, and every rule that matches applies, so `variant` and `size` can be mapped by
19
+ * separate rules and a pair of them by one rule naming both. A rule removes only what its `unset`
20
+ * names: a prop a rule matches on may be one the new API still has.
21
+ *
22
+ * Positions follow the rename rewriter's: an element's own props, every modifier block nested in
23
+ * them (`_hover: { variant: … }` is the same element's props under a condition), and each
24
+ * `layerProps` bag whose layer renders the component.
25
+ */
26
+ /** A rule as a tool takes it: the shape every surface that applies a mapping parses its input with. */
27
+ const propMappingRuleSchema = z.object({
28
+ when: z.record(z.string(), z.union([
29
+ z.string(),
30
+ z.number(),
31
+ z.boolean()
32
+ ])).describe("The stored values an instance must hold for the rule to apply, every one of them: { \"variant\": \"brand-secondary\" }."),
33
+ set: z.record(z.string(), z.unknown()).optional().describe("The props to write: { \"color\": \"brand\", \"prominence\": \"outline\" }."),
34
+ unset: z.array(z.string()).optional().describe("The props to remove. A prop the rule matches on stays unless it is named here, so a removed prop has to be.")
35
+ });
36
+ /** The rules of a mapping, as a tool takes them. */
37
+ const propMappingRulesSchema = z.array(propMappingRuleSchema).min(1).describe("Every rule whose `when` matches an instance as stored applies to it, so separate props are mapped by separate rules, and a combination by one rule naming both.");
38
+ /**
39
+ * The mappings a batch of edits carries, in order: each `component/map-props` op as the mapping it
40
+ * applies. The op changes nothing in the config, so the host that ran the batch applies these to the
41
+ * documents it holds once the batch lands: Create to a branch's design files, a checkout to its call
42
+ * sites. An op whose rules don't parse is left out; running the batch refused it already.
43
+ */
44
+ function propMappingsIn(edits) {
45
+ return edits.flatMap((edit) => {
46
+ const { op } = editOp(edit);
47
+ if (op.kind !== "component" || op.operation !== "map-props") return [];
48
+ const rules = propMappingRulesSchema.safeParse(op.data?.rules);
49
+ if (!rules.success || typeof op.path !== "string") return [];
50
+ return [{
51
+ component: op.path.replace(/^component:/, ""),
52
+ rules: rules.data
53
+ }];
54
+ });
55
+ }
56
+ /**
57
+ * Whether a batch is mappings alone: every edit a `component/map-props` op. Such a batch changes
58
+ * nothing in the config, so a read-only branch (`main`, a published version) still runs it: its
59
+ * design files take edits, and those are all it moves.
60
+ */
61
+ function onlyMapsProps(edits) {
62
+ return edits.length > 0 && propMappingsIn(edits).length === edits.length;
63
+ }
64
+ /**
7
65
  * Why a prop can't hold `value`, or `undefined` when it can. A variant's or a slot's values come off
8
66
  * its surface, which follows a forward to the prop it routes to. Any other prop with a domain (a
9
67
  * composite, a style property) answers through its declaration, as validation does. A bracketed
@@ -165,5 +223,33 @@ function applyPropMapping(config, spec, mapping) {
165
223
  conflicts
166
224
  };
167
225
  }
226
+ /**
227
+ * A mapping tried on some instances' props, writing nothing: the `component/map-props` op. Refused,
228
+ * with each reason, when the component can't take what it writes. Each bag is mapped as a design
229
+ * file's instance is, modifier blocks and layer overrides included, so what it returns is what
230
+ * applying the mapping to a document or a call site would store.
231
+ */
232
+ function mapComponentProps(config, mapping, instances) {
233
+ const errors = propMappingErrors(config, mapping);
234
+ if (errors.length > 0) return {
235
+ ok: false,
236
+ errors
237
+ };
238
+ const { spec, conflicts } = applyPropMapping(config, { elements: Object.fromEntries(instances.map((props, i) => [String(i), {
239
+ type: `component:${mapping.component}`,
240
+ props
241
+ }])) }, mapping);
242
+ return {
243
+ ok: true,
244
+ instances: instances.map((props, i) => {
245
+ const after = spec.elements?.[String(i)]?.props ?? props;
246
+ return {
247
+ props: after,
248
+ changed: after !== props,
249
+ conflicts: conflicts.filter((conflict) => conflict.element === String(i)).flatMap((conflict) => conflict.props)
250
+ };
251
+ })
252
+ };
253
+ }
168
254
  //#endregion
169
- export { applyPropMapping, propMappingErrors };
255
+ export { applyPropMapping, mapComponentProps, onlyMapsProps, propMappingErrors, propMappingRuleSchema, propMappingRulesSchema, propMappingsIn };