@archie/devtools 0.0.23 → 0.0.25

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.
@@ -274,6 +274,185 @@
274
274
  return null
275
275
  }
276
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
+
427
+ // ARCH-906 — the DURABLE `data-agent-id` anchors on the ANCESTOR chain,
428
+ // innermost first. This is a DOM-attribute walk, not a fiber walk (no twin
429
+ // in fiber-bridge.ts to mirror): it reads the semantic anchors the render
430
+ // stamps (`row:<id>`, `data:<Entity>`, `screen:<slug>`, `job:<id>`) and
431
+ // forwards them so the host can resolve a slice DATA cell.
432
+ //
433
+ // WHY THE ANCESTOR CHAIN, and why it is not `editorInstance`. Clicking a
434
+ // table cell resolves the INNERMOST collection — the `<td key={c.key}>` map
435
+ // — so `editorInstance.fiberKey` already carries the FIELD. The ROW it
436
+ // belongs to lives one level up on `<tr data-agent-id="row:<id>">`, and the
437
+ // ENTITY on a container above that (`data-agent-id="data:<Entity>"`).
438
+ // Neither can be the innermost collection key, so the host cannot recover
439
+ // them from `editorInstance` — it needs the ancestor anchors verbatim.
440
+ // Strict ancestors only (the selected node's own anchor is already in
441
+ // `attributes`); the host picks the nearest `row:`/`data:` by namespace.
442
+ var MAX_ANCHOR_CHAIN = 24
443
+ function collectAncestorAnchors(el) {
444
+ var anchors = []
445
+ var node = el && el.parentElement ? el.parentElement : null
446
+ for (var depth = 0; node && depth < MAX_ANCHOR_CHAIN; depth++) {
447
+ if (node.nodeType === 1 && typeof node.getAttribute === "function") {
448
+ var id = node.getAttribute("data-agent-id")
449
+ if (typeof id === "string" && id !== "") anchors.push(id)
450
+ }
451
+ node = node.parentElement
452
+ }
453
+ return anchors
454
+ }
455
+
277
456
  if (window._inspectorSharedTargetObserver) {
278
457
  try {
279
458
  window._inspectorSharedTargetObserver.disconnect()
@@ -2061,6 +2240,42 @@
2061
2240
  // this script's version. The slot string is opaque here; validating it
2062
2241
  // against kit-runtime's `slot-vocabulary` is the host's job.
2063
2242
  editorMeta: editorMeta ? editorMeta.meta : null,
2243
+ // ARCH-883 F2 — the two things the host cannot derive from
2244
+ // `editorMeta` alone (SDD §2.9):
2245
+ //
2246
+ // editorMetaChain: routing a kit-internal node needs the USAGE
2247
+ // (`<Kit.AuthFrame headline="…"/>` in screens/<slug>.jsx), which
2248
+ // is NOT the nearest meta — the nearest meta is the <h1> inside
2249
+ // AuthFrame's own template. The host locates the usage by
2250
+ // matching `propOwner` against a chain entry's componentName and
2251
+ // applies the usage-value law to that entry's staticProps. Both
2252
+ // fields live only on the full meta.
2253
+ //
2254
+ // editorInstance: WHICH row of a repeated node was clicked. The
2255
+ // nodeId is a source position and collides across instances; the
2256
+ // only identity that survives an insert above the edited row is
2257
+ // React's `key`, which exists on the fiber and nowhere else.
2258
+ //
2259
+ // Both are plain data, so an already-published copy of this script
2260
+ // forwards them through structured clone unchanged.
2261
+ //
2262
+ // The chain starts at the element the meta was FOUND on (the same
2263
+ // element `editorMeta` above describes) — a clicked node with no
2264
+ // fiber of its own would otherwise yield an empty chain while
2265
+ // reporting a meta resolved from its parent. The instance walk starts
2266
+ // at the CLICKED element, so an inner collection wins over an outer
2267
+ // one (a tag inside a card inside a list).
2268
+ editorMetaChain: collectEditorMetaChain(
2269
+ editorMeta ? editorMeta.element : element
2270
+ ),
2271
+ editorInstance: resolveInstanceIdentity(element),
2272
+ // ARCH-906 — the ancestor `data-agent-id` chain (innermost first). A
2273
+ // slice DATA cell needs the ROW id (`row:<id>` on the `<tr>`) and the
2274
+ // ENTITY (`data:<Entity>` above it), neither of which is the innermost
2275
+ // collection key `editorInstance` carries. Plain string data, so an
2276
+ // already-published copy of this script forwards it unchanged through
2277
+ // structured clone; a host that does not read it is unaffected.
2278
+ ancestorAnchors: collectAncestorAnchors(element),
2064
2279
  computedStyles: {
2065
2280
  display: computed.display,
2066
2281
  position: computed.position,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
5
  "repository": {
6
6
  "type": "git",