@yahoo/uds-create-config 5.59.0 → 5.59.2

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.
@@ -1004,4 +1004,4 @@ interface ComponentClass extends Omit<typeof ComponentEntity, 'ref' | 'item'> {
1004
1004
  instance: typeof instanceAt;
1005
1005
  }
1006
1006
  //#endregion
1007
- export { AuthoredPropBody, Component, ComponentBody, ComponentClass, ComponentCreateInput, ComponentElementBody, ComponentItemInput, ComponentPropBody, ComponentReference, ComponentStyleBody, ContractStyleRules, ElementVisibility, ElementVisibilityInput, ElementVisibilitySchema, ExtendedComponentRef, ProjectedVisibility, PropShapeSchema, SLOT_CODE_FORMS, VariantValueBody, VariantValues, blankComponentBody, canonicalWhen, forwardedNames, layerRef, ownValues, ownsItsValues, valueRef };
1007
+ export { AnatomyNodeClass, AuthoredPropBody, Component, ComponentBody, ComponentClass, ComponentCreateInput, ComponentElementBody, ComponentItemInput, ComponentPropBody, ComponentReference, ComponentStyleBody, ContractStyleRules, ElementVisibility, ElementVisibilityInput, ElementVisibilitySchema, ExtendedComponentRef, ForwardClass, LayerClass, ProjectedVisibility, PropClass, PropShapeSchema, SLOT_CODE_FORMS, VariantValueBody, VariantValues, blankComponentBody, canonicalWhen, forwardedNames, layerRef, ownValues, ownsItsValues, valueRef };
@@ -303,8 +303,8 @@ const LinkedSystemDefinition = defineEntity({
303
303
  * Draw entities from a linked system: the pin's `link` declaration grows to cover them and the
304
304
  * build mints a pointer row per entity, in this one op. A row of this config's own at a covered
305
305
  * path is deleted so theirs shows through; a row held through another pin becomes an extension
306
- * through this one when `onLinked` is `'take'`. `link-plan` says what that replaces before anything
307
- * is written.
306
+ * through this one when `onLinked` is `'take'`, and the other pin's `options` entry there goes with
307
+ * it. `link-plan` says what that replaces before anything is written.
308
308
  */
309
309
  link: {
310
310
  input: linkInput,
@@ -332,7 +332,7 @@ const LinkedSystemDefinition = defineEntity({
332
332
  input: linkInput,
333
333
  readOnly: true,
334
334
  label: "Plan a link",
335
- description: "What `link` with the same input replaces — rows of yours deleted so the source shows through, every path another linked system provides (`linked`, turned into extensions when `onLinked` says take), members left alone because they are already here from the same origin, paths another linked system declares that the link reaches as a different entity (`settled`), whose row stays, and why the config would refuse the link (`blocked`), a collision it would leave among them. Writes nothing.",
335
+ description: "What `link` with the same input replaces — rows of yours deleted so the source shows through, every path another linked system provides (`linked`, turned into extensions when `onLinked` says take), the `options` entries another linked system holds at what a take moves, which it removes since they would apply to nothing (`dropped`), members left alone because they are already here from the same origin, paths another linked system declares that the link reaches as a different entity (`settled`), whose row stays, and why the config would refuse the link (`blocked`), a collision it would leave among them. Writes nothing.",
336
336
  scope: "item",
337
337
  title: (entity) => `Plan a link from ${entity}`,
338
338
  example: linkExample,
@@ -33,7 +33,7 @@ declare const inlineJsMotion: z.ZodObject<{
33
33
  /** The fields of a JS preset that `motion.create` reads as props, in schema order. The emitter
34
34
  * reads this rather than restating the schema, so a field added here reaches every emitted preset. */
35
35
  declare const jsMotionPropFields: readonly string[];
36
- declare const Motion: EntityClass<z.ZodDiscriminatedUnion<[z.ZodObject<{
36
+ declare const MotionEntity: EntityClass<z.ZodDiscriminatedUnion<[z.ZodObject<{
37
37
  type: z.ZodLiteral<"react-native-reanimated">;
38
38
  value: z.ZodUnion<readonly [z.ZodObject<{
39
39
  transitionProperty: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
@@ -1003,6 +1003,7 @@ declare const Motion: EntityClass<z.ZodDiscriminatedUnion<[z.ZodObject<{
1003
1003
  }, z.core.$strip>;
1004
1004
  };
1005
1005
  }, Record<never, never>, false, Record<never, never>, Record<never, never>>;
1006
- type MotionClass = Named<typeof Motion>;
1006
+ interface MotionClass extends Named<typeof MotionEntity> {}
1007
+ declare const Motion: MotionClass;
1007
1008
  //#endregion
1008
1009
  export { Motion, MotionClass, inlineJsMotion, jsMotionPropFields };
@@ -1,7 +1,9 @@
1
1
  import { Ref as Ref$1 } from "../../framework/utils/refs.js";
2
2
  import { RegisteredComponentHandles, RegisteredGroupPaths, RegisteredModifiers, RegisteredPaths, RegisteredStyleProps } from "../../framework/registered.js";
3
3
  import { Authored } from "../../framework/authoring.js";
4
+ import { KindRef } from "../../framework/defineEntity.js";
4
5
  import { StyleValue } from "./style-bag.js";
6
+ import { StylePropertyClass } from "./StyleProperty.js";
5
7
  import { ComponentSourceInput } from "./component-source-fields.js";
6
8
  import { PropsOrRules } from "./prop-rules.js";
7
9
  import { VariantValues } from "./Component.js";
@@ -725,6 +727,30 @@ type RenderArgs<L extends LayerMap, P extends PropMap, S extends StateMap = Reco
725
727
  } & ([keyof S] extends [never] ? Record<never, never> : {
726
728
  readonly states: StatesHelper<S, L>;
727
729
  }) & DeclaredProps<P, L, unknown, 'render'> & Record<string, unknown>;
730
+ /** A prop map's entries that are the style property of their own name: `bg: StyleProperty.ref('bg')`. */
731
+ type StyleRefs<K extends string> = { readonly [Q in K]: KindRef<StylePropertyClass, 'style-property', Q> };
732
+ /** The same entries in their declared form: `bg: { type: 'styleProperty', value: StyleProperty.ref('bg') }`. */
733
+ type StyleRefProps<K extends string> = { readonly [Q in K]: {
734
+ readonly type: 'styleProperty';
735
+ readonly value: KindRef<StylePropertyClass, 'style-property', Q>;
736
+ } };
737
+ /** The keys of `P` whose entry is exactly the one `M` gives that key. */
738
+ type MatchingKeys<P, M> = { [K in keyof P]: K extends keyof M ? [P[K]] extends [M[K]] ? [M[K]] extends [P[K]] ? K : never : never : never }[keyof P];
739
+ type StyleRefKeys<P> = MatchingKeys<P, StyleRefs<keyof P & string>>;
740
+ type StyleRefPropKeys<P> = MatchingKeys<P, StyleRefProps<keyof P & string>>;
741
+ /** `T`, or nothing to intersect when `K` is empty. */
742
+ type WhenAny<K, T> = [K] extends [never] ? unknown : T;
743
+ /**
744
+ * `P` with its entries that are the style property of their own name gathered into a
745
+ * {@link StyleRefs} and a {@link StyleRefProps}. It's the same type, but a declaration writes it as a
746
+ * list of key names rather than a `KindRef` per entry. A component's type holds the prop map of every
747
+ * layer it reaches, so a system's Box, with an entry for each style property, is otherwise restated in
748
+ * full at every layer that renders it, until the declaration is too long to write (TS7056).
749
+ *
750
+ * The rest of `P` is a mapped type with `as` rather than an `Omit`, which a declaration would write
751
+ * with `P` in full as its type argument.
752
+ */
753
+ type CompactProps<P extends PropMap> = [StyleRefKeys<P> | StyleRefPropKeys<P>] extends [never] ? P : { [K in keyof P as K extends StyleRefKeys<P> | StyleRefPropKeys<P> ? never : K]: P[K] } & WhenAny<StyleRefKeys<P>, StyleRefs<StyleRefKeys<P> & string>> & WhenAny<StyleRefPropKeys<P>, StyleRefProps<StyleRefPropKeys<P> & string>>;
728
754
  interface ComponentBuilder<L extends LayerMap, P extends PropMap,
729
755
  /** Props the RENDER takes that the config doesn't describe — React handlers, mostly (`onPress`).
730
756
  * They're part of the component's surface but not of its data, which is why they're declared at
@@ -767,7 +793,7 @@ F extends string = never, /** The states `.states()` declared — what the rende
767
793
  * provide. The callback and its `layer`/`bind` helpers are gone — they only ever split a path string
768
794
  * that the parameter's own type now checks.
769
795
  */
770
- props<const P2 extends PropMap<LayerPath<L>>>(props: P2 & NoInfer<{ [K in keyof P2]: Exact<P2[K], AuthoredProp<LayerPath<L>>> }>): ComponentBuilder<L, P2, E, D, F, S>;
796
+ props<const P2 extends PropMap<LayerPath<L>>>(props: P2 & NoInfer<{ [K in keyof P2]: Exact<P2[K], AuthoredProp<LayerPath<L>>> }>): ComponentBuilder<L, CompactProps<P2>, E, D, F, S>;
771
797
  /**
772
798
  * What the render knows that a style rule keys on — the field has a value, the image loaded, the
773
799
  * indicator is sliding: `{ filled: { type: 'boolean' } }`, `{ phase: { type: 'variant', value:
@@ -873,9 +899,10 @@ type AuthoredComponent = ComponentBuilder<LayerMap, PropMap, unknown, unknown>;
873
899
  * forward through it (HStack's `root: Box`) then offers what the linked Box takes.
874
900
  *
875
901
  * Everything else (the render's extras, the defaults, the forwards, the states) is read off the
876
- * handle's type rather than restated, so the two can't drift.
902
+ * handle's type rather than restated, so the two can't drift. The laid-over props are compacted
903
+ * again, since laying them over spells the source's {@link StyleRefs} out entry by entry.
877
904
  */
878
- type LinkedHandle<Handle, Props extends PropMap = Record<never, never>, Layers extends Record<string, LayerElement> = Record<never, never>> = Handle extends ComponentBuilder<infer L extends LayerMap, infer P extends PropMap, infer E, infer D, infer F extends string, infer S extends StateMap> ? ComponentBuilder<Without<L, keyof Layers> & Layers extends infer Swapped extends LayerMap ? Swapped : L, Without<P, keyof Props> & Props, E, D, F, S> : Handle;
905
+ type LinkedHandle<Handle, Props extends PropMap = Record<never, never>, Layers extends Record<string, LayerElement> = Record<never, never>> = Handle extends ComponentBuilder<infer L extends LayerMap, infer P extends PropMap, infer E, infer D, infer F extends string, infer S extends StateMap> ? ComponentBuilder<Without<L, keyof Layers> & Layers extends infer Swapped extends LayerMap ? Swapped : L, CompactProps<Without<P, keyof Props> & Props>, E, D, F, S> : Handle;
879
906
  interface DefineComponent {
880
907
  /** The one-layer form: the element IS the component. */
881
908
  <const El extends LayerElement>(element: El): ComponentBuilder<{
@@ -894,7 +921,7 @@ interface DefineComponent {
894
921
  * The type argument is explicit so the builder does not INFER `P2` from an already-exact value: it
895
922
  * would then apply the guard over its own output, which nothing satisfies.
896
923
  */
897
- declare function definePropsFirst<const P extends PropMap<LayerPath>>(props: P & NoInfer<{ [K in keyof P]: Exact<P[K], AuthoredProp<LayerPath>> }>): ComponentBuilder<LayerMap, P>;
924
+ declare function definePropsFirst<const P extends PropMap<LayerPath>>(props: P & NoInfer<{ [K in keyof P]: Exact<P[K], AuthoredProp<LayerPath>> }>): ComponentBuilder<LayerMap, CompactProps<P>>;
898
925
  declare const defineComponent: DefineComponent;
899
926
  //#endregion
900
- export { AsInput, AuthoredComponent, AuthoredDefaultsOf, AuthoredProp, ComponentBuilder, ComponentPropsOf, GlyphComponent, GlyphValue, LayerElement, LayerMap, LinkedHandle, ModifierProps, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, SlotCodeForm, SlotDecl, StateMap, StyleProps, StyleRuleInput, UnconditionalDefaults, defineComponent };
927
+ export { AsInput, AuthoredComponent, AuthoredDefaultsOf, AuthoredProp, ComponentBuilder, ComponentPropsOf, GlyphComponent, GlyphValue, LayerElement, LayerMap, LinkedHandle, ModifierProps, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, SlotCodeForm, SlotDecl, StateMap, StyleProps, StyleRefProps, StyleRefs, StyleRuleInput, UnconditionalDefaults, defineComponent };
@@ -22,6 +22,13 @@ interface LinkedElsewhere extends EntityAddress {
22
22
  /** The slug of the pin it comes through. */
23
23
  readonly from: string;
24
24
  }
25
+ /** An entry in another pin's `options` that a take leaves applying to nothing, and removes. */
26
+ interface DroppedDirective extends EntityAddress {
27
+ /** The slug of the pin whose entry it was. */
28
+ readonly from: string;
29
+ /** The accessor the entry sits under in that pin's `options`. */
30
+ readonly accessor: string;
31
+ }
25
32
  /** One row a link mints. `named` when a path the call named covers it; otherwise it comes along
26
33
  * because something linked points at it. */
27
34
  interface LinkArrival extends EntityAddress {
@@ -40,6 +47,10 @@ interface LinkPlan {
40
47
  readonly linked: readonly LinkedElsewhere[];
41
48
  /** Rows held through another pin, turned into extensions through this one. */
42
49
  readonly extended: readonly EntityAddress[];
50
+ /** The other pins' `options` entries at what the take moves, removed with it: they adjust only the
51
+ * rows their pin derives, and the row there follows this one now. Not carried onto the taken row,
52
+ * since an entry corrects what that pin brings rather than stating a value of this config's own. */
53
+ readonly dropped: readonly DroppedDirective[];
43
54
  /** Members already here from the source's own origin, left alone and kept out of the declaration. */
44
55
  readonly shared: readonly EntityAddress[];
45
56
  /** Paths another pin offers that a `hybrid` link reaches as a different entity. The other pin's
@@ -193,4 +204,4 @@ declare function sourceRowStates({
193
204
  readonly slug: string;
194
205
  }): SourceRowState[];
195
206
  //#endregion
196
- export { BreakOutcome, BreakPlan, BreakPlanInput, BreakPlanRow, BreakReach, LinkArrival, LinkPaths, LinkPlan, LinkPlanInput, LinkedElsewhere, OnLinked, SourceRowState, UnlinkPlan, UnlinkPlanInput, planBreak, planLink, planUnlink, sourceRowStates };
207
+ export { BreakOutcome, BreakPlan, BreakPlanInput, BreakPlanRow, BreakReach, DroppedDirective, LinkArrival, LinkPaths, LinkPlan, LinkPlanInput, LinkedElsewhere, OnLinked, SourceRowState, UnlinkPlan, UnlinkPlanInput, planBreak, planLink, planUnlink, sourceRowStates };
@@ -1,7 +1,7 @@
1
1
  import { collectRefs, isMintedRow, ref, sourceOf, splitRef } from "../../framework/utils/refs.js";
2
2
  import { isConfigRejection, rejectData } from "../../framework/rejection.js";
3
3
  import { blockedBy } from "../../framework/plan.js";
4
- import { excludedCondition } from "../../framework/source-integrity.js";
4
+ import { danglingDirectives, excludedCondition } from "../../framework/source-integrity.js";
5
5
  import { sameOrigin } from "../../framework/Config.js";
6
6
  import { PIN_KIND, covers, declarationOf, entriesFor, excludePaths, excludes, includePaths, narrowDeclaration } from "./link-declaration.js";
7
7
  import { planCopy, sourceEntries } from "./copy-plan.js";
@@ -238,11 +238,20 @@ function planLink({ config, input }) {
238
238
  }
239
239
  declared = Object.keys(next).length === 0 ? current : next;
240
240
  }
241
- const patches = [
241
+ const planned = [
242
242
  ...takes,
243
243
  ...declares,
244
244
  ...shadows
245
245
  ];
246
+ const { dropped, patches: drops } = takes.length > 0 ? directivesTakenFrom({
247
+ config,
248
+ slug,
249
+ patches: planned
250
+ }) : {
251
+ dropped: [],
252
+ patches: []
253
+ };
254
+ const patches = [...planned, ...drops];
246
255
  return {
247
256
  declared,
248
257
  ...outcome({
@@ -255,10 +264,74 @@ function planLink({ config, input }) {
255
264
  addresses,
256
265
  linked,
257
266
  extended,
267
+ dropped,
258
268
  shared,
259
269
  patches
260
270
  };
261
271
  }
272
+ /**
273
+ * The other pins' `options` entries a take leaves applying to nothing, and the pin edits that remove
274
+ * them.
275
+ *
276
+ * An entry adjusts the rows its pin derives. A take writes this config's own row at the path, one that
277
+ * follows `slug`, so the other pin's entry stays in its `options` reading as though it still applied,
278
+ * and the build refuses it ({@link danglingDirectives}). Read off the config the take leaves, so a
279
+ * folder whose members all moved counts the way the build will count it, and only what the take
280
+ * itself left behind: an entry that already applied to nothing is the build's to report, not this
281
+ * take's to remove.
282
+ *
283
+ * Nothing when the take is refused; `outcome` says why.
284
+ */
285
+ function directivesTakenFrom({ config, slug, patches }) {
286
+ let after;
287
+ try {
288
+ after = config.applyAll(patches).withDerivedRows();
289
+ } catch (error) {
290
+ if (!isConfigRejection(error)) throw error;
291
+ return {
292
+ dropped: [],
293
+ patches: []
294
+ };
295
+ }
296
+ const key = (at) => `${at.slug}\u0000${at.accessor}\u0000${at.path}`;
297
+ const already = new Set(danglingDirectives(config).map(key));
298
+ const dropped = [];
299
+ for (const at of danglingDirectives(after)) {
300
+ if (at.missing !== "row" || at.heldBy !== slug || already.has(key(at))) continue;
301
+ dropped.push({
302
+ kind: config.accessors()[at.accessor]?.kind ?? at.accessor,
303
+ path: at.path,
304
+ from: at.slug,
305
+ accessor: at.accessor
306
+ });
307
+ }
308
+ const edits = [];
309
+ for (const pin of new Set(dropped.map((at) => at.from))) {
310
+ const options = config.storedBody("linked-system", pin).options ?? {};
311
+ const data = {};
312
+ let emptied = 0;
313
+ for (const [accessor, entries] of Object.entries(options)) {
314
+ const going = dropped.filter((at) => at.from === pin && at.accessor === accessor).map((at) => at.path);
315
+ if (going.length === 0) continue;
316
+ if (Object.keys(entries ?? {}).filter((path) => !going.includes(path)).length > 0) {
317
+ for (const path of going) data[`options.${accessor}.${path}`] = null;
318
+ continue;
319
+ }
320
+ data[`options.${accessor}`] = null;
321
+ emptied += 1;
322
+ }
323
+ edits.push({
324
+ kind: PIN_KIND,
325
+ operation: "update",
326
+ path: pin,
327
+ data: emptied === Object.keys(options).length ? { options: null } : data
328
+ });
329
+ }
330
+ return {
331
+ dropped,
332
+ patches: edits
333
+ };
334
+ }
262
335
  /** What a plan reports of a refusal: its message, and the collisions when that's what it names. */
263
336
  function refusal(error) {
264
337
  const collisions = error.issues.flatMap((issue) => issue.collisions ?? []);
@@ -5138,6 +5138,40 @@ var Config = class Config {
5138
5138
  const folders = [];
5139
5139
  /** Folders this pass leaves as they stand — already held, over rows it still claims. */
5140
5140
  const standing = /* @__PURE__ */ new Set();
5141
+ const taken = [];
5142
+ for (const kind of this.ownedKinds()) for (const [path, stored] of this.state.snapshot.slice(kind).items) {
5143
+ const body = stored.toJSON();
5144
+ if (!isExtendedRef(body) || isMintedRow(body)) continue;
5145
+ if (pathOf(body) !== path) continue;
5146
+ const slug = sourceOf(body);
5147
+ if (slug !== void 0) taken.push({
5148
+ kind,
5149
+ path,
5150
+ slug
5151
+ });
5152
+ }
5153
+ /** Every folder a pin has a member under this pass, claimed or taken, as `slug kind path`. */
5154
+ const occupied = /* @__PURE__ */ new Set();
5155
+ const occupy = (slug, kind, path) => {
5156
+ const segments = path.split("/");
5157
+ for (let depth = 1; depth < segments.length; depth++) occupied.add(`${slug}\u0000${kind}\u0000${segments.slice(0, depth).join("/")}`);
5158
+ };
5159
+ for (const [target, slug] of claimedFrom) occupy(slug, kindOf(target), pathOf(target));
5160
+ for (const { kind, path, slug } of taken) occupy(slug, kind, path);
5161
+ /**
5162
+ * Whether a folder already here stays as it is. One this config authored does, and so does one
5163
+ * minted through a pin that still has a member under it. One minted through a pin that has none
5164
+ * left there goes to the pin its members follow now: after a take, a `colorMode` whose modes all
5165
+ * follow Yahoo OS kept pointing at the Bridge's, its fields read from a system none of it follows.
5166
+ */
5167
+ const stands = (kind, path) => {
5168
+ const held = this.state.snapshot.group(kind, path)?.toJSON();
5169
+ if (held === void 0) return false;
5170
+ if (!isMintedRow(held)) return true;
5171
+ const pointer = held.ref;
5172
+ const through = isRef(pointer) ? sourceOf(pointer) : void 0;
5173
+ return through !== void 0 && occupied.has(`${through}\u0000${kind}\u0000${path}`);
5174
+ };
5141
5175
  for (const [slug, paths] of offeredBy) {
5142
5176
  const source = this.sourceFor(slug);
5143
5177
  if (!source) continue;
@@ -5149,7 +5183,7 @@ var Config = class Config {
5149
5183
  for (const group of source.groups(kind)) {
5150
5184
  const prefix = `${group.path}/`;
5151
5185
  if (!under.some((path) => path.startsWith(prefix))) continue;
5152
- if (this.state.snapshot.group(kind, group.path)) {
5186
+ if (stands(kind, group.path)) {
5153
5187
  standing.add(`group:${kind}:${group.path}`);
5154
5188
  continue;
5155
5189
  }
@@ -5162,6 +5196,27 @@ var Config = class Config {
5162
5196
  }
5163
5197
  }
5164
5198
  }
5199
+ const derivedFolders = new Set(folders.map(({ kind, path }) => `${kind}:${path}`));
5200
+ for (const { kind, path, slug } of taken) {
5201
+ const source = this.sourceFor(slug);
5202
+ if (!source || !this.entityOf(kind)) continue;
5203
+ for (const group of source.groups(kind)) {
5204
+ if (!path.startsWith(`${group.path}/`)) continue;
5205
+ const target = `${kind}:${group.path}`;
5206
+ if (derivedFolders.has(target) || standing.has(`group:${target}`)) continue;
5207
+ if (stands(kind, group.path)) {
5208
+ standing.add(`group:${target}`);
5209
+ continue;
5210
+ }
5211
+ derivedFolders.add(target);
5212
+ folders.push({
5213
+ kind,
5214
+ path: group.path,
5215
+ slug
5216
+ });
5217
+ claimedFrom.set(target, slug);
5218
+ }
5219
+ }
5165
5220
  for (const [target, slug] of claimedFrom) {
5166
5221
  if (folders.some(({ kind, path }) => `${kind}:${path}` === target)) continue;
5167
5222
  changes.push(derivedItemChange(kindOf(target), pathOf(target), ref(kindOf(target), pathOf(target), slug).__ref, overlayBody({
@@ -5324,7 +5379,22 @@ var Config = class Config {
5324
5379
  const source = this.sourceFor(slug);
5325
5380
  if (!source) return [];
5326
5381
  const members = /* @__PURE__ */ new Set();
5327
- const walked = /* @__PURE__ */ new Set();
5382
+ /**
5383
+ * Every entity or folder visited, and whether a linked row reached it.
5384
+ *
5385
+ * A taken row is visited twice — first as an authored row, then as one its pin adopts — and only a
5386
+ * linked visit lands the items a body points at. Remembering only THAT a target was visited let the
5387
+ * unlinked pass claim the style properties and token folders it found, so the linked pass skipped
5388
+ * them and a mode their tokens vary under never got a row. A target reached unlinked is visited
5389
+ * again when a linked row reaches it; one reached linked is done.
5390
+ */
5391
+ const reached = /* @__PURE__ */ new Map();
5392
+ const reaching = (target, linked) => {
5393
+ const prior = reached.get(target);
5394
+ if (prior === true || prior === false && !linked) return false;
5395
+ reached.set(target, linked);
5396
+ return true;
5397
+ };
5328
5398
  /**
5329
5399
  * A style bag's KEYS name style properties, and a borrowed bag's name the SOURCE's.
5330
5400
  *
@@ -5342,11 +5412,11 @@ var Config = class Config {
5342
5412
  if (!sites) return;
5343
5413
  for (const { bag } of sites(body)) for (const prop of Object.keys(bag)) {
5344
5414
  const target = `style-property:${prop}`;
5345
- if (members.has(target)) continue;
5346
5415
  const local = this.storedBody("style-property", prop);
5347
5416
  if (local && !isMintedRow(local)) continue;
5348
5417
  const declared = source.resolve("style-property", prop);
5349
5418
  if (!declared) continue;
5419
+ if (!reaching(target, linked)) continue;
5350
5420
  members.add(target);
5351
5421
  visitEntity("style-property", declared.toJSON(), true, linked);
5352
5422
  }
@@ -5377,19 +5447,19 @@ var Config = class Config {
5377
5447
  const target = `${kind}:${path}`;
5378
5448
  const item = source.resolve(kind, path);
5379
5449
  if (item) {
5380
- if (!linked || members.has(target)) continue;
5450
+ if (!linked || !reaching(target, true)) continue;
5381
5451
  members.add(target);
5382
5452
  visitEntity(kind, item.toJSON(), true, true);
5383
5453
  continue;
5384
5454
  }
5385
- if (walked.has(target)) continue;
5386
- walked.add(target);
5455
+ if (!reaching(target, linked)) continue;
5387
5456
  if (!source.groups(kind).some((node) => node.path === path)) continue;
5388
5457
  const prefix = `${path}/`;
5389
5458
  for (const member of source.list(kind)) {
5390
5459
  if (!member.path.startsWith(prefix)) continue;
5391
- members.add(`${kind}:${member.path}`);
5392
- visitEntity(kind, member.toJSON(), true, linked);
5460
+ const at = `${kind}:${member.path}`;
5461
+ members.add(at);
5462
+ if (reaching(at, linked)) visitEntity(kind, member.toJSON(), true, linked);
5393
5463
  }
5394
5464
  }
5395
5465
  };
@@ -60,6 +60,9 @@ type Read<F extends FieldsSchema, C extends ComputedFields> = Entity & z.infer<F
60
60
  type KindRef<E, K extends string, P extends string> = ExtendableRef<Partial<CreateInputOf<E>>> & {
61
61
  readonly __ref: `${K}:${P}`;
62
62
  };
63
+ /** What an entity's `item` hands back: a body `E` creates from, or the shorthand for its primary
64
+ * field. */
65
+ type ItemInput<E> = CreateInputOf<E> | string | number | Ref;
63
66
  /** The body `E` creates an item from, read off its `create`. Open when `E` is no entity, as for a
64
67
  * `ref` called detached from one. */
65
68
  type CreateInputOf<E> = E extends {
@@ -94,7 +97,7 @@ type OpInputsOf<S extends UserSchemas> = { readonly [V in keyof S as S[V] extend
94
97
  } ? {
95
98
  readonly input: I;
96
99
  } : never };
97
- type EntityClass<F extends FieldsSchema, G extends FieldsSchema,
100
+ interface EntityClass<F extends FieldsSchema, G extends FieldsSchema,
98
101
  /** The kind as a literal, so `ref` can narrow to the paths a consumer's config declares. Defaults
99
102
  * to `string`, which is what every 2-argument use of this type keeps getting. */
100
103
  K extends string = string,
@@ -112,7 +115,7 @@ Single extends boolean = false,
112
115
  GC extends GroupComputedFields = Record<never, never>,
113
116
  /** The kind's `groupInputs` as written, so `group()` takes each input key at its handler's declared
114
117
  * type. Defaults to none. */
115
- GI extends GroupInputs = Record<never, never>> = Omit<RefSchema, 'extend'> & {
118
+ GI extends GroupInputs = Record<never, never>> extends Omit<RefSchema, 'extend'> {
116
119
  readonly kind: K;
117
120
  /** Whether the kind has exactly one item — see `singleton` on `defineEntity`. A property rather than
118
121
  * only a type parameter so a conditional type can tell the two apart: `get`'s signature differs by
@@ -136,7 +139,7 @@ GI extends GroupInputs = Record<never, never>> = Omit<RefSchema, 'extend'> & {
136
139
  readonly ref: Single extends true ? <E, P extends PathOf<K>>(this: E, path?: P) => KindRef<E, K, P> : <E, P extends PathOf<K>>(this: E, path: P) => KindRef<E, K, P>;
137
140
  /** Typed constructor for one item — a full body, or a bare value/ref that's shorthand
138
141
  * for the kind's primary field. Returned as-is; `register` normalizes the shorthand. */
139
- item(def: CreateInput<F> | string | number | Ref): CreateInput<F> | string | number | Ref;
142
+ item(def: CreateInput<F> | string | number | Ref): ItemInput<this>;
140
143
  /** Typed constructor for a group node — branded so `register` routes it to the group
141
144
  * store; input is the group fields' create body, plus any registration-time input the kind
142
145
  * declares (`groupInputs`), which is consumed here and never stored. */
@@ -148,7 +151,8 @@ GI extends GroupInputs = Record<never, never>> = Omit<RefSchema, 'extend'> & {
148
151
  get(config: Config, ...pathOrRef: Single extends true ? [pathOrRef?: string | Ref] : [pathOrRef: string | Ref]): Read<F, C> | undefined;
149
152
  /** Every item of this kind, typed off the fields — `Component.list(config)`. Total by default;
150
153
  * `{ retired: false }` is the offer surface's read (see `Config.list`). */
151
- list(config: Config, opts?: ListOptions): Read<F, C>[]; /** This kind's items as a folder/file tree — `Component.tree(config)` (the sidebar). */
154
+ list(config: Config, opts?: ListOptions): Read<F, C>[];
155
+ /** This kind's items as a folder/file tree — `Component.tree(config)` (the sidebar). */
152
156
  tree(config: Config): TreeNode[];
153
157
  /** This kind's group nodes and their metadata — `Package.groups(config)`, the packages a system
154
158
  * depends on. The enumeration `list`/`tree` can't give: both derive from item paths, so a group
@@ -162,11 +166,13 @@ GI extends GroupInputs = Record<never, never>> = Omit<RefSchema, 'extend'> & {
162
166
  dependents(config: Config, path: string, opts?: TraverseOptions): string[];
163
167
  /** Create an item of this kind — `Component.create(config, 'Button', {…})`. Typed body; returns
164
168
  * the config for chaining. (The typed twin of `config.apply({kind, operation:'create', …})`.) */
165
- create<C extends Config>(config: C, path: string, data: CreateInput<F>): C; /** Update an item — a partial patch merged over the current value. */
169
+ create<C extends Config>(config: C, path: string, data: CreateInput<F>): C;
170
+ /** Update an item — a partial patch merged over the current value. */
166
171
  update<C extends Config>(config: C, path: string, data: Partial<z.infer<F>>): C;
167
172
  /** Delete an item, with whatever cannot exist without it ({@link EntityKind.structuralBody}). Refused
168
173
  * while anything else still references it. */
169
- delete<C extends Config>(config: C, path: string): C; /** Rename an item's path — references cascade. */
174
+ delete<C extends Config>(config: C, path: string): C;
175
+ /** Rename an item's path — references cascade. */
170
176
  rename<C extends Config>(config: C, path: string, to: string): C;
171
177
  /**
172
178
  * Make a variant of this kind — same kind, different rules, stating only the differences.
@@ -206,7 +212,7 @@ GI extends GroupInputs = Record<never, never>> = Omit<RefSchema, 'extend'> & {
206
212
  * Config that never imported it. See {@link EntityKind.entityName}. */
207
213
  entityName?: string;
208
214
  }): EntityClass<ExtendedFields<F, F2>, ExtendedFields<G, G2>, K, [S2] extends [never] ? S : OpInputsOf<S2>, C, Single, GC, GI>;
209
- };
215
+ }
210
216
  declare function defineEntity<F extends FieldsSchema, G extends FieldsSchema = z.ZodObject<Record<never, never>>, K extends string = string, const S extends UserSchemas = Record<never, never>, const C extends ComputedFields = Record<never, never>, Single extends boolean = false, const GC extends GroupComputedFields = Record<never, never>, const GI extends GroupInputs = Record<never, never>>(spec: {
211
217
  kind: K;
212
218
  /** The kind's fields, or a factory over the write's context (`config`, `body`) when a field's
@@ -401,4 +407,4 @@ type OpInput<E extends {
401
407
  input: infer I extends z.ZodTypeAny;
402
408
  } ? z.input<I> : never;
403
409
  //#endregion
404
- export { EntityClass, GroupComputedInput, ItemOf, KindRef, OpInput, defineEntity };
410
+ export { EntityClass, GroupComputedInput, ItemInput, ItemOf, KindRef, OpInput, defineEntity };
@@ -239,12 +239,17 @@ interface DanglingDirective {
239
239
  /** The accessor as written — this config may have no kind under it. */
240
240
  accessor: string;
241
241
  path: string;
242
- /** `accessor` when no kind is registered under that name, `path` when the source has no such entity. */
243
- missing: 'accessor' | 'path';
242
+ /**
243
+ * `accessor` when no kind is registered under that name, `path` when the source has no such entity,
244
+ * `row` when the source has it but the row here isn't one the pin derives.
245
+ */
246
+ missing: 'accessor' | 'path' | 'row';
247
+ /** With `row`: the pin the row there follows instead, absent when it is this config's own. */
248
+ heldBy?: string;
244
249
  }
245
250
  /**
246
- * Every directive aimed at nothing — an accessor this config has no kind for, or a path the source
247
- * does not have.
251
+ * Every directive aimed at nothing — an accessor this config has no kind for, a path the source
252
+ * does not have, or a path where the row here isn't one the pin derives.
248
253
  *
249
254
  * The one mistake the other checks structurally cannot see. Each of them answers for a path it is
250
255
  * already holding, so it asks "does the declaration mention this one" — and a name nobody has is
@@ -254,6 +259,10 @@ interface DanglingDirective {
254
259
  * Also what notices the source dropping a path. A directive written against an entity a later version
255
260
  * removed keeps parsing and keeps doing nothing, which is the same failure the declared verb exists to
256
261
  * catch one level down.
262
+ *
263
+ * And what notices a path leaving the pin. A row taken through another pin (`linked-system/link` with
264
+ * `onLinked: 'take'`), or authored here, stands where the pin's row stood, and the entry the pin
265
+ * still carries for it applies to nothing.
257
266
  */
258
267
  declare function danglingDirectives(config: Config): DanglingDirective[];
259
268
  /** A borrowed token whose condition is missing or has a different category here. */
@@ -489,8 +489,25 @@ function unadoptedDirectives(config) {
489
489
  return found;
490
490
  }
491
491
  /**
492
- * Every directive aimed at nothing — an accessor this config has no kind for, or a path the source
493
- * does not have.
492
+ * Who holds the row at a path a directive names, when it isn't the directive's own pin: the slug of
493
+ * the pin the row follows instead, `null` for a row of this config's own, `undefined` when the pin's
494
+ * row is there or nothing is.
495
+ *
496
+ * A pin's `options` lay over the rows its declaration derives, and nothing else. A row taken through
497
+ * another pin, or authored here, stands at the path in its place, so the entry applies to nothing.
498
+ */
499
+ function heldElsewhere(config, { slug, kind, path }) {
500
+ const item = config.storedBody(kind, path);
501
+ const group = item === void 0 ? config.groupMeta(kind, path) : void 0;
502
+ if (item === void 0 && group === void 0) return void 0;
503
+ const pointer = item ?? group?.ref;
504
+ const through = isRef(pointer) ? sourceOf(pointer) : void 0;
505
+ if (through === slug) return void 0;
506
+ return through ?? null;
507
+ }
508
+ /**
509
+ * Every directive aimed at nothing — an accessor this config has no kind for, a path the source
510
+ * does not have, or a path where the row here isn't one the pin derives.
494
511
  *
495
512
  * The one mistake the other checks structurally cannot see. Each of them answers for a path it is
496
513
  * already holding, so it asks "does the declaration mention this one" — and a name nobody has is
@@ -500,6 +517,10 @@ function unadoptedDirectives(config) {
500
517
  * Also what notices the source dropping a path. A directive written against an entity a later version
501
518
  * removed keeps parsing and keeps doing nothing, which is the same failure the declared verb exists to
502
519
  * catch one level down.
520
+ *
521
+ * And what notices a path leaving the pin. A row taken through another pin (`linked-system/link` with
522
+ * `onLinked: 'take'`), or authored here, stands where the pin's row stood, and the entry the pin
523
+ * still carries for it applies to nothing.
503
524
  */
504
525
  function danglingDirectives(config) {
505
526
  const found = [];
@@ -516,7 +537,23 @@ function danglingDirectives(config) {
516
537
  });
517
538
  continue;
518
539
  }
519
- if (source.resolve(kind, path) || source.members(kind, path).length > 0) continue;
540
+ if (source.resolve(kind, path) || source.members(kind, path).length > 0) {
541
+ if (config.sourceOverlay(slug, kind, path)?.hidden === true) continue;
542
+ const heldBy = heldElsewhere(config, {
543
+ slug,
544
+ kind,
545
+ path
546
+ });
547
+ if (heldBy === void 0) continue;
548
+ found.push({
549
+ slug,
550
+ accessor,
551
+ path,
552
+ missing: "row",
553
+ ...heldBy === null ? {} : { heldBy }
554
+ });
555
+ continue;
556
+ }
520
557
  found.push({
521
558
  slug,
522
559
  accessor,