@archie/devtools 0.0.47 → 0.1.0

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()) {
@@ -4230,6 +4292,7 @@ function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainer
4230
4292
  }
4231
4293
 
4232
4294
  // src/client/dnd/design-mode/fiber-bridge.ts
4295
+ var import_editor_protocol = require("@8base-archie/editor-protocol");
4233
4296
  var FIBER_KEY_PREFIX = "__reactFiber$";
4234
4297
  var PROPS_KEY_PREFIX = "__reactProps$";
4235
4298
  var metaCache = /* @__PURE__ */ new WeakMap();
@@ -4492,6 +4555,9 @@ function resolveDragSurface(el) {
4492
4555
  };
4493
4556
  }
4494
4557
 
4558
+ // src/types/editor-meta.ts
4559
+ var import_editor_protocol2 = require("@8base-archie/editor-protocol");
4560
+
4495
4561
  // src/client/dnd/design-mode/node-key.ts
4496
4562
  var _runtimeKeyMap = /* @__PURE__ */ new WeakMap();
4497
4563
  var _runtimeKeyCounter = 0;
@@ -4509,11 +4575,18 @@ function getNodeKeyFromElement(el) {
4509
4575
  const resolved = resolveDragSurface(el);
4510
4576
  const host = resolved.host ?? el;
4511
4577
  const meta = identityMeta(host);
4578
+ if (meta?.vid) return meta.vid;
4579
+ const domVid = host.getAttribute("data-vid");
4580
+ if (domVid) return domVid;
4512
4581
  if (meta) return meta.nodeId;
4513
4582
  const persisted = host.getAttribute("data-dnd-node-id");
4514
4583
  if (persisted) return persisted;
4515
4584
  return getRuntimeKey(host);
4516
4585
  }
4586
+ function resolveVidFromElement(el) {
4587
+ const key2 = getNodeKeyFromElement(el);
4588
+ return import_editor_protocol2.VID_PATTERN.test(key2) ? key2 : null;
4589
+ }
4517
4590
  function getInspectorRefFromElement(el) {
4518
4591
  const resolved = resolveDragSurface(el);
4519
4592
  const host = resolved.host ?? el;
@@ -4840,6 +4913,8 @@ var useDesignModeStore = create((set, get) => ({
4840
4913
  afterSibling,
4841
4914
  diagnostics,
4842
4915
  binding,
4916
+ targetVid,
4917
+ containerVid,
4843
4918
  componentName,
4844
4919
  before,
4845
4920
  after
@@ -4867,6 +4942,8 @@ var useDesignModeStore = create((set, get) => ({
4867
4942
  afterSibling: afterSibling ?? null,
4868
4943
  diagnostics: diagnostics ?? null,
4869
4944
  binding: binding ?? null,
4945
+ targetVid: targetVid ?? null,
4946
+ containerVid: containerVid ?? null,
4870
4947
  payload: { before, after },
4871
4948
  at: now
4872
4949
  };
@@ -4895,6 +4972,7 @@ var useDesignModeStore = create((set, get) => ({
4895
4972
  nodeId,
4896
4973
  inspectorRef,
4897
4974
  componentName,
4975
+ targetVid,
4898
4976
  before,
4899
4977
  after
4900
4978
  }) => {
@@ -4906,6 +4984,7 @@ var useDesignModeStore = create((set, get) => ({
4906
4984
  nodeKey,
4907
4985
  nodeId,
4908
4986
  inspectorRef,
4987
+ targetVid: targetVid ?? null,
4909
4988
  payload: { before, after },
4910
4989
  at: now
4911
4990
  };
@@ -4939,6 +5018,8 @@ var useDesignModeStore = create((set, get) => ({
4939
5018
  tsxImports,
4940
5019
  containerId,
4941
5020
  containerNodeKey,
5021
+ containerVid,
5022
+ insertedVid,
4942
5023
  index,
4943
5024
  beforeSiblingInspectorRef,
4944
5025
  beforeSiblingNodeKey
@@ -4957,6 +5038,8 @@ var useDesignModeStore = create((set, get) => ({
4957
5038
  tsxImports: tsxImports ?? null,
4958
5039
  containerId,
4959
5040
  containerNodeKey,
5041
+ containerVid: containerVid ?? null,
5042
+ insertedVid: insertedVid ?? null,
4960
5043
  index,
4961
5044
  beforeSiblingInspectorRef: beforeSiblingInspectorRef ?? null,
4962
5045
  beforeSiblingNodeKey: beforeSiblingNodeKey ?? null,
@@ -5070,6 +5153,7 @@ function shouldShowGrabFeedback(target, scannedByNodeId) {
5070
5153
  }
5071
5154
  function isEligibleForDrag(sc) {
5072
5155
  if (sc.draggable === false) return false;
5156
+ if (isNonDraggableNodeMeta(sc.editorMeta)) return false;
5073
5157
  const source = sc.eligibilitySource ?? "editor-meta";
5074
5158
  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
5159
  // Section, …) carrying the usage's projected meta.
@@ -6692,6 +6776,15 @@ function serializeArrangeChange(event, index) {
6692
6776
  diagnostics: event.kind === "move" ? event.diagnostics : void 0,
6693
6777
  // ARCH-1065 D17 — which slice row this move addresses.
6694
6778
  binding: event.kind === "move" ? event.binding : void 0,
6779
+ // `data-vid` identity migration (`host-bridge.ts`'s `RuntimeMoveDetail`
6780
+ // fields). `index` here is `event.payload.after.index` — the MOVE's own
6781
+ // JSX-child index — never the `index` PARAMETER above (this change's
6782
+ // sequence position in the session). `targetVid` also carries the
6783
+ // resized node's vid on a `resize` change; `containerVid` also carries
6784
+ // the drop container's vid on an `insert` change.
6785
+ targetVid: event.kind === "move" || event.kind === "resize" ? event.targetVid : void 0,
6786
+ containerVid: event.kind === "move" || event.kind === "insert" ? event.containerVid : void 0,
6787
+ index: event.kind === "move" ? event.payload.after.index : void 0,
6695
6788
  // Insert-specific fields
6696
6789
  displayName: event.kind === "insert" ? event.displayName : void 0,
6697
6790
  html: event.kind === "insert" ? event.html : void 0,
@@ -6699,6 +6792,7 @@ function serializeArrangeChange(event, index) {
6699
6792
  tsxImports: event.kind === "insert" ? event.tsxImports : void 0,
6700
6793
  containerId: event.kind === "insert" ? event.containerId : void 0,
6701
6794
  containerNodeKey: event.kind === "insert" ? event.containerNodeKey : void 0,
6795
+ insertedVid: event.kind === "insert" ? event.insertedVid : void 0,
6702
6796
  insertIndex: event.kind === "insert" ? event.index : void 0,
6703
6797
  beforeSiblingInspectorRef: event.kind === "insert" ? event.beforeSiblingInspectorRef : void 0,
6704
6798
  beforeSiblingNodeKey: event.kind === "insert" ? event.beforeSiblingNodeKey : void 0,
@@ -6955,6 +7049,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6955
7049
  const resolvedReorderRef = (0, import_react11.useRef)(null);
6956
7050
  const finalVisualKeyRef = (0, import_react11.useRef)(null);
6957
7051
  const dragStartPositionRef = (0, import_react11.useRef)(null);
7052
+ const droppableContainerIdsRef = (0, import_react11.useRef)(null);
6958
7053
  const sourceCommitLaneRef = (0, import_react11.useRef)(null);
6959
7054
  const commitLaneCacheRef = (0, import_react11.useRef)(/* @__PURE__ */ new Map());
6960
7055
  const safeZoneRef = (0, import_react11.useRef)(null);
@@ -6986,6 +7081,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6986
7081
  resolvedReorderRef,
6987
7082
  finalVisualKeyRef,
6988
7083
  dragStartPositionRef,
7084
+ droppableContainerIdsRef,
6989
7085
  sourceCommitLaneRef,
6990
7086
  commitLaneCacheRef,
6991
7087
  safeZoneRef,
@@ -8187,6 +8283,14 @@ function useDragVisualLoop({
8187
8283
 
8188
8284
  // src/client/dnd/design-mode/useDragLifecycle.ts
8189
8285
  var import_react13 = require("react");
8286
+ function emitRefused(code, cause, element) {
8287
+ dispatchRuntimeEvent({
8288
+ type: "drag-refused",
8289
+ code,
8290
+ cause,
8291
+ vid: element ? resolveVidFromElement(element) : null
8292
+ });
8293
+ }
8190
8294
  function resolveLaneAllowedContainers(anchorElement, srcContainerId) {
8191
8295
  if (!srcContainerId) return null;
8192
8296
  if (!resolveRowBinding(anchorElement)) return null;
@@ -8303,6 +8407,10 @@ function recordCommittedMove(args) {
8303
8407
  });
8304
8408
  const componentName = getEditorMeta(anchorEl)?.componentName;
8305
8409
  const operationId = `${command.at}:${anchorNodeId}`;
8410
+ const targetVid = resolveVidFromElement(selectedEl);
8411
+ const targetContainerId = command.to.parentNodeId ?? command.to.laneContainerId;
8412
+ const targetContainerEl = targetContainerId ? resolveNodeElement(targetContainerId) : null;
8413
+ const containerVid = targetContainerEl ? resolveVidFromElement(targetContainerEl) : null;
8306
8414
  const binding = resolveRowBinding(selectedEl);
8307
8415
  const beforePayload = buildMovePayloadWithEffectiveIdentities({
8308
8416
  placement: command.from,
@@ -8334,6 +8442,8 @@ function recordCommittedMove(args) {
8334
8442
  sourceAfterSibling: sourceAfterSiblingIdentity,
8335
8443
  diagnostics,
8336
8444
  binding,
8445
+ targetVid,
8446
+ containerVid,
8337
8447
  container: containerIdentity,
8338
8448
  beforeSibling: beforeSiblingIdentity,
8339
8449
  afterSibling: afterSiblingIdentity,
@@ -8345,6 +8455,9 @@ function recordCommittedMove(args) {
8345
8455
  type: "element-moved",
8346
8456
  operationId,
8347
8457
  commandType: command.type,
8458
+ targetVid,
8459
+ containerVid,
8460
+ index: command.to.index,
8348
8461
  selected: {
8349
8462
  nodeId: selectedNodeId,
8350
8463
  nodeKey: selectedNodeKey,
@@ -8407,6 +8520,7 @@ function useDragLifecycle({
8407
8520
  refs.resolvedReorderRef.current = null;
8408
8521
  refs.finalVisualKeyRef.current = null;
8409
8522
  refs.dragStartPositionRef.current = null;
8523
+ refs.droppableContainerIdsRef.current = null;
8410
8524
  clearCommitLaneCache();
8411
8525
  refs.safeZoneRef.current = null;
8412
8526
  setInsideRect(null);
@@ -8440,6 +8554,11 @@ function useDragLifecycle({
8440
8554
  eligibilitySource: found.eligibilitySource ?? "editor-meta",
8441
8555
  draggable: found.draggable
8442
8556
  });
8557
+ emitRefused(
8558
+ resolveRefusalCode(found.editorMeta, "GATE_REJECT"),
8559
+ "Node is not draggable (dynamic render, chrome, or kit-origin).",
8560
+ found.element
8561
+ );
8443
8562
  return;
8444
8563
  }
8445
8564
  veTrace("dnd", "drag:start", {
@@ -8463,12 +8582,29 @@ function useDragLifecycle({
8463
8582
  clearCommitLaneCache();
8464
8583
  const anchor = resolveDragAnchor(activeId, proj, refs.nodeMapRef.current);
8465
8584
  const anchorScanned = refs.nodeMapRef.current.get(anchor.anchorNodeId) ?? found;
8466
- if (!anchorScanned || !isEligibleForDrag(anchorScanned)) return;
8585
+ if (!anchorScanned || !isEligibleForDrag(anchorScanned)) {
8586
+ emitRefused(
8587
+ resolveRefusalCode(anchorScanned?.editorMeta, "GATE_REJECT"),
8588
+ "Resolved drag anchor is not draggable.",
8589
+ anchorScanned?.element ?? found.element
8590
+ );
8591
+ return;
8592
+ }
8467
8593
  const anchorNodeId = anchor.anchorNodeId;
8468
8594
  const srcContainerId = anchor.sourceContainerId;
8469
8595
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
8470
8596
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8471
8597
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8598
+ for (const id of buildBlockedNodeMetaContainerIds(proj, refs.nodeMapRef.current)) {
8599
+ blockedContainerIds.add(id);
8600
+ }
8601
+ const droppableSet = computeDroppableSet(anchorNodeId, proj, refs.nodeMapRef.current);
8602
+ refs.droppableContainerIdsRef.current = droppableSet;
8603
+ dispatchRuntimeEvent({
8604
+ type: "drag-started",
8605
+ targetVid: resolveVidFromElement(anchorScanned.element),
8606
+ droppable: Array.from(droppableSet)
8607
+ });
8472
8608
  const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8473
8609
  refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8474
8610
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
@@ -8541,6 +8677,19 @@ function useDragLifecycle({
8541
8677
  syncHistoryAvailability();
8542
8678
  return;
8543
8679
  }
8680
+ if (dropTargetId && !refs.droppableContainerIdsRef.current?.has(dropTargetId)) {
8681
+ const targetContainerEl = resolveNodeElement(dropTargetId);
8682
+ const targetMeta = refs.nodeMapRef.current.get(dropTargetId)?.editorMeta;
8683
+ const fallbackCode = targetMeta?.containerKind === "expression" ? "EXPRESSION_CONTAINER" : "CONTAINER_NOT_PLAIN";
8684
+ emitRefused(
8685
+ resolveRefusalCode(targetMeta, fallbackCode),
8686
+ "Drop target is outside the droppable set for this node.",
8687
+ targetContainerEl
8688
+ );
8689
+ clearDragState();
8690
+ syncHistoryAvailability();
8691
+ return;
8692
+ }
8544
8693
  let command = null;
8545
8694
  if (nestTarget) {
8546
8695
  if (!activeEl.contains(nestTarget.element) && !nestTarget.element.contains(activeEl) && sourcePos?.commitNodeId) {
@@ -8752,6 +8901,7 @@ function useResizeSession({
8752
8901
  return;
8753
8902
  }
8754
8903
  const inspectorRef = getInspectorRefFromElement(element);
8904
+ const targetVid = resolveVidFromElement(element);
8755
8905
  const resizeCmd = {
8756
8906
  type: "resize",
8757
8907
  mode,
@@ -8771,12 +8921,14 @@ function useResizeSession({
8771
8921
  nodeId: selectedNodeId,
8772
8922
  inspectorRef,
8773
8923
  componentName: getEditorMeta(element)?.componentName,
8924
+ targetVid,
8774
8925
  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
8926
  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
8927
  });
8777
8928
  dispatchRuntimeEvent({
8778
8929
  type: "element-resized",
8779
8930
  operationId,
8931
+ targetVid,
8780
8932
  selected: {
8781
8933
  nodeId: selectedNodeId,
8782
8934
  nodeKey: selectedNodeKey,
@@ -8999,6 +9151,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8999
9151
  };
9000
9152
  }
9001
9153
  const operationId = `${insertCommand.at}:${nodeId}`;
9154
+ const containerVid = containerEl ? resolveVidFromElement(containerEl) : null;
9155
+ const insertedVid = resolveVidFromElement(newElement);
9002
9156
  useDesignModeStore.getState().recordInsertChange({
9003
9157
  operationId,
9004
9158
  nodeKey,
@@ -9010,6 +9164,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
9010
9164
  tsxImports: component.tsxImports ?? null,
9011
9165
  containerId: intent.containerId,
9012
9166
  containerNodeKey,
9167
+ containerVid,
9168
+ insertedVid,
9013
9169
  index: intent.index,
9014
9170
  beforeSiblingInspectorRef: beforeSiblingIdentity?.inspectorRef ?? null,
9015
9171
  beforeSiblingNodeKey: beforeSiblingIdentity?.nodeKey ?? null
@@ -9024,6 +9180,8 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
9024
9180
  tsxCode: component.tsxCode ?? null,
9025
9181
  tsxImports: component.tsxImports ?? null,
9026
9182
  container: containerIdentity,
9183
+ containerVid,
9184
+ insertedVid,
9027
9185
  index: intent.index,
9028
9186
  beforeSibling: beforeSiblingIdentity
9029
9187
  });