@archie/devtools 0.0.22 → 0.0.24

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.
package/dist/index.mjs CHANGED
@@ -178,6 +178,36 @@ function babelPluginDndAuto({ types: t }, opts = {}) {
178
178
 
179
179
  // src/babel-plugin-editor-meta.ts
180
180
  import * as nodePath from "path";
181
+ var KIT_SLOT_ATTR = "data-kit-slot";
182
+ var MAX_SLOT_SEGMENTS = 4;
183
+ var NON_BINDING_ATTRS = /* @__PURE__ */ new Set(["__editorMeta", "key", "ref", KIT_SLOT_ATTR]);
184
+ var STRUCTURAL_ATTRS = /* @__PURE__ */ new Set([
185
+ "id",
186
+ "htmlFor",
187
+ "slot",
188
+ "name",
189
+ "type",
190
+ "role",
191
+ // The camelCase carrier of the SAME durable key as `data-agent-id`: kit
192
+ // components take `agentId` and render it as the data attribute. Found on the
193
+ // real corpus (×4) — blocking only the dashed form would have left the prop
194
+ // form routable, and a durable key with a string-literal usage value sails
195
+ // straight through the usage-value law.
196
+ "agentId"
197
+ ]);
198
+ function isNonContentAttribute(name) {
199
+ if (/^on[A-Z]/.test(name)) return true;
200
+ if (name.startsWith("data-")) return true;
201
+ return STRUCTURAL_ATTRS.has(name);
202
+ }
203
+ function normalizeSlot(raw) {
204
+ const value = raw.trim();
205
+ if (!value) return null;
206
+ const segments = value.split("/");
207
+ if (segments.length > MAX_SLOT_SEGMENTS) return null;
208
+ if (segments.some((segment) => segment.trim() === "")) return null;
209
+ return value;
210
+ }
181
211
  function isPackageImport(src) {
182
212
  if (src.startsWith(".") || src.startsWith("/")) return false;
183
213
  if (src.startsWith("@")) {
@@ -193,7 +223,8 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
193
223
  const {
194
224
  fileExclude = /node_modules/,
195
225
  instrumentImported = true,
196
- includeHostSourceRef = false
226
+ includeHostSourceRef = false,
227
+ origin = "screen"
197
228
  } = opts;
198
229
  function getJSXTagName(el) {
199
230
  const { name } = el;
@@ -306,29 +337,275 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
306
337
  }
307
338
  return false;
308
339
  }
340
+ function buildPayloadFilter(payload) {
341
+ return t.callExpression(
342
+ t.arrowFunctionExpression(
343
+ [
344
+ t.objectPattern([
345
+ t.objectProperty(
346
+ t.identifier("__editorMeta"),
347
+ t.identifier("_$em"),
348
+ false,
349
+ false
350
+ ),
351
+ t.objectProperty(
352
+ t.stringLiteral(KIT_SLOT_ATTR),
353
+ t.identifier("_$ks"),
354
+ false,
355
+ false
356
+ ),
357
+ t.restElement(t.identifier("_$r"))
358
+ ])
359
+ ],
360
+ t.identifier("_$r")
361
+ ),
362
+ [t.objectExpression([t.spreadElement(payload)])]
363
+ );
364
+ }
309
365
  function sanitizeSpreadAttributes(attrs) {
310
366
  for (const attr of attrs) {
311
367
  if (!t.isJSXSpreadAttribute(attr)) continue;
312
368
  if (isMetaStripSpreadExpression(attr.argument)) continue;
313
- const origArg = attr.argument;
314
- attr.argument = t.callExpression(
315
- t.arrowFunctionExpression(
316
- [
317
- t.objectPattern([
318
- t.objectProperty(
319
- t.identifier("__editorMeta"),
320
- t.identifier("_$em"),
321
- false,
322
- false
323
- ),
324
- t.restElement(t.identifier("_$r"))
325
- ])
326
- ],
327
- t.identifier("_$r")
328
- ),
329
- [t.objectExpression([t.spreadElement(origArg)])]
330
- );
369
+ attr.argument = buildPayloadFilter(attr.argument);
370
+ }
371
+ }
372
+ function consumeKitSlot(attrs) {
373
+ let slot = null;
374
+ let found = false;
375
+ for (const attr of attrs) {
376
+ if (!t.isJSXAttribute(attr)) continue;
377
+ if (!t.isJSXIdentifier(attr.name, { name: KIT_SLOT_ATTR })) continue;
378
+ found = true;
379
+ if (slot !== null) continue;
380
+ const { value } = attr;
381
+ if (t.isStringLiteral(value)) {
382
+ slot = normalizeSlot(value.value);
383
+ } else if (t.isJSXExpressionContainer(value) && t.isStringLiteral(value.expression)) {
384
+ slot = normalizeSlot(value.expression.value);
385
+ }
386
+ }
387
+ if (!found) return { slot: null, found: false, attrs };
388
+ return {
389
+ slot,
390
+ found: true,
391
+ attrs: attrs.filter(
392
+ (attr) => !(t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: KIT_SLOT_ATTR }))
393
+ )
394
+ };
395
+ }
396
+ function getFunctionName(fn) {
397
+ const node = fn.node;
398
+ if (t.isFunctionDeclaration(node) && node.id) return node.id.name;
399
+ if (t.isFunctionExpression(node) && node.id) return node.id.name;
400
+ const parent = fn.parentPath;
401
+ if (parent?.isVariableDeclarator() && t.isIdentifier(parent.node.id)) {
402
+ return parent.node.id.name;
403
+ }
404
+ return null;
405
+ }
406
+ function buildPropMap(pattern) {
407
+ const map = /* @__PURE__ */ new Map();
408
+ for (const prop of pattern.properties) {
409
+ if (!t.isObjectProperty(prop) || prop.computed) continue;
410
+ const key = t.isIdentifier(prop.key) ? prop.key.name : t.isStringLiteral(prop.key) ? prop.key.value : null;
411
+ if (!key) continue;
412
+ const value = prop.value;
413
+ if (t.isIdentifier(value)) {
414
+ map.set(value.name, key);
415
+ } else if (t.isAssignmentPattern(value) && t.isIdentifier(value.left)) {
416
+ map.set(value.left.name, key);
417
+ }
331
418
  }
419
+ return map;
420
+ }
421
+ function resolvePropContext(path2) {
422
+ let fn = path2.getFunctionParent();
423
+ while (fn) {
424
+ const [first] = fn.node.params;
425
+ if (first && t.isObjectPattern(first)) {
426
+ if (fn.scope.parent !== fn.scope.getProgramParent()) return null;
427
+ return {
428
+ componentScope: fn.scope,
429
+ propMap: buildPropMap(first),
430
+ componentName: getFunctionName(fn)
431
+ };
432
+ }
433
+ fn = fn.getFunctionParent();
434
+ }
435
+ return null;
436
+ }
437
+ function resolveProp(name, scope, ctx) {
438
+ const prop = ctx.propMap.get(name);
439
+ if (!prop) return null;
440
+ const binding = scope.getBinding(name);
441
+ if (!binding) return null;
442
+ if (binding.scope !== ctx.componentScope) return null;
443
+ if (!binding.constant) return null;
444
+ return prop;
445
+ }
446
+ function isScopeWithinComponent(scope, ctx) {
447
+ let current = scope;
448
+ while (current) {
449
+ if (current === ctx.componentScope) return true;
450
+ current = current.parent;
451
+ }
452
+ return false;
453
+ }
454
+ function resolveLocalConstInit(name, scope, ctx) {
455
+ const binding = scope.getBinding(name);
456
+ if (!binding) return null;
457
+ if (!binding.constant) return null;
458
+ const declarator = binding.path;
459
+ if (!declarator.isVariableDeclarator()) return null;
460
+ if (!isScopeWithinComponent(binding.scope, ctx)) return null;
461
+ const init = declarator.node.init;
462
+ if (!init || !t.isExpression(init)) return null;
463
+ return { init, scope: declarator.scope };
464
+ }
465
+ function isStaticStringLiteral(node) {
466
+ if (t.isStringLiteral(node)) return true;
467
+ return t.isTemplateLiteral(node) && node.expressions.length === 0;
468
+ }
469
+ function collectPropIdentifiers(node, scope, ctx, out = []) {
470
+ if (!node) return out;
471
+ const visit = (child) => collectPropIdentifiers(child, scope, ctx, out);
472
+ if (t.isIdentifier(node)) {
473
+ const prop = resolveProp(node.name, scope, ctx);
474
+ if (prop && !out.includes(prop)) out.push(prop);
475
+ return out;
476
+ }
477
+ if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
478
+ visit(node.object);
479
+ if (node.computed) visit(node.property);
480
+ return out;
481
+ }
482
+ if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
483
+ visit(node.callee);
484
+ node.arguments.forEach((arg) => visit(arg));
485
+ return out;
486
+ }
487
+ if (t.isTemplateLiteral(node)) {
488
+ node.expressions.forEach((expr) => visit(expr));
489
+ return out;
490
+ }
491
+ if (t.isConditionalExpression(node)) {
492
+ visit(node.test);
493
+ visit(node.consequent);
494
+ visit(node.alternate);
495
+ return out;
496
+ }
497
+ if (t.isLogicalExpression(node) || t.isBinaryExpression(node)) {
498
+ visit(node.left);
499
+ visit(node.right);
500
+ return out;
501
+ }
502
+ if (t.isUnaryExpression(node) || t.isAwaitExpression(node)) {
503
+ visit(node.argument);
504
+ return out;
505
+ }
506
+ if (t.isArrayExpression(node)) {
507
+ node.elements.forEach((el) => visit(el));
508
+ return out;
509
+ }
510
+ if (t.isObjectExpression(node)) {
511
+ for (const prop of node.properties) {
512
+ if (t.isObjectProperty(prop)) {
513
+ if (prop.computed) visit(prop.key);
514
+ visit(prop.value);
515
+ } else if (t.isSpreadElement(prop)) {
516
+ visit(prop.argument);
517
+ }
518
+ }
519
+ return out;
520
+ }
521
+ if (t.isSpreadElement(node)) {
522
+ visit(node.argument);
523
+ return out;
524
+ }
525
+ if (t.isTSAsExpression(node) || t.isTSNonNullExpression(node) || t.isTypeCastExpression(node)) {
526
+ visit(node.expression);
527
+ return out;
528
+ }
529
+ return out;
530
+ }
531
+ function classifyExpression(expr, scope, ctx, hop) {
532
+ if (t.isIdentifier(expr)) {
533
+ const prop = resolveProp(expr.name, scope, ctx);
534
+ if (prop) return { kind: "direct", prop };
535
+ if (hop > 0) return null;
536
+ const local = resolveLocalConstInit(expr.name, scope, ctx);
537
+ if (!local) return null;
538
+ const inner = classifyExpression(local.init, local.scope, ctx, hop + 1);
539
+ if (!inner) return null;
540
+ return { kind: inner.kind === "formatted" ? "formatted" : "derived", prop: inner.prop };
541
+ }
542
+ if (t.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??") && isStaticStringLiteral(expr.right)) {
543
+ const left = classifyExpression(expr.left, scope, ctx, hop);
544
+ if (!left) return null;
545
+ return { kind: left.kind === "formatted" ? "formatted" : "derived", prop: left.prop };
546
+ }
547
+ const props = collectPropIdentifiers(expr, scope, ctx);
548
+ return props.length > 0 ? { kind: "formatted", prop: props[0] } : null;
549
+ }
550
+ function meaningfulChildren(children) {
551
+ return children.filter((child) => {
552
+ if (t.isJSXText(child)) return child.value.trim() !== "";
553
+ if (t.isJSXExpressionContainer(child)) return !t.isJSXEmptyExpression(child.expression);
554
+ return true;
555
+ });
556
+ }
557
+ function collectPropBindings(path2, attrs) {
558
+ const ctx = resolvePropContext(path2);
559
+ if (!ctx || ctx.propMap.size === 0) return { bindings: [], owner: null };
560
+ if (ctx.componentName === null) return { bindings: [], owner: null };
561
+ const scope = path2.scope;
562
+ const bindings = [];
563
+ const elementPath = path2.parentPath;
564
+ if (elementPath && elementPath.isJSXElement()) {
565
+ const children = meaningfulChildren(elementPath.node.children);
566
+ const isSoleChild = children.length === 1;
567
+ for (const child of children) {
568
+ if (!t.isJSXExpressionContainer(child)) continue;
569
+ const expression = child.expression;
570
+ if (t.isJSXEmptyExpression(expression)) continue;
571
+ const verdict = classifyExpression(expression, scope, ctx, 0);
572
+ if (!verdict) continue;
573
+ bindings.push({
574
+ target: "children",
575
+ prop: verdict.prop,
576
+ kind: isSoleChild ? verdict.kind : "formatted"
577
+ });
578
+ }
579
+ }
580
+ for (const attr of attrs) {
581
+ if (!t.isJSXAttribute(attr)) continue;
582
+ const name = t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
583
+ if (NON_BINDING_ATTRS.has(name)) continue;
584
+ const value = attr.value;
585
+ if (!t.isJSXExpressionContainer(value)) continue;
586
+ const expression = value.expression;
587
+ if (t.isJSXEmptyExpression(expression)) continue;
588
+ const verdict = classifyExpression(expression, scope, ctx, 0);
589
+ if (!verdict) continue;
590
+ bindings.push({
591
+ target: `attr:${name}`,
592
+ prop: verdict.prop,
593
+ // M2b: machine-contract attributes are reported, never offered.
594
+ kind: isNonContentAttribute(name) ? "formatted" : verdict.kind
595
+ });
596
+ }
597
+ return { bindings, owner: bindings.length > 0 ? ctx.componentName : null };
598
+ }
599
+ function propBindingsToAst(bindings) {
600
+ return t.arrayExpression(
601
+ bindings.map(
602
+ (binding) => t.objectExpression([
603
+ t.objectProperty(t.identifier("target"), t.stringLiteral(binding.target)),
604
+ t.objectProperty(t.identifier("prop"), t.stringLiteral(binding.prop)),
605
+ t.objectProperty(t.identifier("kind"), t.stringLiteral(binding.kind))
606
+ ])
607
+ )
608
+ );
332
609
  }
333
610
  return {
334
611
  name: "babel-plugin-editor-meta",
@@ -356,25 +633,13 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
356
633
  if (!propsArg || t.isNullLiteral(propsArg)) return;
357
634
  if (!t.isExpression(propsArg)) return;
358
635
  if (isMetaStripSpreadExpression(propsArg)) return;
359
- args[1] = t.callExpression(
360
- t.arrowFunctionExpression(
361
- [
362
- t.objectPattern([
363
- t.objectProperty(
364
- t.identifier("__editorMeta"),
365
- t.identifier("_$em"),
366
- false,
367
- false
368
- ),
369
- t.restElement(t.identifier("_$r"))
370
- ])
371
- ],
372
- t.identifier("_$r")
373
- ),
374
- [t.objectExpression([t.spreadElement(propsArg)])]
375
- );
636
+ args[1] = buildPayloadFilter(propsArg);
376
637
  },
377
638
  JSXOpeningElement(path2, state) {
639
+ const consumed = consumeKitSlot(path2.node.attributes);
640
+ path2.node.attributes = consumed.attrs;
641
+ const slot = consumed.slot;
642
+ const hasSlotAttr = consumed.found;
378
643
  const filename = state.filename ?? "";
379
644
  if (fileExclude.test(filename)) return;
380
645
  const existing = path2.node.attributes.find(
@@ -420,9 +685,11 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
420
685
  }
421
686
  }
422
687
  const nodeId = computeNodeId(file, line, col, componentName);
423
- const movable = isMovable(path2);
688
+ const isKitOrigin = origin === "kit" || hasSlotAttr;
689
+ const movable = isKitOrigin ? false : isMovable(path2);
424
690
  const parentNodeId = getParentNodeId(path2, file);
425
691
  const staticPropsObj = extractStaticProps(path2.node.attributes);
692
+ const { bindings, owner } = collectPropBindings(path2, path2.node.attributes);
426
693
  const metaObj = t.objectExpression([
427
694
  t.objectProperty(t.identifier("nodeId"), t.stringLiteral(nodeId)),
428
695
  t.objectProperty(t.identifier("file"), t.stringLiteral(file)),
@@ -439,6 +706,22 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
439
706
  parentNodeId ? t.stringLiteral(parentNodeId) : t.nullLiteral()
440
707
  )
441
708
  ]);
709
+ if (slot !== null) {
710
+ metaObj.properties.push(
711
+ t.objectProperty(t.identifier("slot"), t.stringLiteral(slot))
712
+ );
713
+ }
714
+ if (bindings.length > 0) {
715
+ metaObj.properties.push(
716
+ t.objectProperty(t.identifier("propBindings"), propBindingsToAst(bindings))
717
+ );
718
+ metaObj.properties.push(
719
+ t.objectProperty(
720
+ t.identifier("propOwner"),
721
+ owner ? t.stringLiteral(owner) : t.nullLiteral()
722
+ )
723
+ );
724
+ }
442
725
  if (isHostElement && includeHostSourceRef) {
443
726
  path2.node.attributes.push(
444
727
  t.jsxAttribute(
@@ -447,10 +730,11 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
447
730
  )
448
731
  );
449
732
  }
733
+ const metaThunk = t.arrowFunctionExpression([], metaObj);
450
734
  path2.node.attributes.push(
451
735
  t.jsxAttribute(
452
736
  t.jsxIdentifier("__editorMeta"),
453
- t.jsxExpressionContainer(metaObj)
737
+ t.jsxExpressionContainer(metaThunk)
454
738
  )
455
739
  );
456
740
  }
@@ -95,6 +95,28 @@
95
95
  var FIBER_KEY_PREFIX = "__reactFiber$"
96
96
  var PROPS_KEY_PREFIX = "__reactProps$"
97
97
 
98
+ // ARCH-883 P0-1: babel-plugin-editor-meta.ts (TypeScript context) now
99
+ // emits __editorMeta as a zero-arg function, not the object literal
100
+ // directly, so React never writes it into the DOM. Every extraction
101
+ // site below must invoke the value when it is a function. Mirrors
102
+ // readEditorMetaValue in fiber-bridge.ts — if this changes, update
103
+ // that copy to maintain parity.
104
+ function resolveEditorMetaValue(value) {
105
+ if (typeof value === "function") {
106
+ // Each call re-evaluates the plugin-emitted `() => ({...})`
107
+ // closure and returns a FRESH object literal — never rely on
108
+ // meta object identity (`===`) across two separate reads, even
109
+ // within the same render (mirrors readEditorMetaValue in
110
+ // fiber-bridge.ts).
111
+ return value()
112
+ }
113
+ // Twin parity with fiber-bridge.ts's readEditorMetaValue: only
114
+ // undefined/null is a miss, not "any falsy value" — __editorMeta is
115
+ // always an object or a function in practice, so this is a no-op
116
+ // today, but keeps the two implementations provably equivalent.
117
+ return value === undefined || value === null ? null : value
118
+ }
119
+
98
120
  function hasEditorMetaInFiberChain(fiber) {
99
121
  var current = fiber
100
122
  while (current) {
@@ -195,7 +217,7 @@
195
217
  var fiber = getFiberFromElement(el)
196
218
  while (fiber) {
197
219
  if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
198
- return fiber.memoizedProps.__editorMeta
220
+ return resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
199
221
  }
200
222
  fiber = fiber["return"]
201
223
  }
@@ -205,7 +227,7 @@
205
227
  function getDirectEditorMetaFromElement(el) {
206
228
  var fiber = getFiberFromElement(el)
207
229
  if (fiber && fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
208
- return fiber.memoizedProps.__editorMeta
230
+ return resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
209
231
  }
210
232
  return null
211
233
  }
@@ -231,7 +253,7 @@
231
253
  break
232
254
  }
233
255
  if (current.memoizedProps && current.memoizedProps.__editorMeta) {
234
- return current.memoizedProps.__editorMeta
256
+ return resolveEditorMetaValue(current.memoizedProps.__editorMeta)
235
257
  }
236
258
  current = current["return"]
237
259
  }
@@ -252,6 +274,156 @@
252
274
  return null
253
275
  }
254
276
 
277
+ // ── ARCH-883 F2 — usage chain + instance identity ───────────────────
278
+ // Vanilla twins of collectEditorMetaChain (fiber-bridge.ts) and
279
+ // resolveInstanceIdentity (instance-identity.ts). Those files carry the
280
+ // full rationale; keep the three in step.
281
+ var MAX_META_CHAIN = 24
282
+
283
+ // Every __editorMeta on the fiber return chain, innermost first. The host
284
+ // needs the FULL metas (staticProps/propBindings/propOwner), not the
285
+ // narrowed inspectorSource shape: routing a kit-internal node means
286
+ // finding the USAGE above it and reading the prop value AT that usage.
287
+ function collectEditorMetaChain(el) {
288
+ var chain = []
289
+ var fiber = getFiberFromElement(el)
290
+ while (fiber && chain.length < MAX_META_CHAIN) {
291
+ if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
292
+ var meta = resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
293
+ if (meta) chain.push(meta)
294
+ }
295
+ fiber = fiber["return"]
296
+ }
297
+ return chain
298
+ }
299
+
300
+ var MAX_ROW_ROOT_DEPTH = 12
301
+ var MAX_KEY_WALK = 40
302
+
303
+ // The fiber that OWNS the nearest __editorMeta for `el`. Vanilla twin of
304
+ // getEditorMetaOwnerFiber (fiber-bridge.ts).
305
+ function getEditorMetaOwnerFiberFromElement(el) {
306
+ var fiber = getFiberFromElement(el)
307
+ while (fiber) {
308
+ if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
309
+ return fiber
310
+ }
311
+ fiber = fiber["return"]
312
+ }
313
+ return null
314
+ }
315
+
316
+ // The SINGLE host element `owner` renders, or null when it renders zero
317
+ // or several, or crosses a HostPortal (tag 4). Vanilla twin of
318
+ // getSingleHostElement (fiber-bridge.ts) — conservative by design.
319
+ function getSingleHostElementFromFiber(owner) {
320
+ var hosts = []
321
+ var stack = owner && owner.child ? [owner.child] : []
322
+ while (stack.length > 0) {
323
+ var fiber = stack.pop()
324
+ if (!fiber) continue
325
+ if (fiber.tag === 4) return null
326
+ if (
327
+ typeof fiber.type === "string" &&
328
+ fiber.stateNode &&
329
+ fiber.stateNode.nodeType === 1
330
+ ) {
331
+ hosts.push(fiber.stateNode)
332
+ if (hosts.length > 1) return null
333
+ } else if (fiber.child) {
334
+ stack.push(fiber.child)
335
+ }
336
+ if (fiber.sibling) stack.push(fiber.sibling)
337
+ }
338
+ return hosts.length === 1 ? hosts[0] : null
339
+ }
340
+
341
+ // Vanilla twin of projectUsageMeta (useElementScanner.ts): the usage meta
342
+ // when `el` is the SINGLE host root a component usage renders and that
343
+ // component destructured its props, so the host fiber carries no meta.
344
+ function projectUsageMetaFromElement(el) {
345
+ if (getDirectEditorMetaFromElement(el)) return null
346
+ var owner = getEditorMetaOwnerFiberFromElement(el)
347
+ if (!owner) return null
348
+ if (getSingleHostElementFromFiber(owner) !== el) return null
349
+ return resolveEditorMetaValue(owner.memoizedProps.__editorMeta)
350
+ }
351
+
352
+ // The nodeId this element carries AS ITS OWN — direct fiber meta, or the
353
+ // single-host projection. NEVER an inherited ancestor nodeId: walking the
354
+ // fiber chain here made every UNSTAMPED subtree (any two siblings
355
+ // rendered by a component the plugin never transformed — anything from
356
+ // node_modules, and here SVG internals too, since this twin accepts any
357
+ // element node) look like a repeated collection. Meanwhile a
358
+ // direct-meta-ONLY rule would make every kit component that destructures
359
+ // its props look like a singleton — which is the fail-OPEN mistake, and
360
+ // would write a literal into a mapped usage, changing every row.
361
+ // See instance-identity.ts for the full rationale.
362
+ function ownMetaNodeId(el) {
363
+ if (!el || el.nodeType !== 1) return null
364
+ var direct = getDirectEditorMetaFromElement(el)
365
+ if (direct && direct.nodeId) return direct.nodeId
366
+ var projected = projectUsageMetaFromElement(el)
367
+ return projected && projected.nodeId ? projected.nodeId : null
368
+ }
369
+
370
+ // The repeated-row boundary above `el`, or null. INNERMOST repetition
371
+ // wins (a tag inside a card inside a list is identified by the tag's key).
372
+ function findCollectionWindow(el) {
373
+ var node = el
374
+ for (var depth = 0; depth < MAX_ROW_ROOT_DEPTH; depth++) {
375
+ var parent = node.parentElement
376
+ if (!parent) return null
377
+
378
+ var nodeId = ownMetaNodeId(node)
379
+ if (nodeId !== null) {
380
+ var twins = []
381
+ for (var i = 0; i < parent.children.length; i++) {
382
+ var child = parent.children[i]
383
+ if (ownMetaNodeId(child) === nodeId) twins.push(child)
384
+ }
385
+ if (twins.length >= 2) {
386
+ return {
387
+ rowRoot: node,
388
+ container: parent,
389
+ ordinal: twins.indexOf(node),
390
+ }
391
+ }
392
+ }
393
+
394
+ node = parent
395
+ }
396
+ return null
397
+ }
398
+
399
+ // Nearest React key BETWEEN the row root and the collection container.
400
+ // Stopping at the container is the point: a keyed ancestor above it
401
+ // identifies a page, not a row.
402
+ function keyWithinWindow(rowRoot, container) {
403
+ var fiber = getFiberFromElement(rowRoot)
404
+ for (var steps = 0; fiber && steps < MAX_KEY_WALK; steps++) {
405
+ if (fiber.stateNode === container) return null
406
+ var key = fiber.key
407
+ if (typeof key === "string" && key.length > 0) return key
408
+ if (typeof key === "number") return String(key)
409
+ fiber = fiber["return"]
410
+ }
411
+ return null
412
+ }
413
+
414
+ // WHICH instance of a possibly-repeated node this element is.
415
+ // `scanOrdinal` is telemetry/UI only — the host must REJECT an intent
416
+ // with inCollection && fiberKey === null, never fall back to it.
417
+ function resolveInstanceIdentity(el) {
418
+ var win = findCollectionWindow(el)
419
+ if (!win) return { fiberKey: null, scanOrdinal: 0, inCollection: false }
420
+ return {
421
+ fiberKey: keyWithinWindow(win.rowRoot, win.container),
422
+ scanOrdinal: win.ordinal,
423
+ inCollection: true,
424
+ }
425
+ }
426
+
255
427
  if (window._inspectorSharedTargetObserver) {
256
428
  try {
257
429
  window._inspectorSharedTargetObserver.disconnect()
@@ -864,9 +1036,14 @@
864
1036
  postMessageToParent({
865
1037
  type: "INSPECTOR_SCRIPT_LOADED",
866
1038
  timestamp: Date.now(),
867
- version: "2.1.0",
1039
+ version: "2.1.1",
868
1040
  protocolVersion: 1,
869
1041
  capabilities: {
1042
+ // 2.1.1: function-valued __editorMeta props are invoked before any
1043
+ // postMessage, so meta payloads stay structured-cloneable. Hosts can
1044
+ // gate on this bit to confirm the deployed CDN artifact carries the
1045
+ // fix (the 2.1.0 URL is overwritten in place on publish).
1046
+ functionValuedEditorMeta: true,
870
1047
  inspect: true,
871
1048
  dndMove: true,
872
1049
  dndResize: true,
@@ -2020,7 +2197,49 @@
2020
2197
  : null,
2021
2198
  attributes: attributes,
2022
2199
  inspectorSource: inspectorInfo,
2200
+ // ARCH-883 F1 — FORWARD THE META WHOLE. The plugin now also emits
2201
+ // `slot`, `propBindings` and `propOwner` (SDD §2.3/§2.6) and the editor
2202
+ // host routes edits off them; field-picking here would silently strip
2203
+ // them and turn every kit-internal edit into a gap. `inspectorSource`
2204
+ // above is the deliberately narrow structure — this one is not.
2205
+ //
2206
+ // Unlike P0-1 this needs no deploy ordering: the extra keys are plain
2207
+ // data, so an already-published standalone.js forwards them unchanged
2208
+ // through `postMessage`'s structured clone. Whether a given PAGE has
2209
+ // them depends on the plugin that built it, which the host resolves via
2210
+ // `deriveEditorCapabilities(kitRuntimeVersion)` (SDD §2.5) — never from
2211
+ // this script's version. The slot string is opaque here; validating it
2212
+ // against kit-runtime's `slot-vocabulary` is the host's job.
2023
2213
  editorMeta: editorMeta ? editorMeta.meta : null,
2214
+ // ARCH-883 F2 — the two things the host cannot derive from
2215
+ // `editorMeta` alone (SDD §2.9):
2216
+ //
2217
+ // editorMetaChain: routing a kit-internal node needs the USAGE
2218
+ // (`<Kit.AuthFrame headline="…"/>` in screens/<slug>.jsx), which
2219
+ // is NOT the nearest meta — the nearest meta is the <h1> inside
2220
+ // AuthFrame's own template. The host locates the usage by
2221
+ // matching `propOwner` against a chain entry's componentName and
2222
+ // applies the usage-value law to that entry's staticProps. Both
2223
+ // fields live only on the full meta.
2224
+ //
2225
+ // editorInstance: WHICH row of a repeated node was clicked. The
2226
+ // nodeId is a source position and collides across instances; the
2227
+ // only identity that survives an insert above the edited row is
2228
+ // React's `key`, which exists on the fiber and nowhere else.
2229
+ //
2230
+ // Both are plain data, so an already-published copy of this script
2231
+ // forwards them through structured clone unchanged.
2232
+ //
2233
+ // The chain starts at the element the meta was FOUND on (the same
2234
+ // element `editorMeta` above describes) — a clicked node with no
2235
+ // fiber of its own would otherwise yield an empty chain while
2236
+ // reporting a meta resolved from its parent. The instance walk starts
2237
+ // at the CLICKED element, so an inner collection wins over an outer
2238
+ // one (a tag inside a card inside a list).
2239
+ editorMetaChain: collectEditorMetaChain(
2240
+ editorMeta ? editorMeta.element : element
2241
+ ),
2242
+ editorInstance: resolveInstanceIdentity(element),
2024
2243
  computedStyles: {
2025
2244
  display: computed.display,
2026
2245
  position: computed.position,