@archie/devtools 0.0.26 → 0.0.28

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.
@@ -367,13 +367,28 @@
367
367
  return projected && projected.nodeId ? projected.nodeId : null
368
368
  }
369
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).
370
+ // The repeated-row boundary above `el`, or a search outcome explaining
371
+ // why none was found. INNERMOST repetition wins (a tag inside a card
372
+ // inside a list is identified by the tag's key).
373
+ //
374
+ // ARCH-1065 D8-v1/D26 — three-way outcome, matching instance-identity.ts:
375
+ // a CollectionWindow object (found), the string "exhausted" (walked to
376
+ // the true DOM root having found nothing — a COMPLETED search, safe to
377
+ // report as SINGLETON), or null (gave up without a definitive answer:
378
+ // no ancestor to compare against at all, or the depth budget ran out
379
+ // before reaching the root). The PREVIOUS version of this function
380
+ // collapsed "exhausted" and budget-exhaustion into one `null` verdict
381
+ // that `resolveInstanceIdentity` then always read as SINGLETON — more
382
+ // permissive than the TS twin ever intended: a walk that stopped early
383
+ // because it ran out of depth budget never PROVED there was no
384
+ // collection ancestor above it.
372
385
  function findCollectionWindow(el) {
386
+ if (!el.parentElement) return null
387
+
373
388
  var node = el
374
389
  for (var depth = 0; depth < MAX_ROW_ROOT_DEPTH; depth++) {
375
390
  var parent = node.parentElement
376
- if (!parent) return null
391
+ if (!parent) return "exhausted"
377
392
 
378
393
  var nodeId = ownMetaNodeId(node)
379
394
  if (nodeId !== null) {
@@ -411,12 +426,19 @@
411
426
  return null
412
427
  }
413
428
 
414
- // WHICH instance of a possibly-repeated node this element is.
429
+ // WHICH instance of a possibly-repeated node this element is, or NULL
430
+ // when resolution could not reach a definitive answer (ARCH-1065
431
+ // D8-v1/D26 — matches `resolveInstanceIdentity` in instance-identity.ts;
432
+ // this file cannot import it, so the same three-way outcome is
433
+ // re-derived here rather than left more permissive than the TS twin).
415
434
  // `scanOrdinal` is telemetry/UI only — the host must REJECT an intent
416
- // with inCollection && fiberKey === null, never fall back to it.
435
+ // with inCollection && fiberKey === null, never fall back to it, and
436
+ // must treat a NULL editorInstance exactly like that same refusal.
417
437
  function resolveInstanceIdentity(el) {
438
+ if (!(el instanceof HTMLElement)) return null
418
439
  var win = findCollectionWindow(el)
419
- if (!win) return { fiberKey: null, scanOrdinal: 0, inCollection: false }
440
+ if (win === null) return null
441
+ if (win === "exhausted") return { fiberKey: null, scanOrdinal: 0, inCollection: false }
420
442
  return {
421
443
  fiberKey: keyWithinWindow(win.rowRoot, win.container),
422
444
  scanOrdinal: win.ordinal,
@@ -453,6 +475,146 @@
453
475
  return anchors
454
476
  }
455
477
 
478
+ // ARCH-1065 M3 — the D1 grammar attributes babel-plugin-collection-binding
479
+ // stamps at build time (data-agent-entity/-row/-field/-field-state/
480
+ // -field-parts/-field-slot/-collection-order). One frame per DOM
481
+ // attribute name; a leaf typically carries only `field` (+ `fieldSlot`
482
+ // or `fieldState`/`fieldParts`), while its row-root ancestor carries
483
+ // `entity` + `row` + `collectionOrder` — reading both frames is what
484
+ // lets the host recover the full (entity, field, rowId) triple for a
485
+ // click anywhere inside a bound leaf.
486
+ var BINDING_ATTRS = [
487
+ ["entity", "data-agent-entity"],
488
+ ["row", "data-agent-row"],
489
+ ["field", "data-agent-field"],
490
+ ["fieldState", "data-agent-field-state"],
491
+ ["fieldParts", "data-agent-field-parts"],
492
+ ["fieldSlot", "data-agent-field-slot"],
493
+ ["collectionOrder", "data-agent-collection-order"],
494
+ ]
495
+
496
+ function readBindingFrame(el) {
497
+ if (!el || el.nodeType !== 1 || typeof el.getAttribute !== "function") return null
498
+ var frame = null
499
+ for (var i = 0; i < BINDING_ATTRS.length; i++) {
500
+ var value = el.getAttribute(BINDING_ATTRS[i][1])
501
+ if (value !== null) {
502
+ if (!frame) frame = {}
503
+ frame[BINDING_ATTRS[i][0]] = value
504
+ }
505
+ }
506
+ return frame
507
+ }
508
+
509
+ // Walks SELF-outward (unlike collectAncestorAnchors, which is strict
510
+ // ancestors — a click on a leaf's own `data-agent-field` must appear in
511
+ // its own chain entry), one frame per element carrying >=1 binding
512
+ // attribute, bounded by MAX_ANCHOR_CHAIN (the same bound the ARCH-906
513
+ // anchor walk uses — a binding chain never needs to reach deeper than
514
+ // that one does).
515
+ function collectBindingChain(el) {
516
+ var chain = []
517
+ var node = el
518
+ for (var depth = 0; node && depth < MAX_ANCHOR_CHAIN; depth++) {
519
+ var frame = readBindingFrame(node)
520
+ if (frame) chain.push(frame)
521
+ node = node.parentElement
522
+ }
523
+ return chain
524
+ }
525
+
526
+ // The nearest data-agent-row value SELF-outward, or null. Used to
527
+ // resolve "the currently selected row" for QUERY_SLICE_FIELD when the
528
+ // caller does not pass an explicit rowId.
529
+ function nearestRowId(el) {
530
+ var node = el
531
+ for (var depth = 0; node && depth < MAX_ANCHOR_CHAIN; depth++) {
532
+ if (node.nodeType === 1 && typeof node.getAttribute === "function") {
533
+ var rowId = node.getAttribute("data-agent-row")
534
+ if (typeof rowId === "string" && rowId !== "") return rowId
535
+ }
536
+ node = node.parentElement
537
+ }
538
+ return null
539
+ }
540
+
541
+ // ARCH-1065 — the BUILD-TIME contract version this page was stamped
542
+ // with. Opaque here; the host interprets it (SDD analog of `slot`).
543
+ function readBindingContractMeta() {
544
+ try {
545
+ var metaEl = document.querySelector('meta[name="archie-binding-contract"]')
546
+ if (metaEl && typeof metaEl.getAttribute === "function") {
547
+ var content = metaEl.getAttribute("content")
548
+ return typeof content === "string" && content !== "" ? content : null
549
+ }
550
+ } catch (e) {
551
+ // Best-effort — capability forwarding must never throw at boot.
552
+ }
553
+ return null
554
+ }
555
+
556
+ // ARCH-1065 M3 — `window.__slice` read live for the FE's typed controls
557
+ // (design D7): given an entity + field, the DISTINCT values that field
558
+ // takes across every row of that entity in the current slice (the
559
+ // vocabulary a status-SELECT control offers) and, when a row is known,
560
+ // that row's CURRENT value. Nothing about type/format is STAMPED at
561
+ // build time (per the M1/M2/M3 split) — it is read here, live, so it
562
+ // reflects the actual data the page is showing right now.
563
+ function resolveSliceEntityRows(entity) {
564
+ var slice = window.__slice
565
+ if (!slice || typeof slice !== "object") return null
566
+ var rows = slice[entity]
567
+ return Array.isArray(rows) ? rows : null
568
+ }
569
+
570
+ var ISO_DATE_PATTERN =
571
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$/
572
+
573
+ function inferSliceValueType(value) {
574
+ if (value === null || value === undefined || value === "") return "empty-null"
575
+ if (typeof value === "boolean") return "boolean"
576
+ if (typeof value === "number") return "number"
577
+ if (typeof value === "string") {
578
+ return ISO_DATE_PATTERN.test(value) ? "date" : "string"
579
+ }
580
+ return "unknown"
581
+ }
582
+
583
+ function querySliceField(entity, field, rowId) {
584
+ var rows = resolveSliceEntityRows(entity)
585
+ if (!rows) return null
586
+
587
+ var distinctValues = []
588
+ var seenDistinct = {}
589
+ for (var i = 0; i < rows.length; i++) {
590
+ var candidate = rows[i] ? rows[i][field] : undefined
591
+ if (candidate === undefined) continue
592
+ var key = String(candidate)
593
+ if (!seenDistinct[key]) {
594
+ seenDistinct[key] = true
595
+ distinctValues.push(candidate)
596
+ }
597
+ }
598
+
599
+ var currentValue = null
600
+ if (rowId != null) {
601
+ for (var j = 0; j < rows.length; j++) {
602
+ if (rows[j] && String(rows[j].id) === String(rowId)) {
603
+ var rowValue = rows[j][field]
604
+ currentValue = rowValue === undefined ? null : rowValue
605
+ break
606
+ }
607
+ }
608
+ }
609
+
610
+ var typeSample = currentValue !== null ? currentValue : distinctValues[0]
611
+ return {
612
+ currentValue: currentValue,
613
+ valueType: inferSliceValueType(typeSample),
614
+ distinctValues: distinctValues,
615
+ }
616
+ }
617
+
456
618
  if (window._inspectorSharedTargetObserver) {
457
619
  try {
458
620
  window._inspectorSharedTargetObserver.disconnect()
@@ -1065,7 +1227,11 @@
1065
1227
  postMessageToParent({
1066
1228
  type: "INSPECTOR_SCRIPT_LOADED",
1067
1229
  timestamp: Date.now(),
1068
- version: "2.1.1",
1230
+ // 2.2.0 (ARCH-1065): additive only — `bindingChain` on element data,
1231
+ // `QUERY_SLICE_FIELD` in the message protocol, and
1232
+ // `capabilities.bindingContract` below. No existing field changed
1233
+ // shape, so `protocolVersion` stays 1.
1234
+ version: "2.2.0",
1069
1235
  protocolVersion: 1,
1070
1236
  capabilities: {
1071
1237
  // 2.1.1: function-valued __editorMeta props are invoked before any
@@ -1085,6 +1251,21 @@
1085
1251
  // feature-detect on this flag: a frozen 2.0.x inspector disables
1086
1252
  // design mode on "inspect" and needs the legacy "design" mode.
1087
1253
  unifiedInspectArrange: true,
1254
+ // ARCH-1065: the M2 codemod's data-agent-* grammar is readable
1255
+ // (`bindingChain` on element data) and slice-field-queryable
1256
+ // (`QUERY_SLICE_FIELD`). Hosts MUST feature-detect on this flag
1257
+ // before relying on either — a frozen pre-1065 inspector has
1258
+ // neither.
1259
+ collectionBinding: true,
1260
+ // The BUILD-TIME contract version this specific page was stamped
1261
+ // with, forwarded verbatim from `<meta name="archie-binding-contract">`
1262
+ // when the kit build emitted one — null on any page built before
1263
+ // that meta tag existed, or that never carried collection-bound
1264
+ // data. Opaque to this script: validating/interpreting the value is
1265
+ // the host's job (mirrors how `slot` and `bindingContract` — plural
1266
+ // concept, singular mechanism — are treated as opaque strings
1267
+ // elsewhere in this file).
1268
+ bindingContract: readBindingContractMeta(),
1088
1269
  },
1089
1270
  })
1090
1271
 
@@ -2282,6 +2463,12 @@
2282
2463
  // nodeId is a source position and collides across instances; the
2283
2464
  // only identity that survives an insert above the edited row is
2284
2465
  // React's `key`, which exists on the fiber and nowhere else.
2466
+ // ARCH-1065 D8-v1/D26: NULL here means resolution never reached
2467
+ // a definitive answer (detached node, non-HTMLElement, or the
2468
+ // depth-bounded walk ran out before reaching the DOM root) — the
2469
+ // host MUST treat a null editorInstance exactly like the
2470
+ // existing `inCollection && fiberKey === null` refusal, never as
2471
+ // "not in a collection".
2285
2472
  //
2286
2473
  // Both are plain data, so an already-published copy of this script
2287
2474
  // forwards them through structured clone unchanged.
@@ -2303,6 +2490,12 @@
2303
2490
  // already-published copy of this script forwards it unchanged through
2304
2491
  // structured clone; a host that does not read it is unaffected.
2305
2492
  ancestorAnchors: collectAncestorAnchors(element),
2493
+ // ARCH-1065 M3 — the data-agent-* (D1 grammar) chain, self-outward,
2494
+ // for whatever binding attributes the M2 codemod (or a hand-written
2495
+ // kit component, M1) stamped. A host that does not read it is
2496
+ // unaffected; a pre-1065 page simply yields an empty array (`[]`),
2497
+ // same as `ancestorAnchors` does for a page with no anchors.
2498
+ bindingChain: collectBindingChain(element),
2306
2499
  computedStyles: {
2307
2500
  display: computed.display,
2308
2501
  position: computed.position,
@@ -3656,6 +3849,38 @@
3656
3849
  dispatchHostCommand({
3657
3850
  type: "external-drag-cancel",
3658
3851
  })
3852
+ } else if (event.data && event.data.type === "QUERY_SLICE_FIELD") {
3853
+ // ARCH-1065 M3 — {entity, field} -> {currentValue, valueType,
3854
+ // distinctValues} read live off window.__slice, for the FE's typed
3855
+ // controls (D7). `rowId` is optional: when omitted, the CURRENTLY
3856
+ // SELECTED (falling back to hovered) element's nearest
3857
+ // data-agent-row supplies it — the common case is "the user just
3858
+ // clicked a bound cell and wants its type/vocabulary", where the
3859
+ // row is already implied by that click.
3860
+ var queryEntity = event.data.entity
3861
+ var queryField = event.data.field
3862
+ var queryRowId =
3863
+ typeof event.data.rowId === "string" && event.data.rowId !== ""
3864
+ ? event.data.rowId
3865
+ : null
3866
+
3867
+ var queryResult = null
3868
+ if (typeof queryEntity === "string" && typeof queryField === "string") {
3869
+ var contextElement = queryRowId
3870
+ ? null
3871
+ : getSelectedElement() || highlightedElement
3872
+ var effectiveRowId =
3873
+ queryRowId || (contextElement ? nearestRowId(contextElement) : null)
3874
+ queryResult = querySliceField(queryEntity, queryField, effectiveRowId)
3875
+ }
3876
+
3877
+ postMessageToParent({
3878
+ type: "SLICE_FIELD_RESULT",
3879
+ requestId: event.data.requestId,
3880
+ entity: queryEntity,
3881
+ field: queryField,
3882
+ result: queryResult,
3883
+ })
3659
3884
  }
3660
3885
  }
3661
3886
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,6 +24,11 @@
24
24
  "import": "./dist/client/client.mjs",
25
25
  "require": "./dist/client/client.js"
26
26
  },
27
+ "./collection-binding": {
28
+ "types": "./dist/collection-binding.d.ts",
29
+ "import": "./dist/collection-binding.mjs",
30
+ "require": "./dist/collection-binding.js"
31
+ },
27
32
  "./inspector.standalone.js": "./dist/inspector.standalone.js"
28
33
  },
29
34
  "scripts": {