@archie/devtools 0.0.41 → 0.0.43

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.
@@ -78,6 +78,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
78
78
  }
79
79
  return "";
80
80
  }
81
+ function isIntrinsicTagName(name) {
82
+ return t.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
83
+ }
81
84
  function isHostLikeMemberExpressionName(name) {
82
85
  if (!t.isJSXMemberExpression(name)) return false;
83
86
  const propertyName = name.property.name;
@@ -168,6 +171,158 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
168
171
  }
169
172
  return hasAny ? t.objectExpression(props) : null;
170
173
  }
174
+ function describeChildrenText(opening) {
175
+ const elementPath = opening.parentPath;
176
+ if (!elementPath || !elementPath.isJSXElement()) return null;
177
+ const element = elementPath.node;
178
+ if (!element.closingElement) return null;
179
+ const children = element.children;
180
+ if (children.length === 0) return "";
181
+ if (children.length !== 1) return "__dynamic__";
182
+ const [only] = children;
183
+ if (!t.isJSXText(only)) return "__dynamic__";
184
+ return only.value.trim();
185
+ }
186
+ function literalInitText(init) {
187
+ if (t.isStringLiteral(init)) return init.value;
188
+ if (t.isTemplateLiteral(init) && init.expressions.length === 0) {
189
+ const [quasi] = init.quasis;
190
+ if (!quasi) return null;
191
+ return quasi.value.cooked ?? quasi.value.raw;
192
+ }
193
+ return null;
194
+ }
195
+ function describeLocalConstSource(name, binding) {
196
+ const declarator = binding.path;
197
+ if (!declarator.isVariableDeclarator()) return null;
198
+ const declaration = declarator.parentPath;
199
+ if (!declaration?.isVariableDeclaration()) return null;
200
+ if (declaration.node.kind !== "const" && declaration.node.kind !== "let") return null;
201
+ const init = declarator.node.init;
202
+ if (!init) return null;
203
+ const literal = literalInitText(init);
204
+ if (literal === null) return null;
205
+ const loc = init.loc?.start;
206
+ if (!loc) return null;
207
+ return {
208
+ kind: "local-const",
209
+ name,
210
+ // The init lives in the SAME file being transformed by construction —
211
+ // `scope.getBinding` never crosses a file boundary — so the caller's
212
+ // own `file` (computed once per visited node) is correct here too.
213
+ file: currentFile,
214
+ line: loc.line,
215
+ col: loc.column + 1,
216
+ literal,
217
+ // FE MINOR-5: how many places in the FILE read this const — every
218
+ // reference, not only the one this call is describing. `refs > 1`
219
+ // means one initializer feeds more than one rendered element, so a
220
+ // consumer must warn/refuse rather than treat a rewrite as scoped to
221
+ // just this usage (see `PropSource`'s docstring for the full contract).
222
+ refs: binding.referencePaths.length
223
+ };
224
+ }
225
+ function resolvableSameFileBinding(name, scope) {
226
+ const binding = scope.getBinding(name);
227
+ if (!binding) return null;
228
+ if (!binding.constant) return null;
229
+ const bp = binding.path;
230
+ if (bp.isImportSpecifier() || bp.isImportDefaultSpecifier() || bp.isImportNamespaceSpecifier()) {
231
+ return null;
232
+ }
233
+ return binding;
234
+ }
235
+ function memberChain(expr) {
236
+ const path2 = [];
237
+ let current = expr;
238
+ while (t.isMemberExpression(current)) {
239
+ if (current.computed) return null;
240
+ if (!t.isIdentifier(current.property)) return null;
241
+ path2.unshift(current.property.name);
242
+ current = current.object;
243
+ }
244
+ if (!t.isIdentifier(current)) return null;
245
+ return { root: current.name, path: path2 };
246
+ }
247
+ let currentFile = "";
248
+ function describeSource(expr, scope) {
249
+ if (t.isIdentifier(expr)) {
250
+ const binding = resolvableSameFileBinding(expr.name, scope);
251
+ if (!binding) return { kind: "other" };
252
+ const local = describeLocalConstSource(expr.name, binding);
253
+ if (local) return local;
254
+ return { kind: "identifier", name: expr.name };
255
+ }
256
+ if (t.isMemberExpression(expr) && !expr.computed) {
257
+ const chain = memberChain(expr);
258
+ if (chain && resolvableSameFileBinding(chain.root, scope)) {
259
+ return { kind: "member", root: chain.root, path: chain.path };
260
+ }
261
+ }
262
+ return { kind: "other" };
263
+ }
264
+ function isStaticPropsLiteral(expr) {
265
+ return t.isStringLiteral(expr) || t.isNumericLiteral(expr) || t.isBooleanLiteral(expr) || t.isNullLiteral(expr);
266
+ }
267
+ function describeChildrenSource(opening) {
268
+ const elementPath = opening.parentPath;
269
+ if (!elementPath || !elementPath.isJSXElement()) return null;
270
+ const children = elementPath.node.children;
271
+ if (children.length !== 1) return null;
272
+ const [only] = children;
273
+ if (!t.isJSXExpressionContainer(only)) return null;
274
+ if (t.isJSXEmptyExpression(only.expression)) return null;
275
+ return describeSource(only.expression, opening.scope);
276
+ }
277
+ function collectPropSources(attrs, scope) {
278
+ const entries = [];
279
+ for (const attr of attrs) {
280
+ if (!t.isJSXAttribute(attr)) continue;
281
+ const name = t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
282
+ if (NON_BINDING_ATTRS.has(name)) continue;
283
+ const value = attr.value;
284
+ if (!t.isJSXExpressionContainer(value)) continue;
285
+ const expression = value.expression;
286
+ if (t.isJSXEmptyExpression(expression)) continue;
287
+ if (isStaticPropsLiteral(expression)) continue;
288
+ entries.push([name, describeSource(expression, scope)]);
289
+ }
290
+ return entries;
291
+ }
292
+ function propSourceToAst(source) {
293
+ switch (source.kind) {
294
+ case "identifier":
295
+ return t.objectExpression([
296
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("identifier")),
297
+ t.objectProperty(t.identifier("name"), t.stringLiteral(source.name))
298
+ ]);
299
+ case "local-const":
300
+ return t.objectExpression([
301
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("local-const")),
302
+ t.objectProperty(t.identifier("name"), t.stringLiteral(source.name)),
303
+ t.objectProperty(t.identifier("file"), t.stringLiteral(source.file)),
304
+ t.objectProperty(t.identifier("line"), t.numericLiteral(source.line)),
305
+ t.objectProperty(t.identifier("col"), t.numericLiteral(source.col)),
306
+ t.objectProperty(
307
+ t.identifier("literal"),
308
+ source.literal === null ? t.nullLiteral() : t.stringLiteral(source.literal)
309
+ ),
310
+ t.objectProperty(t.identifier("refs"), t.numericLiteral(source.refs))
311
+ ]);
312
+ case "member":
313
+ return t.objectExpression([
314
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("member")),
315
+ t.objectProperty(t.identifier("root"), t.stringLiteral(source.root)),
316
+ t.objectProperty(
317
+ t.identifier("path"),
318
+ t.arrayExpression(source.path.map((segment) => t.stringLiteral(segment)))
319
+ )
320
+ ]);
321
+ case "other":
322
+ default:
323
+ return t.objectExpression([t.objectProperty(t.identifier("kind"), t.stringLiteral("other"))]);
324
+ }
325
+ }
171
326
  function isMetaStripSpreadExpression(arg) {
172
327
  if (!t.isCallExpression(arg)) return false;
173
328
  if (!t.isArrowFunctionExpression(arg.callee)) return false;
@@ -275,6 +430,14 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
275
430
  }
276
431
  return map;
277
432
  }
433
+ function restParamName(pattern) {
434
+ for (const prop of pattern.properties) {
435
+ if (!t.isRestElement(prop)) continue;
436
+ if (t.isIdentifier(prop.argument)) return prop.argument.name;
437
+ return null;
438
+ }
439
+ return null;
440
+ }
278
441
  function resolvePropContext(path2) {
279
442
  let fn = path2.getFunctionParent();
280
443
  while (fn) {
@@ -283,7 +446,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
283
446
  return {
284
447
  componentScope: fn.scope,
285
448
  propMap: buildPropMap(first),
286
- componentName: getFunctionName(fn)
449
+ componentName: getFunctionName(fn),
450
+ restParam: restParamName(first),
451
+ componentPath: fn
287
452
  };
288
453
  }
289
454
  fn = fn.getFunctionParent();
@@ -403,12 +568,62 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
403
568
  const props = collectPropIdentifiers(expr, scope, ctx);
404
569
  return props.length > 0 ? { kind: "formatted", prop: props[0] } : null;
405
570
  }
571
+ function unwrapSanitizedSpread(arg) {
572
+ if (!t.isCallExpression(arg)) return arg;
573
+ if (!isMetaStripSpreadExpression(arg)) return arg;
574
+ if (arg.arguments.length !== 1) return arg;
575
+ const [payload] = arg.arguments;
576
+ if (!t.isObjectExpression(payload) || payload.properties.length !== 1) return arg;
577
+ const [only] = payload.properties;
578
+ if (!t.isSpreadElement(only)) return arg;
579
+ return only.argument;
580
+ }
581
+ function forwardsOwnerRestChildren(opening, ctx) {
582
+ if (ctx.restParam === null) return false;
583
+ const elementPath = opening.parentPath;
584
+ if (!elementPath || !elementPath.isJSXElement()) return false;
585
+ if (meaningfulChildren(t, elementPath.node.children).length > 0) return false;
586
+ let spreadsRest = 0;
587
+ for (const attr of opening.node.attributes) {
588
+ if (t.isJSXAttribute(attr)) {
589
+ if (t.isJSXIdentifier(attr.name, { name: "children" })) return false;
590
+ continue;
591
+ }
592
+ if (!t.isJSXSpreadAttribute(attr)) continue;
593
+ const payload = unwrapSanitizedSpread(attr.argument);
594
+ if (!t.isIdentifier(payload, { name: ctx.restParam })) return false;
595
+ spreadsRest += 1;
596
+ }
597
+ if (spreadsRest !== 1) return false;
598
+ const binding = opening.scope.getBinding(ctx.restParam);
599
+ if (!binding) return false;
600
+ if (binding.scope !== ctx.componentScope) return false;
601
+ return binding.constant;
602
+ }
603
+ const restForwarderCounts = /* @__PURE__ */ new WeakMap();
604
+ function countRestForwarders(ctx) {
605
+ const cached = restForwarderCounts.get(ctx.componentPath.node);
606
+ if (cached !== void 0) return cached;
607
+ let count = 0;
608
+ ctx.componentPath.traverse({
609
+ JSXOpeningElement(inner) {
610
+ if (forwardsOwnerRestChildren(inner, ctx)) count += 1;
611
+ }
612
+ });
613
+ restForwarderCounts.set(ctx.componentPath.node, count);
614
+ return count;
615
+ }
406
616
  function collectPropBindings(path2, attrs) {
407
617
  const ctx = resolvePropContext(path2);
408
- if (!ctx || ctx.propMap.size === 0) return { bindings: [], owner: null };
618
+ if (!ctx || ctx.propMap.size === 0 && ctx.restParam === null) {
619
+ return { bindings: [], owner: null };
620
+ }
409
621
  if (ctx.componentName === null) return { bindings: [], owner: null };
410
622
  const scope = path2.scope;
411
623
  const bindings = [];
624
+ if (forwardsOwnerRestChildren(path2, ctx) && countRestForwarders(ctx) === 1) {
625
+ bindings.push({ target: "children", prop: "children", kind: "direct" });
626
+ }
412
627
  const elementPath = path2.parentPath;
413
628
  if (elementPath && elementPath.isJSXElement()) {
414
629
  const children = meaningfulChildren(t, elementPath.node.children);
@@ -499,6 +714,7 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
499
714
  if (!loc) return;
500
715
  const root = state.file.opts.root ?? "";
501
716
  const file = root ? nodePath.relative(root, filename) : filename;
717
+ currentFile = file;
502
718
  const line = loc.line;
503
719
  const col = loc.column + 1;
504
720
  const componentName = getJSXTagName(path2.node);
@@ -510,7 +726,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
510
726
  return;
511
727
  }
512
728
  let rootName = null;
513
- if (t.isJSXIdentifier(path2.node.name)) {
729
+ if (isIntrinsicTagName(path2.node.name)) {
730
+ rootName = null;
731
+ } else if (t.isJSXIdentifier(path2.node.name)) {
514
732
  rootName = path2.node.name.name;
515
733
  } else if (t.isJSXMemberExpression(path2.node.name)) {
516
734
  let cur = path2.node.name;
@@ -572,6 +790,21 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
572
790
  t.objectProperty(t.identifier("slot"), t.stringLiteral(slot))
573
791
  );
574
792
  }
793
+ if (!isHostElement) {
794
+ const childrenText = describeChildrenText(path2);
795
+ metaObj.properties.push(
796
+ t.objectProperty(
797
+ t.identifier("childrenText"),
798
+ childrenText === null ? t.nullLiteral() : t.stringLiteral(childrenText)
799
+ )
800
+ );
801
+ const childrenSource = describeChildrenSource(path2);
802
+ if (childrenSource) {
803
+ metaObj.properties.push(
804
+ t.objectProperty(t.identifier("childrenSource"), propSourceToAst(childrenSource))
805
+ );
806
+ }
807
+ }
575
808
  if (bindings.length > 0) {
576
809
  metaObj.properties.push(
577
810
  t.objectProperty(t.identifier("propBindings"), propBindingsToAst(bindings))
@@ -583,6 +816,19 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
583
816
  )
584
817
  );
585
818
  }
819
+ const propSourceEntries = collectPropSources(path2.node.attributes, path2.scope);
820
+ if (propSourceEntries.length > 0) {
821
+ metaObj.properties.push(
822
+ t.objectProperty(
823
+ t.identifier("propSources"),
824
+ t.objectExpression(
825
+ propSourceEntries.map(
826
+ ([name, source]) => t.objectProperty(t.stringLiteral(name), propSourceToAst(source))
827
+ )
828
+ )
829
+ )
830
+ );
831
+ }
586
832
  if (isHostElement && includeHostSourceRef) {
587
833
  path2.node.attributes.push(
588
834
  t.jsxAttribute(
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  meaningfulChildren
3
- } from "./chunk-6QB35SLQ.mjs";
3
+ } from "./chunk-P5XMHBFS.mjs";
4
4
 
5
5
  // src/babel-plugin-collection-binding.ts
6
6
  import { VISITOR_KEYS } from "@babel/types";
package/dist/index.d.mts CHANGED
@@ -12,6 +12,10 @@ import { types, PluginObj } from '@babel/core';
12
12
  * nodeId, file, line, col, componentName, staticProps, movable, parentNodeId
13
13
  * + (ARCH-883 F1) slot, propBindings, propOwner
14
14
  * + (ARCH-883-KOI) origin
15
+ * + (ARCH-1444) childrenText — non-host elements only
16
+ * + (ARCH-1450) childrenSource, propSources — same-file identifier tracing
17
+ * for values `propBindings` cannot reach (no destructured-prop component
18
+ * in scope, or a value that never touches a prop at all)
15
19
  *
16
20
  * At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
17
21
  * element identity without any DOM attributes.
@@ -169,6 +173,17 @@ declare function babelPluginEditorMeta({ types: t }: {
169
173
  * `id`/`htmlFor`/`name`/`type`/`role`/`slot`) are downgraded to `formatted` by the
170
174
  * PRODUCER, because no usage value could make them a text edit. The consumer does
171
175
  * not need to re-derive that list; it only needs the allowlist above.
176
+ *
177
+ * ARCH-1443: `{ target: 'children', prop: 'children', kind: 'direct' }` is emitted
178
+ * for an element that forwards its component's props REST and renders no children
179
+ * of its own — the shape EVERY ejected shadcn-style primitive has
180
+ * (`({ className, ...p }) => <h3 className={…} {...p} />`). Nothing about the
181
+ * allowlist changes: the usage value of `children` is the JSX children written at
182
+ * the usage site (`<CardTitle>Cómo funciona</CardTitle>`), and the same
183
+ * usage-value law applies to it — plain text is writable, a nested element or a
184
+ * computed expression is a gap. The producer refuses whenever more than one
185
+ * element in the component forwards the rest, since one `children` prop cannot
186
+ * name which of them an edit is about.
172
187
  */
173
188
  interface PropBinding {
174
189
  /**
@@ -204,6 +219,134 @@ interface PropBinding {
204
219
  */
205
220
  kind: 'direct' | 'derived' | 'formatted';
206
221
  }
222
+ /**
223
+ * ARCH-1450 — where a rendered EXPRESSION traces to in the SAME file, for the
224
+ * one case `propBindings` structurally cannot answer: a value that never
225
+ * touches a component prop at all.
226
+ *
227
+ * `propBindings`/`propOwner` trace an expression to a PROP of the enclosing
228
+ * (module-level, destructured) component — that machinery is silent whenever
229
+ * there is no such component (a screen's `export default function X()` with
230
+ * no destructured params) or whenever the expression bottoms out in a plain
231
+ * local rather than a prop (`const title = "Contenido"; … <Section
232
+ * title={title}>`). In both cases `collectPropBindings` returns `{bindings:
233
+ * [], owner: null}` and the identifier name never crosses the postMessage
234
+ * boundary in any form — the FE's `usage-prop-resolver` has nothing to walk.
235
+ * `childrenSource`/`propSources` close exactly that gap, independently of
236
+ * whether a `PropBinding` was also emitted for the same target: the two
237
+ * fields answer different questions ("is this a prop, and is it a safe
238
+ * rewrite" vs "what source-level thing does this identifier name") and a
239
+ * consumer may need either, or both, depending on what it already knows.
240
+ *
241
+ * THE FOUR ANSWERS:
242
+ *
243
+ * `identifier` — the expression is a bare identifier whose binding
244
+ * resolves (constant, same file, not an import) but is
245
+ * not a literal-init local — a destructured prop used
246
+ * directly, a plain function parameter, or a local
247
+ * `const`/`let` whose initializer is not itself a plain
248
+ * string. `name` is the LOCAL identifier as written at
249
+ * this usage — NOT necessarily the prop name `propBindings`
250
+ * would report for a renamed destructure
251
+ * (`{ headline: h }` reports `h` here, `headline` there).
252
+ * Consumers that need the true prop name still read
253
+ * `propBindings`; this field only says "this traces to a
254
+ * resolvable local named X", which is enough to tell a
255
+ * prop/parameter apart from a dead end.
256
+ * `local-const` — the identifier resolves to a `const`/`let` declared in
257
+ * THIS SAME FILE (by construction — scope resolution never
258
+ * crosses a file boundary) whose initializer is a
259
+ * `StringLiteral` or a `TemplateLiteral` with NO
260
+ * interpolations. `literal` is that string — the exact
261
+ * text a consumer would show/replace. `file`/`line`/`col`
262
+ * point at the INITIALIZER, not the identifier's usage —
263
+ * that is where a rewrite must land: replacing the
264
+ * identifier's occurrence would rename a variable, not
265
+ * change what it renders wherever else it is used.
266
+ * `refs` (FE MINOR-5) is `binding.referencePaths.length` —
267
+ * how many places in the file READ this const, counting
268
+ * every usage, not only the one this `PropSource` sits on.
269
+ * `refs > 1` means the SAME initializer feeds more than
270
+ * one rendered element (`const title = "X"; <A
271
+ * title={title}/><B title={title}/>` — both usages report
272
+ * `refs: 2`): rewriting the initializer changes what BOTH
273
+ * elements show, so a consumer must warn or refuse rather
274
+ * than treat the write as scoped to the one element the
275
+ * edit was made on. `refs === 1` is the only shape safe to
276
+ * treat as a single-target rewrite.
277
+ * `member` — a non-computed member-expression chain
278
+ * (`props.title`, `item.address.city`) whose ROOT resolves
279
+ * the same way `identifier`'s binding does (constant, same
280
+ * file, not an import). `root` is the base identifier;
281
+ * `path` is the property chain beyond it, in order
282
+ * (`['address', 'city']` for `item.address.city`).
283
+ * `other` — the honest gap marker, same role `formatted` plays for
284
+ * `PropBinding.kind` and `'__dynamic__'` plays for
285
+ * `childrenText`: an expression the plugin looked at and
286
+ * declined to trace — a computed member (`obj[key]`), a
287
+ * call, a template WITH interpolations, a conditional, an
288
+ * identifier with no binding (a free/global read), a
289
+ * binding that IS reassigned somewhere in the file, or one
290
+ * that comes from an `import`. NEVER inferred past any of
291
+ * those — a wrong trace is strictly worse than an honest
292
+ * gap.
293
+ *
294
+ * SCOPE — deliberately NARROW, unlike `childrenText`/`origin`: both fields
295
+ * are OMITTED (not stamped `other`) whenever there is nothing to say, the
296
+ * same convention `propBindings`/`propOwner`/`slot` already use ("emitted
297
+ * only when it carries information"). `childrenSource` is only computed when
298
+ * the element's children are EXACTLY one `JSXExpressionContainer` (the case
299
+ * that makes `childrenText === '__dynamic__'` for the single most common
300
+ * reason — `<h1>{title}</h1>` — and therefore the case where a source trace
301
+ * is actually useful); self-closing elements, plain-text children and
302
+ * mixed/multi children never get it. `propSources` only carries entries for
303
+ * attributes whose value is a `JSXExpressionContainer` wrapping something
304
+ * OTHER than a literal `extractStaticProps` already captures exactly
305
+ * (`staticProps` already has the true value for `title={"Static"}`; adding a
306
+ * `propSources.title: {kind:'other'}` next to it would look like a gap where
307
+ * there is none). Both fields are ABSENT — not `null`/`{}` — on every bundle
308
+ * built before ARCH-1450, the same absence-means-"cannot answer" law every
309
+ * other additive field in this file states.
310
+ *
311
+ * Emitted for BOTH host and non-host elements for `propSources` (attribute
312
+ * resolution does not depend on the element being a component usage — a host
313
+ * `<div title={title}>` traces exactly the same way `staticProps` treats host
314
+ * and component attributes identically). `childrenSource` follows
315
+ * `childrenText`'s own host exemption: a host element's children are the
316
+ * screen's own literal JSX, not a component usage, so there is no target for
317
+ * an editor-driven rewrite to route through in the first place.
318
+ *
319
+ * CROSS-REPO CONTRACT (ARCH-1450 CR MINOR-1): `usage-prop-resolver.ts`
320
+ * (archie-app) is the only consumer, and the two repos share no fixture of
321
+ * their own — a rename here (e.g. `literal` -> `value`) would leave both
322
+ * suites green while every real generated project broke. The published
323
+ * package ships `fixtures/prop-source.fixture.json` (see `files` in
324
+ * `package.json`) specifically so archie-app's tests can assert against the
325
+ * REAL emitted shape instead of a hand-typed copy of it. That file is
326
+ * GENERATED, never hand-edited — see `src/prop-source-fixture.generate.test.ts`,
327
+ * which regenerates it from the live plugin and fails loud the moment the
328
+ * committed copy drifts. Regenerate with `UPDATE_FIXTURES=1 npx vitest run
329
+ * src/prop-source-fixture.generate.test.ts`, review the diff, and bump
330
+ * archie-app's copy in the same change.
331
+ */
332
+ type PropSource = {
333
+ kind: 'identifier';
334
+ name: string;
335
+ } | {
336
+ kind: 'local-const';
337
+ name: string;
338
+ file: string;
339
+ line: number;
340
+ col: number;
341
+ literal: string | null;
342
+ refs: number;
343
+ } | {
344
+ kind: 'member';
345
+ root: string;
346
+ path: string[];
347
+ } | {
348
+ kind: 'other';
349
+ };
207
350
  /** Metadata injected into every JSX element by babel-plugin-editor-meta. */
208
351
  interface EditorMeta {
209
352
  /** Stable identifier: "file:line:col:componentName" */
@@ -216,6 +359,38 @@ interface EditorMeta {
216
359
  componentName: string;
217
360
  /** Safely serializable literal prop values. Dynamic expressions are "__dynamic__". */
218
361
  staticProps: Record<string, unknown> | null;
362
+ /**
363
+ * ARCH-1444 — the SHAPE of this element's JSX children, and their text when
364
+ * that text is plain. `staticProps` is built from ATTRIBUTES only, so without
365
+ * this field the `children` prop is the one binding target whose value at the
366
+ * usage a consumer cannot see — and the usage-value law above ("plain text is
367
+ * writable, a nested element or a computed expression is a gap") is stated in
368
+ * terms of exactly that value.
369
+ *
370
+ * `null` — the element is SELF-CLOSING: no children container at
371
+ * all. NOT the same as `''` (`<X></X>`), which can take a
372
+ * text write; `<X />` cannot without a structural rewrite.
373
+ * `''` — an empty children container.
374
+ * `'__dynamic__'` — anything that is not exactly one JSXText child: an
375
+ * expression container, a nested element, a comment, or
376
+ * several children. Same sentinel as `staticProps`.
377
+ * any other string— the sole JSXText child, TRIMMED (JSX itself trims
378
+ * leading/trailing whitespace-with-newlines, so the
379
+ * trimmed form is what renders). A newline that SURVIVES
380
+ * the trim is real multi-line content: a consumer that
381
+ * writes by splicing must refuse it, because replacing it
382
+ * with one line deletes source lines and invalidates every
383
+ * anchor below.
384
+ *
385
+ * Emitted for NON-HOST elements only — a usage is always a component tag, and
386
+ * stamping every `<div>` would be weight for an answer nothing asks.
387
+ *
388
+ * ABSENT (`undefined`) on a host element and on every bundle built before this
389
+ * change. Absence is NOT "no children": it means the page cannot answer the
390
+ * question, and a consumer must gap the edit rather than assume (the same law
391
+ * `origin`'s docstring states at length).
392
+ */
393
+ childrenText?: string | null;
219
394
  /**
220
395
  * Whether the JSX node is in a reorderable position (2+ siblings).
221
396
  *
@@ -354,6 +529,30 @@ interface EditorMeta {
354
529
  * not the usage's, and routing those would write the wrong prop. Mismatch = gap.
355
530
  */
356
531
  propOwner?: string | null;
532
+ /**
533
+ * ARCH-1450 — source trace for this element's SOLE children expression
534
+ * (`<h1>{title}</h1>`), independently of whether `propBindings` also has a
535
+ * `target: 'children'` entry for the same node. See `PropSource`'s own
536
+ * docstring for the full contract (the four kinds, the narrow emission
537
+ * scope, why it exists alongside `propBindings`).
538
+ *
539
+ * Absent when the element is self-closing, has plain-text children, has
540
+ * more than one child, or is a HOST element — see `PropSource`'s docstring.
541
+ * Absent on every bundle built before ARCH-1450.
542
+ */
543
+ childrenSource?: PropSource;
544
+ /**
545
+ * ARCH-1450 — source trace for each ATTRIBUTE whose value is a
546
+ * `JSXExpressionContainer`, keyed by the attribute name (never `attr:`
547
+ * prefixed — this is not `PropBinding.target`). See `PropSource`'s own
548
+ * docstring for the full contract.
549
+ *
550
+ * Emitted only when non-empty — read it as `meta.propSources ?? {}`. An
551
+ * attribute whose expression is a literal already exact in `staticProps`
552
+ * never gets an entry here (nothing to trace beyond what `staticProps`
553
+ * already says). Absent on every bundle built before ARCH-1450.
554
+ */
555
+ propSources?: Record<string, PropSource>;
357
556
  }
358
557
 
359
558
  interface ArchieDevToolsOptions {