@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.
package/README.md CHANGED
@@ -73,6 +73,42 @@ Changes to `babel-plugin-editor-meta.ts`'s `__editorMeta` prop shape and
73
73
  `inspector.standalone.js`'s meta-read paths have a load-bearing publish
74
74
  order — see [`docs/deploy-order.md`](docs/deploy-order.md).
75
75
 
76
+ ## Identidad `data-vid`, NodeMeta, protocolo 1.x
77
+
78
+ `babelPluginEditorMeta` ahora LEE `data-vid` (persistido en el source por el
79
+ stamper de Genesis, `/^[a-z0-9]{6,8}$/`) en vez de derivar la identidad de
80
+ `file:line:col:componentName`, que se invalida en cada escritura del source.
81
+ Cuando el elemento tiene `data-vid`, `__editorMeta.nodeId === __editorMeta.vid`
82
+ y el plugin también emite los campos de `NodeMeta`
83
+ (`@8base-archie/editor-protocol`): `dynamicType`, `isKitOrigin`, `isChrome`,
84
+ `containerKind` y, cuando aplica, `lockReason`. `parentNodeId` usa el `vid`
85
+ del padre cuando existe.
86
+
87
+ Sin `data-vid`, el plugin cae al direccionamiento legado SOLO cuando la
88
+ opción `allowLegacyIds` es `true` (default en esta versión — transicional,
89
+ se eliminará más adelante); con `allowLegacyIds: false` el elemento no se
90
+ instrumenta.
91
+
92
+ En runtime, `node-key.ts` resuelve la identidad con la prioridad
93
+ `meta.vid` → `data-vid` del DOM → (legado) `meta.nodeId` → `runtime:N`, y
94
+ `fiber-bridge.ts` expone `getNodeMeta(el)`, que valida el `NodeMeta`
95
+ ensamblado contra `NodeMetaSchema` antes de devolverlo. `structural-policy.ts`
96
+ añade `isNonDraggableNodeMeta`, `isNonDroppableNodeMeta` y
97
+ `computeDroppableSet` sobre esos mismos campos.
98
+
99
+ El handshake (`INSPECTOR_SCRIPT_LOADED`/`CAPABILITIES_RESULT`) añade
100
+ `editorProtocolVersion` (el `PROTOCOL_VERSION` semver del paquete de
101
+ contrato — un campo NUEVO, nunca superpuesto al `protocolVersion` numérico
102
+ existente que `archie-app` ya compara estrictamente) y las capabilities
103
+ `vidIdentity`, `nodeMeta`, `refusals`. `ELEMENT_SELECTED` añade `vid` y
104
+ `nodeMeta`. El vocabulario `REFUSED{code, cause, vid}` está disponible
105
+ (`refuseNode` en `inspector.standalone.js`) para cuando un host rechaza un
106
+ gesto de arrastre/drop fuera de política.
107
+
108
+ Consume `@8base-archie/editor-protocol` como fuente única del contrato
109
+ (`Vid`, `NodeMeta`, `LockReason`, los schemas Zod) — ver
110
+ `src/types/editor-meta.ts`, que re-exporta esos tipos en vez de duplicarlos.
111
+
76
112
  ## Local linking
77
113
 
78
114
  ```bash
@@ -6,6 +6,7 @@ var getDirname = () => path.dirname(getFilename());
6
6
  var __dirname = /* @__PURE__ */ getDirname();
7
7
 
8
8
  // src/babel-plugin-editor-meta.ts
9
+ import { VID_ATTR, VID_PATTERN } from "@8base-archie/editor-protocol";
9
10
  import * as nodePath from "path";
10
11
  function meaningfulChildren(t, children) {
11
12
  return children.filter((child) => {
@@ -61,7 +62,8 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
61
62
  instrumentImported = true,
62
63
  includeHostSourceRef = false,
63
64
  origin = "screen",
64
- templateOrigin = false
65
+ templateOrigin = false,
66
+ allowLegacyIds = true
65
67
  } = opts;
66
68
  function getJSXTagName(el) {
67
69
  const { name } = el;
@@ -109,6 +111,77 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
109
111
  function computeNodeId(file, line, col, componentName) {
110
112
  return `${file}:${line}:${col}:${componentName}`;
111
113
  }
114
+ function readVidAttr(attrs) {
115
+ for (const attr of attrs) {
116
+ if (!t.isJSXAttribute(attr)) continue;
117
+ if (!t.isJSXIdentifier(attr.name, { name: VID_ATTR })) continue;
118
+ const { value } = attr;
119
+ let raw = null;
120
+ if (t.isStringLiteral(value)) {
121
+ raw = value.value;
122
+ } else if (t.isJSXExpressionContainer(value) && t.isStringLiteral(value.expression)) {
123
+ raw = value.expression.value;
124
+ }
125
+ if (raw !== null && VID_PATTERN.test(raw)) return raw;
126
+ return null;
127
+ }
128
+ return null;
129
+ }
130
+ function computeDynamicType(path2) {
131
+ let current = path2.parentPath;
132
+ while (current) {
133
+ if (current.isJSXExpressionContainer()) {
134
+ const expr = current.node.expression;
135
+ if (t.isConditionalExpression(expr) || t.isLogicalExpression(expr)) {
136
+ return "CONDITIONAL";
137
+ }
138
+ }
139
+ if (current.isArrowFunctionExpression() || current.isFunctionExpression()) {
140
+ const call = current.parentPath;
141
+ if (call?.isCallExpression()) {
142
+ const callee = call.node.callee;
143
+ if (t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property) && /^(map|flatMap|forEach)$/.test(callee.property.name)) {
144
+ return "ARRAY";
145
+ }
146
+ }
147
+ break;
148
+ }
149
+ if (current.isFunctionDeclaration()) break;
150
+ current = current.parentPath;
151
+ }
152
+ return "STATIC";
153
+ }
154
+ const SHELL_FILE_RE = /(^|\/)shell\.jsx$/;
155
+ const SHELL_DIR_RE = /(^|\/)components\/shell\//;
156
+ const CHROME_ANCESTOR_TAGS = /* @__PURE__ */ new Set(["nav", "aside", "header"]);
157
+ function computeIsChrome(file, path2) {
158
+ if (SHELL_FILE_RE.test(file) || SHELL_DIR_RE.test(file)) return true;
159
+ let current = path2.parentPath;
160
+ while (current) {
161
+ if (current.isJSXElement()) {
162
+ const tag = getJSXTagName(current.node.openingElement);
163
+ if (CHROME_ANCESTOR_TAGS.has(tag)) return true;
164
+ }
165
+ current = current.parentPath;
166
+ }
167
+ return false;
168
+ }
169
+ function computeContainerKind(path2) {
170
+ const elPath = path2.parentPath;
171
+ if (!elPath?.isJSXElement()) return "none";
172
+ const children = meaningfulChildren(t, elPath.node.children);
173
+ if (children.length === 0) return "none";
174
+ const allPlain = children.every((c) => t.isJSXElement(c) || t.isJSXText(c));
175
+ return allPlain ? "plain" : "expression";
176
+ }
177
+ function computeLockReason(isKitOrigin, isChrome, dynamicType, containerKind) {
178
+ if (isKitOrigin) return "KIT_INTERNAL";
179
+ if (isChrome) return "CHROME_STRUCTURAL";
180
+ if (dynamicType === "ARRAY") return "DYNAMIC_ARRAY";
181
+ if (dynamicType === "CONDITIONAL") return "DYNAMIC_CONDITIONAL";
182
+ if (containerKind === "expression") return "EXPRESSION_CONTAINER";
183
+ return void 0;
184
+ }
112
185
  function isMovable(path2) {
113
186
  const elPath = path2.parentPath;
114
187
  if (!elPath) return false;
@@ -126,6 +199,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
126
199
  while (ancestor) {
127
200
  if (ancestor.isJSXElement()) {
128
201
  const opening = ancestor.node.openingElement;
202
+ const parentVid = readVidAttr(opening.attributes);
203
+ if (parentVid) return parentVid;
204
+ if (!allowLegacyIds) return null;
129
205
  const loc = opening.loc?.start;
130
206
  if (loc) {
131
207
  const tag = getJSXTagName(opening);
@@ -752,7 +828,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
752
828
  return;
753
829
  }
754
830
  }
755
- const nodeId = computeNodeId(file, line, col, componentName);
831
+ const vid = readVidAttr(path2.node.attributes);
832
+ if (!vid && !allowLegacyIds) return;
833
+ const nodeId = vid ?? computeNodeId(file, line, col, componentName);
756
834
  const isKitOrigin = origin === "kit" || hasSlotAttr;
757
835
  const movable = isKitOrigin ? false : isMovable(path2);
758
836
  const parentNodeId = getParentNodeId(path2, file);
@@ -774,6 +852,24 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
774
852
  parentNodeId ? t.stringLiteral(parentNodeId) : t.nullLiteral()
775
853
  )
776
854
  ]);
855
+ if (vid) {
856
+ const dynamicType = computeDynamicType(path2);
857
+ const isChrome = computeIsChrome(file, path2);
858
+ const containerKind = computeContainerKind(path2);
859
+ const lockReason = computeLockReason(isKitOrigin, isChrome, dynamicType, containerKind);
860
+ metaObj.properties.push(
861
+ t.objectProperty(t.identifier("vid"), t.stringLiteral(vid)),
862
+ t.objectProperty(t.identifier("isKitOrigin"), t.booleanLiteral(isKitOrigin)),
863
+ t.objectProperty(t.identifier("dynamicType"), t.stringLiteral(dynamicType)),
864
+ t.objectProperty(t.identifier("isChrome"), t.booleanLiteral(isChrome)),
865
+ t.objectProperty(t.identifier("containerKind"), t.stringLiteral(containerKind))
866
+ );
867
+ if (lockReason) {
868
+ metaObj.properties.push(
869
+ t.objectProperty(t.identifier("lockReason"), t.stringLiteral(lockReason))
870
+ );
871
+ }
872
+ }
777
873
  if (isKitOrigin) {
778
874
  if (templateOrigin) {
779
875
  metaObj.properties.push(
@@ -4116,13 +4116,21 @@ function summarizeRuntimeEvent(detail) {
4116
4116
  container: summarizeIdentity(detail.container),
4117
4117
  beforeSibling: summarizeIdentity(detail.beforeSibling),
4118
4118
  sourceContainer: summarizeIdentity(detail.sourceContainer),
4119
- diagnostics: detail.diagnostics ?? null
4119
+ diagnostics: detail.diagnostics ?? null,
4120
+ targetVid: detail.targetVid ?? null,
4121
+ containerVid: detail.containerVid ?? null,
4122
+ index: detail.index
4120
4123
  };
4124
+ case "drag-started":
4125
+ return { targetVid: detail.targetVid, droppableCount: detail.droppable.length };
4126
+ case "drag-refused":
4127
+ return { code: detail.code, cause: detail.cause, vid: detail.vid };
4121
4128
  case "element-resized":
4122
4129
  return {
4123
4130
  operationId: detail.operationId,
4124
4131
  selected: summarizeIdentity(detail.selected),
4125
4132
  anchor: summarizeIdentity(detail.anchor),
4133
+ targetVid: detail.targetVid ?? null,
4126
4134
  resize: {
4127
4135
  mode: detail.resize.mode,
4128
4136
  selectedNodeKey: detail.resize.selectedNodeKey,
@@ -4139,6 +4147,8 @@ function summarizeRuntimeEvent(detail) {
4139
4147
  nodeKey: detail.nodeKey,
4140
4148
  displayName: detail.displayName,
4141
4149
  container: summarizeIdentity(detail.container),
4150
+ containerVid: detail.containerVid ?? null,
4151
+ insertedVid: detail.insertedVid ?? null,
4142
4152
  index: detail.index,
4143
4153
  beforeSibling: summarizeIdentity(detail.beforeSibling),
4144
4154
  hasTsxCode: !!detail.tsxCode,
@@ -4220,6 +4230,58 @@ function isValidLaneContainer(containerId, projection, nodeMap) {
4220
4230
  if (container.children.length === 0) return false;
4221
4231
  return !isStructuralContainer(containerId, nodeMap);
4222
4232
  }
4233
+ function isNonDroppableNodeMeta(meta) {
4234
+ if (!meta) return false;
4235
+ if (meta.containerKind && meta.containerKind !== "plain") return true;
4236
+ if (meta.isChrome) return true;
4237
+ if (meta.isKitOrigin) return true;
4238
+ return false;
4239
+ }
4240
+ function isNonDraggableNodeMeta(meta) {
4241
+ if (!meta) return false;
4242
+ if (meta.dynamicType && meta.dynamicType !== "STATIC") return true;
4243
+ if (meta.isChrome) return true;
4244
+ if (meta.isKitOrigin) return true;
4245
+ return false;
4246
+ }
4247
+ function collectSubtreeIds(rootId, projection) {
4248
+ const ids2 = /* @__PURE__ */ new Set([rootId]);
4249
+ const stack = [rootId];
4250
+ while (stack.length > 0) {
4251
+ const id = stack.pop();
4252
+ const node = projection.containerIndex.get(id);
4253
+ if (!node) continue;
4254
+ for (const childId of node.children) {
4255
+ if (ids2.has(childId)) continue;
4256
+ ids2.add(childId);
4257
+ stack.push(childId);
4258
+ }
4259
+ }
4260
+ return ids2;
4261
+ }
4262
+ function computeDroppableSet(sourceId, projection, nodeMap) {
4263
+ const excluded = collectSubtreeIds(sourceId, projection);
4264
+ const droppable = /* @__PURE__ */ new Set();
4265
+ for (const containerId of projection.containerIndex.keys()) {
4266
+ if (excluded.has(containerId)) continue;
4267
+ if (isStructuralContainer(containerId, nodeMap)) continue;
4268
+ if (isNonDroppableNodeMeta(nodeMap.get(containerId)?.editorMeta)) continue;
4269
+ droppable.add(containerId);
4270
+ }
4271
+ return droppable;
4272
+ }
4273
+ function buildBlockedNodeMetaContainerIds(projection, nodeMap) {
4274
+ const blocked = /* @__PURE__ */ new Set();
4275
+ for (const containerId of projection.containerIndex.keys()) {
4276
+ if (isNonDroppableNodeMeta(nodeMap.get(containerId)?.editorMeta)) {
4277
+ blocked.add(containerId);
4278
+ }
4279
+ }
4280
+ return blocked;
4281
+ }
4282
+ function resolveRefusalCode(meta, fallback) {
4283
+ return meta?.lockReason ?? fallback;
4284
+ }
4223
4285
  function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainerId) {
4224
4286
  const blocked = /* @__PURE__ */ new Set();
4225
4287
  for (const containerId of projection.containerIndex.keys()) {
@@ -4229,6 +4291,9 @@ function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainer
4229
4291
  return blocked;
4230
4292
  }
4231
4293
 
4294
+ // src/client/dnd/design-mode/node-meta-guard.ts
4295
+ var VID_PATTERN = /^[a-z0-9]{6,8}$/;
4296
+
4232
4297
  // src/client/dnd/design-mode/fiber-bridge.ts
4233
4298
  var FIBER_KEY_PREFIX = "__reactFiber$";
4234
4299
  var PROPS_KEY_PREFIX = "__reactProps$";
@@ -4509,11 +4574,18 @@ function getNodeKeyFromElement(el) {
4509
4574
  const resolved = resolveDragSurface(el);
4510
4575
  const host = resolved.host ?? el;
4511
4576
  const meta = identityMeta(host);
4577
+ if (meta?.vid) return meta.vid;
4578
+ const domVid = host.getAttribute("data-vid");
4579
+ if (domVid) return domVid;
4512
4580
  if (meta) return meta.nodeId;
4513
4581
  const persisted = host.getAttribute("data-dnd-node-id");
4514
4582
  if (persisted) return persisted;
4515
4583
  return getRuntimeKey(host);
4516
4584
  }
4585
+ function resolveVidFromElement(el) {
4586
+ const key2 = getNodeKeyFromElement(el);
4587
+ return VID_PATTERN.test(key2) ? key2 : null;
4588
+ }
4517
4589
  function getInspectorRefFromElement(el) {
4518
4590
  const resolved = resolveDragSurface(el);
4519
4591
  const host = resolved.host ?? el;
@@ -4840,6 +4912,8 @@ var useDesignModeStore = create((set, get) => ({
4840
4912
  afterSibling,
4841
4913
  diagnostics,
4842
4914
  binding,
4915
+ targetVid,
4916
+ containerVid,
4843
4917
  componentName,
4844
4918
  before,
4845
4919
  after
@@ -4867,6 +4941,8 @@ var useDesignModeStore = create((set, get) => ({
4867
4941
  afterSibling: afterSibling ?? null,
4868
4942
  diagnostics: diagnostics ?? null,
4869
4943
  binding: binding ?? null,
4944
+ targetVid: targetVid ?? null,
4945
+ containerVid: containerVid ?? null,
4870
4946
  payload: { before, after },
4871
4947
  at: now
4872
4948
  };
@@ -4895,6 +4971,7 @@ var useDesignModeStore = create((set, get) => ({
4895
4971
  nodeId,
4896
4972
  inspectorRef,
4897
4973
  componentName,
4974
+ targetVid,
4898
4975
  before,
4899
4976
  after
4900
4977
  }) => {
@@ -4906,6 +4983,7 @@ var useDesignModeStore = create((set, get) => ({
4906
4983
  nodeKey,
4907
4984
  nodeId,
4908
4985
  inspectorRef,
4986
+ targetVid: targetVid ?? null,
4909
4987
  payload: { before, after },
4910
4988
  at: now
4911
4989
  };
@@ -4939,6 +5017,8 @@ var useDesignModeStore = create((set, get) => ({
4939
5017
  tsxImports,
4940
5018
  containerId,
4941
5019
  containerNodeKey,
5020
+ containerVid,
5021
+ insertedVid,
4942
5022
  index,
4943
5023
  beforeSiblingInspectorRef,
4944
5024
  beforeSiblingNodeKey
@@ -4957,6 +5037,8 @@ var useDesignModeStore = create((set, get) => ({
4957
5037
  tsxImports: tsxImports ?? null,
4958
5038
  containerId,
4959
5039
  containerNodeKey,
5040
+ containerVid: containerVid ?? null,
5041
+ insertedVid: insertedVid ?? null,
4960
5042
  index,
4961
5043
  beforeSiblingInspectorRef: beforeSiblingInspectorRef ?? null,
4962
5044
  beforeSiblingNodeKey: beforeSiblingNodeKey ?? null,
@@ -5070,6 +5152,7 @@ function shouldShowGrabFeedback(target, scannedByNodeId) {
5070
5152
  }
5071
5153
  function isEligibleForDrag(sc) {
5072
5154
  if (sc.draggable === false) return false;
5155
+ if (isNonDraggableNodeMeta(sc.editorMeta)) return false;
5073
5156
  const source = sc.eligibilitySource ?? "editor-meta";
5074
5157
  return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant" || // RFC E2.4 P1: single host root of a kit-component usage (MetricCard,
5075
5158
  // Section, …) carrying the usage's projected meta.
@@ -6692,6 +6775,15 @@ function serializeArrangeChange(event, index) {
6692
6775
  diagnostics: event.kind === "move" ? event.diagnostics : void 0,
6693
6776
  // ARCH-1065 D17 — which slice row this move addresses.
6694
6777
  binding: event.kind === "move" ? event.binding : void 0,
6778
+ // `data-vid` identity migration (`host-bridge.ts`'s `RuntimeMoveDetail`
6779
+ // fields). `index` here is `event.payload.after.index` — the MOVE's own
6780
+ // JSX-child index — never the `index` PARAMETER above (this change's
6781
+ // sequence position in the session). `targetVid` also carries the
6782
+ // resized node's vid on a `resize` change; `containerVid` also carries
6783
+ // the drop container's vid on an `insert` change.
6784
+ targetVid: event.kind === "move" || event.kind === "resize" ? event.targetVid : void 0,
6785
+ containerVid: event.kind === "move" || event.kind === "insert" ? event.containerVid : void 0,
6786
+ index: event.kind === "move" ? event.payload.after.index : void 0,
6695
6787
  // Insert-specific fields
6696
6788
  displayName: event.kind === "insert" ? event.displayName : void 0,
6697
6789
  html: event.kind === "insert" ? event.html : void 0,
@@ -6699,6 +6791,7 @@ function serializeArrangeChange(event, index) {
6699
6791
  tsxImports: event.kind === "insert" ? event.tsxImports : void 0,
6700
6792
  containerId: event.kind === "insert" ? event.containerId : void 0,
6701
6793
  containerNodeKey: event.kind === "insert" ? event.containerNodeKey : void 0,
6794
+ insertedVid: event.kind === "insert" ? event.insertedVid : void 0,
6702
6795
  insertIndex: event.kind === "insert" ? event.index : void 0,
6703
6796
  beforeSiblingInspectorRef: event.kind === "insert" ? event.beforeSiblingInspectorRef : void 0,
6704
6797
  beforeSiblingNodeKey: event.kind === "insert" ? event.beforeSiblingNodeKey : void 0,
@@ -6955,6 +7048,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6955
7048
  const resolvedReorderRef = (0, import_react11.useRef)(null);
6956
7049
  const finalVisualKeyRef = (0, import_react11.useRef)(null);
6957
7050
  const dragStartPositionRef = (0, import_react11.useRef)(null);
7051
+ const droppableContainerIdsRef = (0, import_react11.useRef)(null);
6958
7052
  const sourceCommitLaneRef = (0, import_react11.useRef)(null);
6959
7053
  const commitLaneCacheRef = (0, import_react11.useRef)(/* @__PURE__ */ new Map());
6960
7054
  const safeZoneRef = (0, import_react11.useRef)(null);
@@ -6986,6 +7080,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6986
7080
  resolvedReorderRef,
6987
7081
  finalVisualKeyRef,
6988
7082
  dragStartPositionRef,
7083
+ droppableContainerIdsRef,
6989
7084
  sourceCommitLaneRef,
6990
7085
  commitLaneCacheRef,
6991
7086
  safeZoneRef,
@@ -8187,6 +8282,14 @@ function useDragVisualLoop({
8187
8282
 
8188
8283
  // src/client/dnd/design-mode/useDragLifecycle.ts
8189
8284
  var import_react13 = require("react");
8285
+ function emitRefused(code, cause, element) {
8286
+ dispatchRuntimeEvent({
8287
+ type: "drag-refused",
8288
+ code,
8289
+ cause,
8290
+ vid: element ? resolveVidFromElement(element) : null
8291
+ });
8292
+ }
8190
8293
  function resolveLaneAllowedContainers(anchorElement, srcContainerId) {
8191
8294
  if (!srcContainerId) return null;
8192
8295
  if (!resolveRowBinding(anchorElement)) return null;
@@ -8303,6 +8406,10 @@ function recordCommittedMove(args) {
8303
8406
  });
8304
8407
  const componentName = getEditorMeta(anchorEl)?.componentName;
8305
8408
  const operationId = `${command.at}:${anchorNodeId}`;
8409
+ const targetVid = resolveVidFromElement(selectedEl);
8410
+ const targetContainerId = command.to.parentNodeId ?? command.to.laneContainerId;
8411
+ const targetContainerEl = targetContainerId ? resolveNodeElement(targetContainerId) : null;
8412
+ const containerVid = targetContainerEl ? resolveVidFromElement(targetContainerEl) : null;
8306
8413
  const binding = resolveRowBinding(selectedEl);
8307
8414
  const beforePayload = buildMovePayloadWithEffectiveIdentities({
8308
8415
  placement: command.from,
@@ -8334,6 +8441,8 @@ function recordCommittedMove(args) {
8334
8441
  sourceAfterSibling: sourceAfterSiblingIdentity,
8335
8442
  diagnostics,
8336
8443
  binding,
8444
+ targetVid,
8445
+ containerVid,
8337
8446
  container: containerIdentity,
8338
8447
  beforeSibling: beforeSiblingIdentity,
8339
8448
  afterSibling: afterSiblingIdentity,
@@ -8345,6 +8454,9 @@ function recordCommittedMove(args) {
8345
8454
  type: "element-moved",
8346
8455
  operationId,
8347
8456
  commandType: command.type,
8457
+ targetVid,
8458
+ containerVid,
8459
+ index: command.to.index,
8348
8460
  selected: {
8349
8461
  nodeId: selectedNodeId,
8350
8462
  nodeKey: selectedNodeKey,
@@ -8407,6 +8519,7 @@ function useDragLifecycle({
8407
8519
  refs.resolvedReorderRef.current = null;
8408
8520
  refs.finalVisualKeyRef.current = null;
8409
8521
  refs.dragStartPositionRef.current = null;
8522
+ refs.droppableContainerIdsRef.current = null;
8410
8523
  clearCommitLaneCache();
8411
8524
  refs.safeZoneRef.current = null;
8412
8525
  setInsideRect(null);
@@ -8440,6 +8553,11 @@ function useDragLifecycle({
8440
8553
  eligibilitySource: found.eligibilitySource ?? "editor-meta",
8441
8554
  draggable: found.draggable
8442
8555
  });
8556
+ emitRefused(
8557
+ resolveRefusalCode(found.editorMeta, "GATE_REJECT"),
8558
+ "Node is not draggable (dynamic render, chrome, or kit-origin).",
8559
+ found.element
8560
+ );
8443
8561
  return;
8444
8562
  }
8445
8563
  veTrace("dnd", "drag:start", {
@@ -8463,12 +8581,29 @@ function useDragLifecycle({
8463
8581
  clearCommitLaneCache();
8464
8582
  const anchor = resolveDragAnchor(activeId, proj, refs.nodeMapRef.current);
8465
8583
  const anchorScanned = refs.nodeMapRef.current.get(anchor.anchorNodeId) ?? found;
8466
- if (!anchorScanned || !isEligibleForDrag(anchorScanned)) return;
8584
+ if (!anchorScanned || !isEligibleForDrag(anchorScanned)) {
8585
+ emitRefused(
8586
+ resolveRefusalCode(anchorScanned?.editorMeta, "GATE_REJECT"),
8587
+ "Resolved drag anchor is not draggable.",
8588
+ anchorScanned?.element ?? found.element
8589
+ );
8590
+ return;
8591
+ }
8467
8592
  const anchorNodeId = anchor.anchorNodeId;
8468
8593
  const srcContainerId = anchor.sourceContainerId;
8469
8594
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
8470
8595
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8471
8596
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8597
+ for (const id of buildBlockedNodeMetaContainerIds(proj, refs.nodeMapRef.current)) {
8598
+ blockedContainerIds.add(id);
8599
+ }
8600
+ const droppableSet = computeDroppableSet(anchorNodeId, proj, refs.nodeMapRef.current);
8601
+ refs.droppableContainerIdsRef.current = droppableSet;
8602
+ dispatchRuntimeEvent({
8603
+ type: "drag-started",
8604
+ targetVid: resolveVidFromElement(anchorScanned.element),
8605
+ droppable: Array.from(droppableSet)
8606
+ });
8472
8607
  const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8473
8608
  refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8474
8609
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
@@ -8541,6 +8676,19 @@ function useDragLifecycle({
8541
8676
  syncHistoryAvailability();
8542
8677
  return;
8543
8678
  }
8679
+ if (dropTargetId && !refs.droppableContainerIdsRef.current?.has(dropTargetId)) {
8680
+ const targetContainerEl = resolveNodeElement(dropTargetId);
8681
+ const targetMeta = refs.nodeMapRef.current.get(dropTargetId)?.editorMeta;
8682
+ const fallbackCode = targetMeta?.containerKind === "expression" ? "EXPRESSION_CONTAINER" : "CONTAINER_NOT_PLAIN";
8683
+ emitRefused(
8684
+ resolveRefusalCode(targetMeta, fallbackCode),
8685
+ "Drop target is outside the droppable set for this node.",
8686
+ targetContainerEl
8687
+ );
8688
+ clearDragState();
8689
+ syncHistoryAvailability();
8690
+ return;
8691
+ }
8544
8692
  let command = null;
8545
8693
  if (nestTarget) {
8546
8694
  if (!activeEl.contains(nestTarget.element) && !nestTarget.element.contains(activeEl) && sourcePos?.commitNodeId) {
@@ -8752,6 +8900,7 @@ function useResizeSession({
8752
8900
  return;
8753
8901
  }
8754
8902
  const inspectorRef = getInspectorRefFromElement(element);
8903
+ const targetVid = resolveVidFromElement(element);
8755
8904
  const resizeCmd = {
8756
8905
  type: "resize",
8757
8906
  mode,
@@ -8771,12 +8920,14 @@ function useResizeSession({
8771
8920
  nodeId: selectedNodeId,
8772
8921
  inspectorRef,
8773
8922
  componentName: getEditorMeta(element)?.componentName,
8923
+ targetVid,
8774
8924
  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 } },
8775
8925
  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 } }
8776
8926
  });
8777
8927
  dispatchRuntimeEvent({
8778
8928
  type: "element-resized",
8779
8929
  operationId,
8930
+ targetVid,
8780
8931
  selected: {
8781
8932
  nodeId: selectedNodeId,
8782
8933
  nodeKey: selectedNodeKey,
@@ -8999,6 +9150,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8999
9150
  };
9000
9151
  }
9001
9152
  const operationId = `${insertCommand.at}:${nodeId}`;
9153
+ const containerVid = containerEl ? resolveVidFromElement(containerEl) : null;
9154
+ const insertedVid = resolveVidFromElement(newElement);
9002
9155
  useDesignModeStore.getState().recordInsertChange({
9003
9156
  operationId,
9004
9157
  nodeKey,
@@ -9010,6 +9163,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
9010
9163
  tsxImports: component.tsxImports ?? null,
9011
9164
  containerId: intent.containerId,
9012
9165
  containerNodeKey,
9166
+ containerVid,
9167
+ insertedVid,
9013
9168
  index: intent.index,
9014
9169
  beforeSiblingInspectorRef: beforeSiblingIdentity?.inspectorRef ?? null,
9015
9170
  beforeSiblingNodeKey: beforeSiblingIdentity?.nodeKey ?? null
@@ -9024,6 +9179,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
9024
9179
  tsxCode: component.tsxCode ?? null,
9025
9180
  tsxImports: component.tsxImports ?? null,
9026
9181
  container: containerIdentity,
9182
+ containerVid,
9183
+ insertedVid,
9027
9184
  index: intent.index,
9028
9185
  beforeSibling: beforeSiblingIdentity
9029
9186
  });