@archie/devtools 0.0.48 → 0.1.1

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.
@@ -4081,13 +4081,21 @@ function summarizeRuntimeEvent(detail) {
4081
4081
  container: summarizeIdentity(detail.container),
4082
4082
  beforeSibling: summarizeIdentity(detail.beforeSibling),
4083
4083
  sourceContainer: summarizeIdentity(detail.sourceContainer),
4084
- diagnostics: detail.diagnostics ?? null
4084
+ diagnostics: detail.diagnostics ?? null,
4085
+ targetVid: detail.targetVid ?? null,
4086
+ containerVid: detail.containerVid ?? null,
4087
+ index: detail.index
4085
4088
  };
4089
+ case "drag-started":
4090
+ return { targetVid: detail.targetVid, droppableCount: detail.droppable.length };
4091
+ case "drag-refused":
4092
+ return { code: detail.code, cause: detail.cause, vid: detail.vid };
4086
4093
  case "element-resized":
4087
4094
  return {
4088
4095
  operationId: detail.operationId,
4089
4096
  selected: summarizeIdentity(detail.selected),
4090
4097
  anchor: summarizeIdentity(detail.anchor),
4098
+ targetVid: detail.targetVid ?? null,
4091
4099
  resize: {
4092
4100
  mode: detail.resize.mode,
4093
4101
  selectedNodeKey: detail.resize.selectedNodeKey,
@@ -4104,6 +4112,8 @@ function summarizeRuntimeEvent(detail) {
4104
4112
  nodeKey: detail.nodeKey,
4105
4113
  displayName: detail.displayName,
4106
4114
  container: summarizeIdentity(detail.container),
4115
+ containerVid: detail.containerVid ?? null,
4116
+ insertedVid: detail.insertedVid ?? null,
4107
4117
  index: detail.index,
4108
4118
  beforeSibling: summarizeIdentity(detail.beforeSibling),
4109
4119
  hasTsxCode: !!detail.tsxCode,
@@ -4185,6 +4195,58 @@ function isValidLaneContainer(containerId, projection, nodeMap) {
4185
4195
  if (container.children.length === 0) return false;
4186
4196
  return !isStructuralContainer(containerId, nodeMap);
4187
4197
  }
4198
+ function isNonDroppableNodeMeta(meta) {
4199
+ if (!meta) return false;
4200
+ if (meta.containerKind && meta.containerKind !== "plain") return true;
4201
+ if (meta.isChrome) return true;
4202
+ if (meta.isKitOrigin) return true;
4203
+ return false;
4204
+ }
4205
+ function isNonDraggableNodeMeta(meta) {
4206
+ if (!meta) return false;
4207
+ if (meta.dynamicType && meta.dynamicType !== "STATIC") return true;
4208
+ if (meta.isChrome) return true;
4209
+ if (meta.isKitOrigin) return true;
4210
+ return false;
4211
+ }
4212
+ function collectSubtreeIds(rootId, projection) {
4213
+ const ids2 = /* @__PURE__ */ new Set([rootId]);
4214
+ const stack = [rootId];
4215
+ while (stack.length > 0) {
4216
+ const id = stack.pop();
4217
+ const node = projection.containerIndex.get(id);
4218
+ if (!node) continue;
4219
+ for (const childId of node.children) {
4220
+ if (ids2.has(childId)) continue;
4221
+ ids2.add(childId);
4222
+ stack.push(childId);
4223
+ }
4224
+ }
4225
+ return ids2;
4226
+ }
4227
+ function computeDroppableSet(sourceId, projection, nodeMap) {
4228
+ const excluded = collectSubtreeIds(sourceId, projection);
4229
+ const droppable = /* @__PURE__ */ new Set();
4230
+ for (const containerId of projection.containerIndex.keys()) {
4231
+ if (excluded.has(containerId)) continue;
4232
+ if (isStructuralContainer(containerId, nodeMap)) continue;
4233
+ if (isNonDroppableNodeMeta(nodeMap.get(containerId)?.editorMeta)) continue;
4234
+ droppable.add(containerId);
4235
+ }
4236
+ return droppable;
4237
+ }
4238
+ function buildBlockedNodeMetaContainerIds(projection, nodeMap) {
4239
+ const blocked = /* @__PURE__ */ new Set();
4240
+ for (const containerId of projection.containerIndex.keys()) {
4241
+ if (isNonDroppableNodeMeta(nodeMap.get(containerId)?.editorMeta)) {
4242
+ blocked.add(containerId);
4243
+ }
4244
+ }
4245
+ return blocked;
4246
+ }
4247
+ function resolveRefusalCode(meta, fallback) {
4248
+ return meta?.lockReason ?? fallback;
4249
+ }
4188
4250
  function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainerId) {
4189
4251
  const blocked = /* @__PURE__ */ new Set();
4190
4252
  for (const containerId of projection.containerIndex.keys()) {
@@ -4194,6 +4256,9 @@ function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainer
4194
4256
  return blocked;
4195
4257
  }
4196
4258
 
4259
+ // src/client/dnd/design-mode/node-meta-guard.ts
4260
+ var VID_PATTERN = /^[a-z0-9]{6,8}$/;
4261
+
4197
4262
  // src/client/dnd/design-mode/fiber-bridge.ts
4198
4263
  var FIBER_KEY_PREFIX = "__reactFiber$";
4199
4264
  var PROPS_KEY_PREFIX = "__reactProps$";
@@ -4474,11 +4539,18 @@ function getNodeKeyFromElement(el) {
4474
4539
  const resolved = resolveDragSurface(el);
4475
4540
  const host = resolved.host ?? el;
4476
4541
  const meta = identityMeta(host);
4542
+ if (meta?.vid) return meta.vid;
4543
+ const domVid = host.getAttribute("data-vid");
4544
+ if (domVid) return domVid;
4477
4545
  if (meta) return meta.nodeId;
4478
4546
  const persisted = host.getAttribute("data-dnd-node-id");
4479
4547
  if (persisted) return persisted;
4480
4548
  return getRuntimeKey(host);
4481
4549
  }
4550
+ function resolveVidFromElement(el) {
4551
+ const key2 = getNodeKeyFromElement(el);
4552
+ return VID_PATTERN.test(key2) ? key2 : null;
4553
+ }
4482
4554
  function getInspectorRefFromElement(el) {
4483
4555
  const resolved = resolveDragSurface(el);
4484
4556
  const host = resolved.host ?? el;
@@ -4805,6 +4877,8 @@ var useDesignModeStore = create((set, get) => ({
4805
4877
  afterSibling,
4806
4878
  diagnostics,
4807
4879
  binding,
4880
+ targetVid,
4881
+ containerVid,
4808
4882
  componentName,
4809
4883
  before,
4810
4884
  after
@@ -4832,6 +4906,8 @@ var useDesignModeStore = create((set, get) => ({
4832
4906
  afterSibling: afterSibling ?? null,
4833
4907
  diagnostics: diagnostics ?? null,
4834
4908
  binding: binding ?? null,
4909
+ targetVid: targetVid ?? null,
4910
+ containerVid: containerVid ?? null,
4835
4911
  payload: { before, after },
4836
4912
  at: now
4837
4913
  };
@@ -4860,6 +4936,7 @@ var useDesignModeStore = create((set, get) => ({
4860
4936
  nodeId,
4861
4937
  inspectorRef,
4862
4938
  componentName,
4939
+ targetVid,
4863
4940
  before,
4864
4941
  after
4865
4942
  }) => {
@@ -4871,6 +4948,7 @@ var useDesignModeStore = create((set, get) => ({
4871
4948
  nodeKey,
4872
4949
  nodeId,
4873
4950
  inspectorRef,
4951
+ targetVid: targetVid ?? null,
4874
4952
  payload: { before, after },
4875
4953
  at: now
4876
4954
  };
@@ -4904,6 +4982,8 @@ var useDesignModeStore = create((set, get) => ({
4904
4982
  tsxImports,
4905
4983
  containerId,
4906
4984
  containerNodeKey,
4985
+ containerVid,
4986
+ insertedVid,
4907
4987
  index,
4908
4988
  beforeSiblingInspectorRef,
4909
4989
  beforeSiblingNodeKey
@@ -4922,6 +5002,8 @@ var useDesignModeStore = create((set, get) => ({
4922
5002
  tsxImports: tsxImports ?? null,
4923
5003
  containerId,
4924
5004
  containerNodeKey,
5005
+ containerVid: containerVid ?? null,
5006
+ insertedVid: insertedVid ?? null,
4925
5007
  index,
4926
5008
  beforeSiblingInspectorRef: beforeSiblingInspectorRef ?? null,
4927
5009
  beforeSiblingNodeKey: beforeSiblingNodeKey ?? null,
@@ -5035,6 +5117,7 @@ function shouldShowGrabFeedback(target, scannedByNodeId) {
5035
5117
  }
5036
5118
  function isEligibleForDrag(sc) {
5037
5119
  if (sc.draggable === false) return false;
5120
+ if (isNonDraggableNodeMeta(sc.editorMeta)) return false;
5038
5121
  const source = sc.eligibilitySource ?? "editor-meta";
5039
5122
  return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant" || // RFC E2.4 P1: single host root of a kit-component usage (MetricCard,
5040
5123
  // Section, …) carrying the usage's projected meta.
@@ -6657,6 +6740,15 @@ function serializeArrangeChange(event, index) {
6657
6740
  diagnostics: event.kind === "move" ? event.diagnostics : void 0,
6658
6741
  // ARCH-1065 D17 — which slice row this move addresses.
6659
6742
  binding: event.kind === "move" ? event.binding : void 0,
6743
+ // `data-vid` identity migration (`host-bridge.ts`'s `RuntimeMoveDetail`
6744
+ // fields). `index` here is `event.payload.after.index` — the MOVE's own
6745
+ // JSX-child index — never the `index` PARAMETER above (this change's
6746
+ // sequence position in the session). `targetVid` also carries the
6747
+ // resized node's vid on a `resize` change; `containerVid` also carries
6748
+ // the drop container's vid on an `insert` change.
6749
+ targetVid: event.kind === "move" || event.kind === "resize" ? event.targetVid : void 0,
6750
+ containerVid: event.kind === "move" || event.kind === "insert" ? event.containerVid : void 0,
6751
+ index: event.kind === "move" ? event.payload.after.index : void 0,
6660
6752
  // Insert-specific fields
6661
6753
  displayName: event.kind === "insert" ? event.displayName : void 0,
6662
6754
  html: event.kind === "insert" ? event.html : void 0,
@@ -6664,6 +6756,7 @@ function serializeArrangeChange(event, index) {
6664
6756
  tsxImports: event.kind === "insert" ? event.tsxImports : void 0,
6665
6757
  containerId: event.kind === "insert" ? event.containerId : void 0,
6666
6758
  containerNodeKey: event.kind === "insert" ? event.containerNodeKey : void 0,
6759
+ insertedVid: event.kind === "insert" ? event.insertedVid : void 0,
6667
6760
  insertIndex: event.kind === "insert" ? event.index : void 0,
6668
6761
  beforeSiblingInspectorRef: event.kind === "insert" ? event.beforeSiblingInspectorRef : void 0,
6669
6762
  beforeSiblingNodeKey: event.kind === "insert" ? event.beforeSiblingNodeKey : void 0,
@@ -6920,6 +7013,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6920
7013
  const resolvedReorderRef = useRef8(null);
6921
7014
  const finalVisualKeyRef = useRef8(null);
6922
7015
  const dragStartPositionRef = useRef8(null);
7016
+ const droppableContainerIdsRef = useRef8(null);
6923
7017
  const sourceCommitLaneRef = useRef8(null);
6924
7018
  const commitLaneCacheRef = useRef8(/* @__PURE__ */ new Map());
6925
7019
  const safeZoneRef = useRef8(null);
@@ -6951,6 +7045,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6951
7045
  resolvedReorderRef,
6952
7046
  finalVisualKeyRef,
6953
7047
  dragStartPositionRef,
7048
+ droppableContainerIdsRef,
6954
7049
  sourceCommitLaneRef,
6955
7050
  commitLaneCacheRef,
6956
7051
  safeZoneRef,
@@ -8152,6 +8247,14 @@ function useDragVisualLoop({
8152
8247
 
8153
8248
  // src/client/dnd/design-mode/useDragLifecycle.ts
8154
8249
  import { useState as useState5, useCallback as useCallback6 } from "react";
8250
+ function emitRefused(code, cause, element) {
8251
+ dispatchRuntimeEvent({
8252
+ type: "drag-refused",
8253
+ code,
8254
+ cause,
8255
+ vid: element ? resolveVidFromElement(element) : null
8256
+ });
8257
+ }
8155
8258
  function resolveLaneAllowedContainers(anchorElement, srcContainerId) {
8156
8259
  if (!srcContainerId) return null;
8157
8260
  if (!resolveRowBinding(anchorElement)) return null;
@@ -8268,6 +8371,10 @@ function recordCommittedMove(args) {
8268
8371
  });
8269
8372
  const componentName = getEditorMeta(anchorEl)?.componentName;
8270
8373
  const operationId = `${command.at}:${anchorNodeId}`;
8374
+ const targetVid = resolveVidFromElement(selectedEl);
8375
+ const targetContainerId = command.to.parentNodeId ?? command.to.laneContainerId;
8376
+ const targetContainerEl = targetContainerId ? resolveNodeElement(targetContainerId) : null;
8377
+ const containerVid = targetContainerEl ? resolveVidFromElement(targetContainerEl) : null;
8271
8378
  const binding = resolveRowBinding(selectedEl);
8272
8379
  const beforePayload = buildMovePayloadWithEffectiveIdentities({
8273
8380
  placement: command.from,
@@ -8299,6 +8406,8 @@ function recordCommittedMove(args) {
8299
8406
  sourceAfterSibling: sourceAfterSiblingIdentity,
8300
8407
  diagnostics,
8301
8408
  binding,
8409
+ targetVid,
8410
+ containerVid,
8302
8411
  container: containerIdentity,
8303
8412
  beforeSibling: beforeSiblingIdentity,
8304
8413
  afterSibling: afterSiblingIdentity,
@@ -8310,6 +8419,9 @@ function recordCommittedMove(args) {
8310
8419
  type: "element-moved",
8311
8420
  operationId,
8312
8421
  commandType: command.type,
8422
+ targetVid,
8423
+ containerVid,
8424
+ index: command.to.index,
8313
8425
  selected: {
8314
8426
  nodeId: selectedNodeId,
8315
8427
  nodeKey: selectedNodeKey,
@@ -8372,6 +8484,7 @@ function useDragLifecycle({
8372
8484
  refs.resolvedReorderRef.current = null;
8373
8485
  refs.finalVisualKeyRef.current = null;
8374
8486
  refs.dragStartPositionRef.current = null;
8487
+ refs.droppableContainerIdsRef.current = null;
8375
8488
  clearCommitLaneCache();
8376
8489
  refs.safeZoneRef.current = null;
8377
8490
  setInsideRect(null);
@@ -8405,6 +8518,11 @@ function useDragLifecycle({
8405
8518
  eligibilitySource: found.eligibilitySource ?? "editor-meta",
8406
8519
  draggable: found.draggable
8407
8520
  });
8521
+ emitRefused(
8522
+ resolveRefusalCode(found.editorMeta, "GATE_REJECT"),
8523
+ "Node is not draggable (dynamic render, chrome, or kit-origin).",
8524
+ found.element
8525
+ );
8408
8526
  return;
8409
8527
  }
8410
8528
  veTrace("dnd", "drag:start", {
@@ -8428,12 +8546,29 @@ function useDragLifecycle({
8428
8546
  clearCommitLaneCache();
8429
8547
  const anchor = resolveDragAnchor(activeId, proj, refs.nodeMapRef.current);
8430
8548
  const anchorScanned = refs.nodeMapRef.current.get(anchor.anchorNodeId) ?? found;
8431
- if (!anchorScanned || !isEligibleForDrag(anchorScanned)) return;
8549
+ if (!anchorScanned || !isEligibleForDrag(anchorScanned)) {
8550
+ emitRefused(
8551
+ resolveRefusalCode(anchorScanned?.editorMeta, "GATE_REJECT"),
8552
+ "Resolved drag anchor is not draggable.",
8553
+ anchorScanned?.element ?? found.element
8554
+ );
8555
+ return;
8556
+ }
8432
8557
  const anchorNodeId = anchor.anchorNodeId;
8433
8558
  const srcContainerId = anchor.sourceContainerId;
8434
8559
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
8435
8560
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8436
8561
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8562
+ for (const id of buildBlockedNodeMetaContainerIds(proj, refs.nodeMapRef.current)) {
8563
+ blockedContainerIds.add(id);
8564
+ }
8565
+ const droppableSet = computeDroppableSet(anchorNodeId, proj, refs.nodeMapRef.current);
8566
+ refs.droppableContainerIdsRef.current = droppableSet;
8567
+ dispatchRuntimeEvent({
8568
+ type: "drag-started",
8569
+ targetVid: resolveVidFromElement(anchorScanned.element),
8570
+ droppable: Array.from(droppableSet)
8571
+ });
8437
8572
  const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8438
8573
  refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8439
8574
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
@@ -8506,6 +8641,19 @@ function useDragLifecycle({
8506
8641
  syncHistoryAvailability();
8507
8642
  return;
8508
8643
  }
8644
+ if (dropTargetId && !refs.droppableContainerIdsRef.current?.has(dropTargetId)) {
8645
+ const targetContainerEl = resolveNodeElement(dropTargetId);
8646
+ const targetMeta = refs.nodeMapRef.current.get(dropTargetId)?.editorMeta;
8647
+ const fallbackCode = targetMeta?.containerKind === "expression" ? "EXPRESSION_CONTAINER" : "CONTAINER_NOT_PLAIN";
8648
+ emitRefused(
8649
+ resolveRefusalCode(targetMeta, fallbackCode),
8650
+ "Drop target is outside the droppable set for this node.",
8651
+ targetContainerEl
8652
+ );
8653
+ clearDragState();
8654
+ syncHistoryAvailability();
8655
+ return;
8656
+ }
8509
8657
  let command = null;
8510
8658
  if (nestTarget) {
8511
8659
  if (!activeEl.contains(nestTarget.element) && !nestTarget.element.contains(activeEl) && sourcePos?.commitNodeId) {
@@ -8717,6 +8865,7 @@ function useResizeSession({
8717
8865
  return;
8718
8866
  }
8719
8867
  const inspectorRef = getInspectorRefFromElement(element);
8868
+ const targetVid = resolveVidFromElement(element);
8720
8869
  const resizeCmd = {
8721
8870
  type: "resize",
8722
8871
  mode,
@@ -8736,12 +8885,14 @@ function useResizeSession({
8736
8885
  nodeId: selectedNodeId,
8737
8886
  inspectorRef,
8738
8887
  componentName: getEditorMeta(element)?.componentName,
8888
+ targetVid,
8739
8889
  before: { mode, selectedNodeKey, targetNodeKey, targetNodeId, width: resizeCmd.before.width, height: resizeCmd.before.height, colSpan: resizeCmd.before.colSpan, rowSpan: resizeCmd.before.rowSpan, styleDiff: { before: beforeStyles, after: afterStyles } },
8740
8890
  after: { mode, selectedNodeKey, targetNodeKey, targetNodeId, width: resizeCmd.after.width, height: resizeCmd.after.height, colSpan: resizeCmd.after.colSpan, rowSpan: resizeCmd.after.rowSpan, styleDiff: { before: beforeStyles, after: afterStyles } }
8741
8891
  });
8742
8892
  dispatchRuntimeEvent({
8743
8893
  type: "element-resized",
8744
8894
  operationId,
8895
+ targetVid,
8745
8896
  selected: {
8746
8897
  nodeId: selectedNodeId,
8747
8898
  nodeKey: selectedNodeKey,
@@ -8964,6 +9115,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8964
9115
  };
8965
9116
  }
8966
9117
  const operationId = `${insertCommand.at}:${nodeId}`;
9118
+ const containerVid = containerEl ? resolveVidFromElement(containerEl) : null;
9119
+ const insertedVid = resolveVidFromElement(newElement);
8967
9120
  useDesignModeStore.getState().recordInsertChange({
8968
9121
  operationId,
8969
9122
  nodeKey,
@@ -8975,6 +9128,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8975
9128
  tsxImports: component.tsxImports ?? null,
8976
9129
  containerId: intent.containerId,
8977
9130
  containerNodeKey,
9131
+ containerVid,
9132
+ insertedVid,
8978
9133
  index: intent.index,
8979
9134
  beforeSiblingInspectorRef: beforeSiblingIdentity?.inspectorRef ?? null,
8980
9135
  beforeSiblingNodeKey: beforeSiblingIdentity?.nodeKey ?? null
@@ -8989,6 +9144,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8989
9144
  tsxCode: component.tsxCode ?? null,
8990
9145
  tsxImports: component.tsxImports ?? null,
8991
9146
  container: containerIdentity,
9147
+ containerVid,
9148
+ insertedVid,
8992
9149
  index: intent.index,
8993
9150
  beforeSibling: beforeSiblingIdentity
8994
9151
  });
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(babel_plugin_collection_binding_exports);
26
26
  var import_types = require("@babel/types");
27
27
 
28
28
  // src/babel-plugin-editor-meta.ts
29
+ var import_editor_protocol = require("@8base-archie/editor-protocol");
29
30
  function meaningfulChildren(t, children) {
30
31
  return children.filter((child) => {
31
32
  if (t.isJSXText(child)) return child.value.trim() !== "";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  meaningfulChildren
3
- } from "./chunk-P5XMHBFS.mjs";
3
+ } from "./chunk-THKKWDMJ.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
@@ -16,6 +16,13 @@ import { types, PluginObj } from '@babel/core';
16
16
  * + (ARCH-1450) childrenSource, propSources — same-file identifier tracing
17
17
  * for values `propBindings` cannot reach (no destructured-prop component
18
18
  * in scope, or a value that never touches a prop at all)
19
+ * + (data-vid identity migration) vid, isKitOrigin, dynamicType, isChrome,
20
+ * containerKind, lockReason — read from a `data-vid` attribute (stamped
21
+ * into persisted source by the Genesis stamper) plus the `NodeMeta`
22
+ * contract fields (`@8base-archie/editor-protocol`) computed from the
23
+ * element's own JSX/function ancestry. `nodeId === vid` when `data-vid`
24
+ * is present; `allowLegacyIds` (default `true`, transitional) governs
25
+ * what happens when it is not — see `EditorMetaPluginOptions` below.
19
26
  *
20
27
  * At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
21
28
  * element identity without any DOM attributes.
@@ -136,6 +143,28 @@ interface EditorMetaPluginOptions {
136
143
  * @default false
137
144
  */
138
145
  templateOrigin?: boolean;
146
+ /**
147
+ * `data-vid` identity migration (vid-identity, decision binding). When a
148
+ * `JSXOpeningElement` carries a well-formed `data-vid` attribute
149
+ * (`VID_PATTERN`, `/^[a-z0-9]{6,8}$/`, stamped into persisted source by
150
+ * the Genesis stamper — see `@8base-archie/editor-protocol`'s
151
+ * `src/ids.ts`), the plugin ALWAYS reads it and uses it as `nodeId`
152
+ * (`meta.vid` is also emitted). This option only controls what happens
153
+ * when NO `data-vid` is present:
154
+ *
155
+ * - `true` (default in this version — TRANSITIONAL, to be removed once
156
+ * every source tree is stamped) — fall back to the legacy
157
+ * `"file:line:col:componentName"` addressing, exactly as before this
158
+ * change.
159
+ * - `false` — an element with no `data-vid` is NOT instrumented at all
160
+ * (no `__editorMeta` is emitted for it): a stable identity contract
161
+ * cannot be met by an addressing scheme that invalidates on every
162
+ * source write, so the plugin declines rather than silently hand out
163
+ * one.
164
+ *
165
+ * @default true
166
+ */
167
+ allowLegacyIds?: boolean;
139
168
  }
140
169
  declare function babelPluginEditorMeta({ types: t }: {
141
170
  types: typeof types;
@@ -347,10 +376,27 @@ type PropSource = {
347
376
  } | {
348
377
  kind: 'other';
349
378
  };
379
+
350
380
  /** Metadata injected into every JSX element by babel-plugin-editor-meta. */
351
381
  interface EditorMeta {
352
- /** Stable identifier: "file:line:col:componentName" */
382
+ /**
383
+ * Stable identifier for this node. `vid` identity (see below) is the
384
+ * canonical shape going forward: when the persisted source carries a
385
+ * `data-vid` attribute, `nodeId === vid`. Absent a `data-vid` (a bundle
386
+ * built with `allowLegacyIds: true`, the plugin's transitional default),
387
+ * this falls back to the legacy `"file:line:col:componentName"` shape,
388
+ * which invalidates on every write to the source.
389
+ */
353
390
  nodeId: string;
391
+ /**
392
+ * `data-vid` identity (vid-identity migration) — present only when the
393
+ * source element carried a well-formed `data-vid` attribute
394
+ * (`VID_PATTERN`, `/^[a-z0-9]{6,8}$/`). Stamped into PERSISTED source by
395
+ * the Genesis stamper, NOT computed here — the plugin only reads it.
396
+ * Absent on a node with no `data-vid` (legacy addressing) and on every
397
+ * bundle built before this change.
398
+ */
399
+ vid?: string;
354
400
  /** Relative file path from project root. */
355
401
  file: string;
356
402
  line: number;
@@ -553,6 +599,51 @@ interface EditorMeta {
553
599
  * already says). Absent on every bundle built before ARCH-1450.
554
600
  */
555
601
  propSources?: Record<string, PropSource>;
602
+ /**
603
+ * `NodeMeta` fields (vid-identity migration, `@8base-archie/editor-protocol`
604
+ * contract). Emitted only alongside `vid` (a legacy, `data-vid`-less node
605
+ * has no NodeMeta to report) — read via `fiber-bridge.ts`'s `getNodeMeta`,
606
+ * which validates the assembled `NodeMeta` object with `NodeMetaSchema`
607
+ * rather than trusting these fields individually.
608
+ *
609
+ * `isKitOrigin` restates the existing `origin === 'kit'` fact as a plain
610
+ * boolean (the `NodeMeta` contract's shape) — `origin` itself is
611
+ * unchanged and stays the field every existing consumer already reads.
612
+ */
613
+ isKitOrigin?: boolean;
614
+ /**
615
+ * Whether this node renders under a `.map`/`forEach`/`flatMap` callback
616
+ * (`'ARRAY'`), under a `ConditionalExpression`/`LogicalExpression` inside a
617
+ * `JSXExpressionContainer` (`'CONDITIONAL'`), or neither (`'STATIC'`).
618
+ * Computed at compile time by walking JSX/function ancestors — see
619
+ * `computeDynamicType` in `babel-plugin-editor-meta.ts` for the exact walk
620
+ * and its stopping rule at the first non-qualifying function boundary.
621
+ */
622
+ dynamicType?: 'STATIC' | 'ARRAY' | 'CONDITIONAL';
623
+ /**
624
+ * True for a node inside `shell.jsx`, `components/shell/**`, or with a
625
+ * `nav`/`aside`/`header` ancestor in the same file — the app shell/chrome
626
+ * region, structurally locked against drag/drop regardless of
627
+ * `isKitOrigin`. See `computeIsChrome` in `babel-plugin-editor-meta.ts`.
628
+ */
629
+ isChrome?: boolean;
630
+ /**
631
+ * Shape of this element's JSX children for drop-target purposes: `'plain'`
632
+ * when every child is a `JSXElement` or whitespace-trimmed `JSXText`,
633
+ * `'expression'` when any child is a `JSXExpressionContainer`/spread/
634
+ * iteration, `'none'` when the element has no element children (including
635
+ * self-closing). See `computeContainerKind` in `babel-plugin-editor-meta.ts`.
636
+ */
637
+ containerKind?: 'plain' | 'expression' | 'none';
638
+ /**
639
+ * Derived refusal reason, in priority order: `KIT_INTERNAL` (kit-origin) >
640
+ * `CHROME_STRUCTURAL` (shell/chrome) > `DYNAMIC_ARRAY`/`DYNAMIC_CONDITIONAL`
641
+ * (dynamic rendering shape) > `EXPRESSION_CONTAINER` (only relevant when
642
+ * this node is considered as a DROP TARGET, i.e. `containerKind ===
643
+ * 'expression'`). Absent when none of these apply — the node is a normal,
644
+ * writable, static, non-chrome, non-kit element.
645
+ */
646
+ lockReason?: 'KIT_INTERNAL' | 'DYNAMIC_ARRAY' | 'DYNAMIC_CONDITIONAL' | 'CHROME_STRUCTURAL' | 'EXPRESSION_CONTAINER';
556
647
  }
557
648
 
558
649
  interface ArchieDevToolsOptions {
package/dist/index.d.ts CHANGED
@@ -16,6 +16,13 @@ import { types, PluginObj } from '@babel/core';
16
16
  * + (ARCH-1450) childrenSource, propSources — same-file identifier tracing
17
17
  * for values `propBindings` cannot reach (no destructured-prop component
18
18
  * in scope, or a value that never touches a prop at all)
19
+ * + (data-vid identity migration) vid, isKitOrigin, dynamicType, isChrome,
20
+ * containerKind, lockReason — read from a `data-vid` attribute (stamped
21
+ * into persisted source by the Genesis stamper) plus the `NodeMeta`
22
+ * contract fields (`@8base-archie/editor-protocol`) computed from the
23
+ * element's own JSX/function ancestry. `nodeId === vid` when `data-vid`
24
+ * is present; `allowLegacyIds` (default `true`, transitional) governs
25
+ * what happens when it is not — see `EditorMetaPluginOptions` below.
19
26
  *
20
27
  * At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
21
28
  * element identity without any DOM attributes.
@@ -136,6 +143,28 @@ interface EditorMetaPluginOptions {
136
143
  * @default false
137
144
  */
138
145
  templateOrigin?: boolean;
146
+ /**
147
+ * `data-vid` identity migration (vid-identity, decision binding). When a
148
+ * `JSXOpeningElement` carries a well-formed `data-vid` attribute
149
+ * (`VID_PATTERN`, `/^[a-z0-9]{6,8}$/`, stamped into persisted source by
150
+ * the Genesis stamper — see `@8base-archie/editor-protocol`'s
151
+ * `src/ids.ts`), the plugin ALWAYS reads it and uses it as `nodeId`
152
+ * (`meta.vid` is also emitted). This option only controls what happens
153
+ * when NO `data-vid` is present:
154
+ *
155
+ * - `true` (default in this version — TRANSITIONAL, to be removed once
156
+ * every source tree is stamped) — fall back to the legacy
157
+ * `"file:line:col:componentName"` addressing, exactly as before this
158
+ * change.
159
+ * - `false` — an element with no `data-vid` is NOT instrumented at all
160
+ * (no `__editorMeta` is emitted for it): a stable identity contract
161
+ * cannot be met by an addressing scheme that invalidates on every
162
+ * source write, so the plugin declines rather than silently hand out
163
+ * one.
164
+ *
165
+ * @default true
166
+ */
167
+ allowLegacyIds?: boolean;
139
168
  }
140
169
  declare function babelPluginEditorMeta({ types: t }: {
141
170
  types: typeof types;
@@ -347,10 +376,27 @@ type PropSource = {
347
376
  } | {
348
377
  kind: 'other';
349
378
  };
379
+
350
380
  /** Metadata injected into every JSX element by babel-plugin-editor-meta. */
351
381
  interface EditorMeta {
352
- /** Stable identifier: "file:line:col:componentName" */
382
+ /**
383
+ * Stable identifier for this node. `vid` identity (see below) is the
384
+ * canonical shape going forward: when the persisted source carries a
385
+ * `data-vid` attribute, `nodeId === vid`. Absent a `data-vid` (a bundle
386
+ * built with `allowLegacyIds: true`, the plugin's transitional default),
387
+ * this falls back to the legacy `"file:line:col:componentName"` shape,
388
+ * which invalidates on every write to the source.
389
+ */
353
390
  nodeId: string;
391
+ /**
392
+ * `data-vid` identity (vid-identity migration) — present only when the
393
+ * source element carried a well-formed `data-vid` attribute
394
+ * (`VID_PATTERN`, `/^[a-z0-9]{6,8}$/`). Stamped into PERSISTED source by
395
+ * the Genesis stamper, NOT computed here — the plugin only reads it.
396
+ * Absent on a node with no `data-vid` (legacy addressing) and on every
397
+ * bundle built before this change.
398
+ */
399
+ vid?: string;
354
400
  /** Relative file path from project root. */
355
401
  file: string;
356
402
  line: number;
@@ -553,6 +599,51 @@ interface EditorMeta {
553
599
  * already says). Absent on every bundle built before ARCH-1450.
554
600
  */
555
601
  propSources?: Record<string, PropSource>;
602
+ /**
603
+ * `NodeMeta` fields (vid-identity migration, `@8base-archie/editor-protocol`
604
+ * contract). Emitted only alongside `vid` (a legacy, `data-vid`-less node
605
+ * has no NodeMeta to report) — read via `fiber-bridge.ts`'s `getNodeMeta`,
606
+ * which validates the assembled `NodeMeta` object with `NodeMetaSchema`
607
+ * rather than trusting these fields individually.
608
+ *
609
+ * `isKitOrigin` restates the existing `origin === 'kit'` fact as a plain
610
+ * boolean (the `NodeMeta` contract's shape) — `origin` itself is
611
+ * unchanged and stays the field every existing consumer already reads.
612
+ */
613
+ isKitOrigin?: boolean;
614
+ /**
615
+ * Whether this node renders under a `.map`/`forEach`/`flatMap` callback
616
+ * (`'ARRAY'`), under a `ConditionalExpression`/`LogicalExpression` inside a
617
+ * `JSXExpressionContainer` (`'CONDITIONAL'`), or neither (`'STATIC'`).
618
+ * Computed at compile time by walking JSX/function ancestors — see
619
+ * `computeDynamicType` in `babel-plugin-editor-meta.ts` for the exact walk
620
+ * and its stopping rule at the first non-qualifying function boundary.
621
+ */
622
+ dynamicType?: 'STATIC' | 'ARRAY' | 'CONDITIONAL';
623
+ /**
624
+ * True for a node inside `shell.jsx`, `components/shell/**`, or with a
625
+ * `nav`/`aside`/`header` ancestor in the same file — the app shell/chrome
626
+ * region, structurally locked against drag/drop regardless of
627
+ * `isKitOrigin`. See `computeIsChrome` in `babel-plugin-editor-meta.ts`.
628
+ */
629
+ isChrome?: boolean;
630
+ /**
631
+ * Shape of this element's JSX children for drop-target purposes: `'plain'`
632
+ * when every child is a `JSXElement` or whitespace-trimmed `JSXText`,
633
+ * `'expression'` when any child is a `JSXExpressionContainer`/spread/
634
+ * iteration, `'none'` when the element has no element children (including
635
+ * self-closing). See `computeContainerKind` in `babel-plugin-editor-meta.ts`.
636
+ */
637
+ containerKind?: 'plain' | 'expression' | 'none';
638
+ /**
639
+ * Derived refusal reason, in priority order: `KIT_INTERNAL` (kit-origin) >
640
+ * `CHROME_STRUCTURAL` (shell/chrome) > `DYNAMIC_ARRAY`/`DYNAMIC_CONDITIONAL`
641
+ * (dynamic rendering shape) > `EXPRESSION_CONTAINER` (only relevant when
642
+ * this node is considered as a DROP TARGET, i.e. `containerKind ===
643
+ * 'expression'`). Absent when none of these apply — the node is a normal,
644
+ * writable, static, non-chrome, non-kit element.
645
+ */
646
+ lockReason?: 'KIT_INTERNAL' | 'DYNAMIC_ARRAY' | 'DYNAMIC_CONDITIONAL' | 'CHROME_STRUCTURAL' | 'EXPRESSION_CONTAINER';
556
647
  }
557
648
 
558
649
  interface ArchieDevToolsOptions {