@archie/devtools 0.0.29 → 0.0.31

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.
@@ -6696,6 +6696,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6696
6696
  const lastIntentRef = (0, import_react11.useRef)(null);
6697
6697
  const visualIntentRef = (0, import_react11.useRef)(null);
6698
6698
  const sourceContainerIdRef = (0, import_react11.useRef)(null);
6699
+ const rowLockedRef = (0, import_react11.useRef)(false);
6699
6700
  const dragSessionAnchorRef = (0, import_react11.useRef)(null);
6700
6701
  const lockRef = (0, import_react11.useRef)({ containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null });
6701
6702
  const targetPlaneRef = (0, import_react11.useRef)("lane");
@@ -6727,6 +6728,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6727
6728
  lastIntentRef,
6728
6729
  visualIntentRef,
6729
6730
  sourceContainerIdRef,
6731
+ rowLockedRef,
6730
6732
  dragSessionAnchorRef,
6731
6733
  lockRef,
6732
6734
  targetPlaneRef,
@@ -7632,6 +7634,12 @@ function buildPreviewLayout({
7632
7634
  var CONTAINER_SWITCH_HYSTERESIS = 12;
7633
7635
  var SMALL_NODE_NEST_DWELL_MS = 100;
7634
7636
  var LARGE_NODE_NEST_DWELL_MS = 320;
7637
+ function computeAllowNest(nestPreview, isLargeDraggedNode, rowLocked) {
7638
+ if (rowLocked) return false;
7639
+ if (!nestPreview) return false;
7640
+ const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7641
+ return !!childId && !isLargeDraggedNode;
7642
+ }
7635
7643
  function useDragVisualLoop({
7636
7644
  activeDrag,
7637
7645
  refs,
@@ -7681,11 +7689,7 @@ function useDragVisualLoop({
7681
7689
  const adjustedPy = py + scrollDy;
7682
7690
  const nestPreview = engine.peekNestIntent(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7683
7691
  const isLargeDraggedNode = shouldBlockNestForLargeNode(activeDrag.w, activeDrag.h);
7684
- let allowNest = false;
7685
- if (nestPreview) {
7686
- const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7687
- allowNest = !!childId && !isLargeDraggedNode;
7688
- }
7692
+ const allowNest = computeAllowNest(nestPreview, isLargeDraggedNode, !!refs.rowLockedRef.current);
7689
7693
  if (!allowNest) {
7690
7694
  refs.nestCandidateRef.current = null;
7691
7695
  refs.nestTargetRef.current = null;
@@ -7870,6 +7874,18 @@ function useDragVisualLoop({
7870
7874
 
7871
7875
  // src/client/dnd/design-mode/useDragLifecycle.ts
7872
7876
  var import_react13 = require("react");
7877
+ function resolveLaneAllowedContainers(anchorElement, srcContainerId) {
7878
+ if (!srcContainerId) return null;
7879
+ if (!resolveRowBinding(anchorElement)) return null;
7880
+ return /* @__PURE__ */ new Set([srcContainerId]);
7881
+ }
7882
+ function buildDragIntentEngineOptions(anchorElement, srcContainerId, blockedContainerIds) {
7883
+ return {
7884
+ laneRootContainerId: srcContainerId,
7885
+ laneAllowedContainers: resolveLaneAllowedContainers(anchorElement, srcContainerId),
7886
+ blockedContainerIds
7887
+ };
7888
+ }
7873
7889
  function buildRuntimeIdentityLocal(nodeId, resolveNodeElement, effectiveLaneSnapshot) {
7874
7890
  if (!nodeId) return null;
7875
7891
  const element = resolveNodeElement(nodeId);
@@ -8069,6 +8085,7 @@ function useDragLifecycle({
8069
8085
  refs.lastIntentRef.current = null;
8070
8086
  refs.visualIntentRef.current = null;
8071
8087
  refs.sourceContainerIdRef.current = null;
8088
+ refs.rowLockedRef.current = false;
8072
8089
  refs.dragSessionAnchorRef.current = null;
8073
8090
  refs.lockRef.current = { containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8074
8091
  refs.targetPlaneRef.current = "lane";
@@ -8124,10 +8141,9 @@ function useDragLifecycle({
8124
8141
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8125
8142
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8126
8143
  refs.blockedContainerIdsRef.current = blockedContainerIds;
8127
- refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, {
8128
- laneRootContainerId: srcContainerId,
8129
- blockedContainerIds
8130
- });
8144
+ const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8145
+ refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8146
+ refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
8131
8147
  refs.engineRef.current.setTargetPlane("lane");
8132
8148
  refs.targetPlaneRef.current = "lane";
8133
8149
  refs.sourceContainerIdRef.current = srcContainerId;
@@ -6661,6 +6661,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6661
6661
  const lastIntentRef = useRef8(null);
6662
6662
  const visualIntentRef = useRef8(null);
6663
6663
  const sourceContainerIdRef = useRef8(null);
6664
+ const rowLockedRef = useRef8(false);
6664
6665
  const dragSessionAnchorRef = useRef8(null);
6665
6666
  const lockRef = useRef8({ containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null });
6666
6667
  const targetPlaneRef = useRef8("lane");
@@ -6692,6 +6693,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6692
6693
  lastIntentRef,
6693
6694
  visualIntentRef,
6694
6695
  sourceContainerIdRef,
6696
+ rowLockedRef,
6695
6697
  dragSessionAnchorRef,
6696
6698
  lockRef,
6697
6699
  targetPlaneRef,
@@ -7597,6 +7599,12 @@ function buildPreviewLayout({
7597
7599
  var CONTAINER_SWITCH_HYSTERESIS = 12;
7598
7600
  var SMALL_NODE_NEST_DWELL_MS = 100;
7599
7601
  var LARGE_NODE_NEST_DWELL_MS = 320;
7602
+ function computeAllowNest(nestPreview, isLargeDraggedNode, rowLocked) {
7603
+ if (rowLocked) return false;
7604
+ if (!nestPreview) return false;
7605
+ const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7606
+ return !!childId && !isLargeDraggedNode;
7607
+ }
7600
7608
  function useDragVisualLoop({
7601
7609
  activeDrag,
7602
7610
  refs,
@@ -7646,11 +7654,7 @@ function useDragVisualLoop({
7646
7654
  const adjustedPy = py + scrollDy;
7647
7655
  const nestPreview = engine.peekNestIntent(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7648
7656
  const isLargeDraggedNode = shouldBlockNestForLargeNode(activeDrag.w, activeDrag.h);
7649
- let allowNest = false;
7650
- if (nestPreview) {
7651
- const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7652
- allowNest = !!childId && !isLargeDraggedNode;
7653
- }
7657
+ const allowNest = computeAllowNest(nestPreview, isLargeDraggedNode, !!refs.rowLockedRef.current);
7654
7658
  if (!allowNest) {
7655
7659
  refs.nestCandidateRef.current = null;
7656
7660
  refs.nestTargetRef.current = null;
@@ -7835,6 +7839,18 @@ function useDragVisualLoop({
7835
7839
 
7836
7840
  // src/client/dnd/design-mode/useDragLifecycle.ts
7837
7841
  import { useState as useState5, useCallback as useCallback6 } from "react";
7842
+ function resolveLaneAllowedContainers(anchorElement, srcContainerId) {
7843
+ if (!srcContainerId) return null;
7844
+ if (!resolveRowBinding(anchorElement)) return null;
7845
+ return /* @__PURE__ */ new Set([srcContainerId]);
7846
+ }
7847
+ function buildDragIntentEngineOptions(anchorElement, srcContainerId, blockedContainerIds) {
7848
+ return {
7849
+ laneRootContainerId: srcContainerId,
7850
+ laneAllowedContainers: resolveLaneAllowedContainers(anchorElement, srcContainerId),
7851
+ blockedContainerIds
7852
+ };
7853
+ }
7838
7854
  function buildRuntimeIdentityLocal(nodeId, resolveNodeElement, effectiveLaneSnapshot) {
7839
7855
  if (!nodeId) return null;
7840
7856
  const element = resolveNodeElement(nodeId);
@@ -8034,6 +8050,7 @@ function useDragLifecycle({
8034
8050
  refs.lastIntentRef.current = null;
8035
8051
  refs.visualIntentRef.current = null;
8036
8052
  refs.sourceContainerIdRef.current = null;
8053
+ refs.rowLockedRef.current = false;
8037
8054
  refs.dragSessionAnchorRef.current = null;
8038
8055
  refs.lockRef.current = { containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8039
8056
  refs.targetPlaneRef.current = "lane";
@@ -8089,10 +8106,9 @@ function useDragLifecycle({
8089
8106
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8090
8107
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8091
8108
  refs.blockedContainerIdsRef.current = blockedContainerIds;
8092
- refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, {
8093
- laneRootContainerId: srcContainerId,
8094
- blockedContainerIds
8095
- });
8109
+ const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8110
+ refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8111
+ refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
8096
8112
  refs.engineRef.current.setTargetPlane("lane");
8097
8113
  refs.targetPlaneRef.current = "lane";
8098
8114
  refs.sourceContainerIdRef.current = srcContainerId;
@@ -615,6 +615,122 @@
615
615
  }
616
616
  }
617
617
 
618
+ // ARCH-1065 M3 (v2.3.0) — the nearest data-agent-row-bearing element
619
+ // SELF-outward. This is `nearestRowId` above but returning the ELEMENT
620
+ // (not just its id), because QUERY_COLLECTION_ORDER needs the row
621
+ // root's sibling `data-agent-entity`/`data-agent-collection-order`
622
+ // frame too, not only the row id. Nested collections resolve correctly
623
+ // "for free": walking self-outward finds the INNERMOST row root first,
624
+ // i.e. the nearest enclosing collection of the target node.
625
+ function nearestRowRootElement(el) {
626
+ var node = el
627
+ for (var depth = 0; node && depth < MAX_ANCHOR_CHAIN; depth++) {
628
+ if (node.nodeType === 1 && typeof node.getAttribute === "function") {
629
+ var rowId = node.getAttribute("data-agent-row")
630
+ if (typeof rowId === "string" && rowId !== "") return node
631
+ }
632
+ node = node.parentElement
633
+ }
634
+ return null
635
+ }
636
+
637
+ // The first row-root element stamped with the given entity name,
638
+ // anywhere in the document. Used when QUERY_COLLECTION_ORDER is
639
+ // addressed by `entity` alone (no `selector`) — the entity-name
640
+ // targeting mode.
641
+ function findFirstRowRootForEntity(entity) {
642
+ if (typeof entity !== "string" || entity === "") return null
643
+ try {
644
+ return safeQuerySelector(
645
+ '[data-agent-entity="' +
646
+ escapeAttributeSelectorValue(entity) +
647
+ '"][data-agent-row]'
648
+ )
649
+ } catch (e) {
650
+ return null
651
+ }
652
+ }
653
+
654
+ // Every row root that belongs to the SAME collection instantiation as
655
+ // `rowRootEl`: the babel codemod (M2) stamps each `.map()` row's root
656
+ // element as a DIRECT child of whatever wraps the `.map()` call, so
657
+ // siblings sharing `data-agent-entity` under `rowRootEl`'s parent ARE
658
+ // the collection, in DOM order. Falls back to `[rowRootEl]` alone when
659
+ // no parent is reachable (defensive — should not happen for an
660
+ // attached element).
661
+ function collectionRowElements(rowRootEl, entity) {
662
+ var parent = rowRootEl.parentElement
663
+ if (!parent) return [rowRootEl]
664
+ var siblings = []
665
+ var kids = parent.children
666
+ for (var i = 0; i < kids.length; i++) {
667
+ var kid = kids[i]
668
+ if (
669
+ kid.nodeType === 1 &&
670
+ typeof kid.getAttribute === "function" &&
671
+ kid.getAttribute("data-agent-entity") === entity &&
672
+ kid.hasAttribute("data-agent-row")
673
+ ) {
674
+ siblings.push(kid)
675
+ }
676
+ }
677
+ return siblings.length > 0 ? siblings : [rowRootEl]
678
+ }
679
+
680
+ // ARCH-1065 M3 (v2.3.0) — {entity, order, rowIds, rowCount} for the
681
+ // collection enclosing a target node, read live off the DOM
682
+ // (data-agent-entity/-row/-collection-order, D18) and cross-checked
683
+ // against window.__slice[entity].length when the slice is available.
684
+ //
685
+ // `order` fails CLOSED ("derived") on anything but an explicit
686
+ // "stable" attribute value. babel-plugin-collection-binding stamps
687
+ // entity/row/collection-order ATOMICALLY — three addAttr calls back to
688
+ // back with no branch between them — and no other emitter of
689
+ // data-agent-row exists. So data-agent-row present WITHOUT
690
+ // data-agent-collection-order can only mean the stamping contract
691
+ // never ran for this element: the order is UNKNOWN, not "stable".
692
+ // Defaulting unknown to "stable" would let the FE permit a drag whose
693
+ // Save can't be honored (silent, user-visible corruption); defaulting
694
+ // to "derived" only disables reorder on markup we can't vouch for.
695
+ function queryCollectionOrder(rowRootEl) {
696
+ if (!rowRootEl) return null
697
+ var entity = rowRootEl.getAttribute("data-agent-entity")
698
+ if (typeof entity !== "string" || entity === "") return null
699
+
700
+ var orderAttr = rowRootEl.getAttribute("data-agent-collection-order")
701
+ var order = orderAttr === "stable" ? "stable" : "derived"
702
+
703
+ var rowElements = collectionRowElements(rowRootEl, entity)
704
+ var rowIds = []
705
+ for (var i = 0; i < rowElements.length; i++) {
706
+ var rowId = rowElements[i].getAttribute("data-agent-row")
707
+ if (typeof rowId === "string" && rowId !== "") rowIds.push(rowId)
708
+ }
709
+ var domRowCount = rowIds.length
710
+
711
+ var sliceRows = resolveSliceEntityRows(entity)
712
+ var sliceRowCount = sliceRows ? sliceRows.length : null
713
+
714
+ // `rowCount` is the A17 primary field — DOM population, the same
715
+ // rows the FE samples values from. `domRowCount`/`sliceRowCount` are
716
+ // the divergence-diagnostic pair (identical to rowCount when the
717
+ // slice agrees, or absent from parity entirely when the slice is
718
+ // unavailable). Known limit: two same-entity `.map()`s that happen
719
+ // to share one DOM parent merge into a single reported collection —
720
+ // rare by construction (siblings of a `.map()` output share a
721
+ // parent by definition), and there is no collection-INSTANCE stamp
722
+ // (only per-row entity/row/order) to disambiguate two side-by-side
723
+ // instantiations of the same entity.
724
+ return {
725
+ entity: entity,
726
+ order: order,
727
+ rowIds: rowIds,
728
+ rowCount: domRowCount,
729
+ domRowCount: domRowCount,
730
+ sliceRowCount: sliceRowCount,
731
+ }
732
+ }
733
+
618
734
  if (window._inspectorSharedTargetObserver) {
619
735
  try {
620
736
  window._inspectorSharedTargetObserver.disconnect()
@@ -1231,7 +1347,10 @@
1231
1347
  // `QUERY_SLICE_FIELD` in the message protocol, and
1232
1348
  // `capabilities.bindingContract` below. No existing field changed
1233
1349
  // shape, so `protocolVersion` stays 1.
1234
- version: "2.2.0",
1350
+ // 2.3.0 (ARCH-1065 A17-support): additive only — `QUERY_COLLECTION_ORDER`
1351
+ // in the message protocol and `capabilities.collectionOrderQuery`
1352
+ // below. No existing field changed shape, so `protocolVersion` stays 1.
1353
+ version: "2.3.0",
1235
1354
  protocolVersion: 1,
1236
1355
  capabilities: {
1237
1356
  // 2.1.1: function-valued __editorMeta props are invoked before any
@@ -1257,6 +1376,12 @@
1257
1376
  // before relying on either — a frozen pre-1065 inspector has
1258
1377
  // neither.
1259
1378
  collectionBinding: true,
1379
+ // ARCH-1065 A17-support (v2.3.0): `QUERY_COLLECTION_ORDER` resolves
1380
+ // the enclosing collection's order ("stable"/"derived") and row
1381
+ // ids/count for a target node, off the same data-agent-* grammar
1382
+ // as `collectionBinding`. Hosts MUST feature-detect on this flag —
1383
+ // a frozen pre-2.3.0 inspector has no handler for the message.
1384
+ collectionOrderQuery: true,
1260
1385
  // The BUILD-TIME contract version this specific page was stamped
1261
1386
  // with, forwarded verbatim from `<meta name="archie-binding-contract">`
1262
1387
  // when the kit build emitted one — null on any page built before
@@ -3881,6 +4006,58 @@
3881
4006
  field: queryField,
3882
4007
  result: queryResult,
3883
4008
  })
4009
+ } else if (event.data && event.data.type === "QUERY_COLLECTION_ORDER") {
4010
+ // ARCH-1065 M3 (v2.3.0) — {order, rowIds, rowCount} for the
4011
+ // collection enclosing a target node, so the FE can refuse
4012
+ // reorder-at-drag on "derived" collections and drive the
4013
+ // repetition-aware enum threshold off rowCount. Mirrors
4014
+ // QUERY_SLICE_FIELD's request/response envelope and graceful
4015
+ // (result: null) failure shape.
4016
+ //
4017
+ // Targeting, in priority order:
4018
+ // 1. `selector` — a CSS selector for ANY element inside the
4019
+ // collection (a row, or a leaf nested inside one); resolved
4020
+ // the same way other selector-addressed messages resolve one
4021
+ // (`findElementBySelector`), then walked up to the nearest
4022
+ // enclosing row root.
4023
+ // 2. `entity` alone (no selector) — the first row root in the
4024
+ // document stamped with that entity name.
4025
+ // 3. neither — mirrors QUERY_SLICE_FIELD's no-rowId fallback:
4026
+ // the currently selected (else hovered) element.
4027
+ var collectionQuerySelector =
4028
+ typeof event.data.selector === "string" && event.data.selector !== ""
4029
+ ? event.data.selector
4030
+ : null
4031
+ var collectionQueryEntity =
4032
+ typeof event.data.entity === "string" && event.data.entity !== ""
4033
+ ? event.data.entity
4034
+ : null
4035
+
4036
+ var collectionRowRoot = null
4037
+ if (collectionQuerySelector) {
4038
+ var collectionTargetEl = findElementBySelector(collectionQuerySelector)
4039
+ collectionRowRoot = collectionTargetEl
4040
+ ? nearestRowRootElement(collectionTargetEl)
4041
+ : null
4042
+ } else if (collectionQueryEntity) {
4043
+ collectionRowRoot = findFirstRowRootForEntity(collectionQueryEntity)
4044
+ } else {
4045
+ var collectionContextEl = getSelectedElement() || highlightedElement
4046
+ collectionRowRoot = collectionContextEl
4047
+ ? nearestRowRootElement(collectionContextEl)
4048
+ : null
4049
+ }
4050
+
4051
+ var collectionQueryResult = queryCollectionOrder(collectionRowRoot)
4052
+
4053
+ postMessageToParent({
4054
+ type: "COLLECTION_ORDER_RESULT",
4055
+ requestId: event.data.requestId,
4056
+ entity: collectionQueryResult
4057
+ ? collectionQueryResult.entity
4058
+ : collectionQueryEntity,
4059
+ result: collectionQueryResult,
4060
+ })
3884
4061
  }
3885
4062
  }
3886
4063
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
5
  "repository": {
6
6
  "type": "git",