@archie/devtools 0.0.30 → 0.0.32

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.
@@ -534,6 +534,11 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
534
534
  parentNodeId ? t.stringLiteral(parentNodeId) : t.nullLiteral()
535
535
  )
536
536
  ]);
537
+ if (isKitOrigin) {
538
+ metaObj.properties.push(
539
+ t.objectProperty(t.identifier("origin"), t.stringLiteral("kit"))
540
+ );
541
+ }
537
542
  if (slot !== null) {
538
543
  metaObj.properties.push(
539
544
  t.objectProperty(t.identifier("slot"), t.stringLiteral(slot))
@@ -4160,6 +4160,7 @@ var FIBER_KEY_PREFIX = "__reactFiber$";
4160
4160
  var PROPS_KEY_PREFIX = "__reactProps$";
4161
4161
  var metaCache = /* @__PURE__ */ new WeakMap();
4162
4162
  var directMetaCache = /* @__PURE__ */ new WeakMap();
4163
+ var usageMetaCache = /* @__PURE__ */ new WeakMap();
4163
4164
  function readEditorMetaValue(value) {
4164
4165
  if (typeof value === "function") {
4165
4166
  return value();
@@ -4324,6 +4325,34 @@ function getSingleHostElement(owner) {
4324
4325
  }
4325
4326
  return hosts.length === 1 ? hosts[0] : null;
4326
4327
  }
4328
+ function getUsageMetaOwnerFiber(el) {
4329
+ let fiber = getFiberSelectionFromElement(el).fiber;
4330
+ while (fiber) {
4331
+ const meta = readEditorMetaValue(fiber.memoizedProps?.__editorMeta);
4332
+ if (meta && meta.origin !== "kit") return fiber;
4333
+ fiber = fiber.return;
4334
+ }
4335
+ return null;
4336
+ }
4337
+ function projectFromOwner(owner, el) {
4338
+ if (!owner) return null;
4339
+ if (getSingleHostElement(owner) !== el) return null;
4340
+ return readEditorMetaValue(owner.memoizedProps?.__editorMeta);
4341
+ }
4342
+ function projectUsageMeta(el) {
4343
+ const selection = getFiberSelectionFromElement(el);
4344
+ const cached = readFiberAwareCache(usageMetaCache, el, selection);
4345
+ if (cached !== void 0) return cached;
4346
+ return writeFiberAwareCache(usageMetaCache, el, selection, computeUsageMeta(el));
4347
+ }
4348
+ function computeUsageMeta(el) {
4349
+ const direct = getDirectEditorMeta(el);
4350
+ if (direct && direct.origin !== "kit") return null;
4351
+ const usage = projectFromOwner(getUsageMetaOwnerFiber(el), el);
4352
+ if (usage) return usage;
4353
+ if (direct) return null;
4354
+ return projectFromOwner(getEditorMetaOwnerFiber(el), el);
4355
+ }
4327
4356
  function findMetaOwnerElement(el) {
4328
4357
  let current = el;
4329
4358
  while (current) {
@@ -4391,10 +4420,13 @@ function getRuntimeKey(el) {
4391
4420
  _runtimeKeyMap.set(el, key2);
4392
4421
  return key2;
4393
4422
  }
4423
+ function identityMeta(host) {
4424
+ return projectUsageMeta(host) ?? getEditorMeta(host);
4425
+ }
4394
4426
  function getNodeKeyFromElement(el) {
4395
4427
  const resolved = resolveDragSurface(el);
4396
4428
  const host = resolved.host ?? el;
4397
- const meta = getEditorMeta(host);
4429
+ const meta = identityMeta(host);
4398
4430
  if (meta) return meta.nodeId;
4399
4431
  const persisted = host.getAttribute("data-dnd-node-id");
4400
4432
  if (persisted) return persisted;
@@ -4403,7 +4435,7 @@ function getNodeKeyFromElement(el) {
4403
4435
  function getInspectorRefFromElement(el) {
4404
4436
  const resolved = resolveDragSurface(el);
4405
4437
  const host = resolved.host ?? el;
4406
- const meta = getEditorMeta(host);
4438
+ const meta = identityMeta(host);
4407
4439
  if (!meta) return null;
4408
4440
  return { relativePath: meta.file, line: meta.line, column: meta.col };
4409
4441
  }
@@ -5081,13 +5113,6 @@ function isInspectorEligible(el) {
5081
5113
  if (el.hasAttribute("data-dnd-force-allow")) return "force-allow";
5082
5114
  return null;
5083
5115
  }
5084
- function projectUsageMeta(el) {
5085
- if (getDirectEditorMeta(el)) return null;
5086
- const owner = getEditorMetaOwnerFiber(el);
5087
- if (!owner) return null;
5088
- if (getSingleHostElement(owner) !== el) return null;
5089
- return readEditorMetaValue(owner.memoizedProps?.__editorMeta);
5090
- }
5091
5116
  function isRuntimeVisualInternal(el) {
5092
5117
  if (shouldIgnoreAsVirtualRuntime(el)) return true;
5093
5118
  const tag = el.tagName.toLowerCase();
@@ -5117,10 +5142,10 @@ function ensureUniqueNodeId(base, used) {
5117
5142
  function resolveStableNodeId(el, used) {
5118
5143
  const persisted = el.getAttribute(DND_NODE_ID_ATTR);
5119
5144
  if (persisted && !used.has(persisted)) return persisted;
5120
- const metaId = getEditorMetaNodeId(el);
5121
- if (metaId) return ensureUniqueNodeId(metaId, used);
5122
5145
  const projected = projectUsageMeta(el);
5123
5146
  if (projected) return ensureUniqueNodeId(projected.nodeId, used);
5147
+ const metaId = getEditorMetaNodeId(el);
5148
+ if (metaId) return ensureUniqueNodeId(metaId, used);
5124
5149
  return ensureUniqueNodeId(getRuntimeNodeId(el), used);
5125
5150
  }
5126
5151
  function getUniqueSelector(el) {
@@ -5259,13 +5284,15 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5259
5284
  const surfaceMeta = hostEl instanceof HTMLElement ? getDirectEditorMeta(hostEl) ?? void 0 : void 0;
5260
5285
  const ownerMeta = dragSurface.metaOwner && dragSurface.metaOwner instanceof HTMLElement ? getEditorMeta(dragSurface.metaOwner) : null;
5261
5286
  const resolvedSelector = getUniqueSelector(hostEl);
5262
- const draggable = inferredEligibility === "force-allow" || !nodeId.startsWith("runtime:");
5287
+ const usageMeta = hostEl instanceof HTMLElement ? projectUsageMeta(hostEl) : null;
5288
+ const identityMeta2 = usageMeta ?? ownerMeta ?? surfaceMeta;
5289
+ const draggable = inferredEligibility === "force-allow" || !nodeId.startsWith("runtime:") && (identityMeta2?.origin !== "kit" || resolveRowBinding(hostEl) !== null);
5263
5290
  results.push({
5264
5291
  nodeId,
5265
5292
  element: hostEl,
5266
5293
  selector: resolvedSelector,
5267
5294
  draggable,
5268
- sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5295
+ sourceMetaNodeId: identityMeta2?.nodeId ?? null,
5269
5296
  resolvedHostNodeId: nodeId,
5270
5297
  resolvedHostSelector: resolvedSelector,
5271
5298
  resolutionKind: dragSurface.kind,
@@ -5273,6 +5300,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5273
5300
  rect: hostRect,
5274
5301
  containerStrategy,
5275
5302
  eligibilitySource: inferredEligibility,
5303
+ identityOrigin: identityMeta2?.origin ?? null,
5276
5304
  editorMeta: surfaceMeta
5277
5305
  });
5278
5306
  if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
@@ -5282,6 +5310,38 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5282
5310
  }
5283
5311
  return results;
5284
5312
  }
5313
+ function buildMovedElementEntry(el, usedNodeIds) {
5314
+ const rect = el.getBoundingClientRect();
5315
+ if (rect.width === 0 || rect.height === 0) return null;
5316
+ const nodeId = resolveStableNodeId(el, usedNodeIds);
5317
+ el.setAttribute(DND_NODE_ID_ATTR, nodeId);
5318
+ const parentEl = el.parentElement;
5319
+ const surfaceMeta = getDirectEditorMeta(el) ?? void 0;
5320
+ const ownerMeta = getEditorMeta(el);
5321
+ const identityMeta2 = projectUsageMeta(el) ?? ownerMeta ?? surfaceMeta;
5322
+ const selector = getUniqueSelector(el);
5323
+ const eligibility = isInspectorEligible(el);
5324
+ return {
5325
+ nodeId,
5326
+ element: el,
5327
+ selector,
5328
+ // Same draggable rule as the main scan, INCLUDING the force-allow
5329
+ // escape — an element that was draggable when the user moved it must
5330
+ // not silently lose draggability for having landed below MAX_DEPTH.
5331
+ // Same kit-origin bound-row exemption as the main scan (see there).
5332
+ draggable: eligibility === "force-allow" || !nodeId.startsWith("runtime:") && (identityMeta2?.origin !== "kit" || resolveRowBinding(el) !== null),
5333
+ sourceMetaNodeId: identityMeta2?.nodeId ?? null,
5334
+ resolvedHostNodeId: nodeId,
5335
+ resolvedHostSelector: selector,
5336
+ resolutionKind: ownerMeta && ownerMeta.componentName[0] !== ownerMeta.componentName[0].toLowerCase() ? "wrapper-to-host" : "direct-host",
5337
+ label: getLabel(el),
5338
+ rect,
5339
+ containerStrategy: parentEl && isFlexOrGridParent(parentEl) ? resolveContainerStrategy(parentEl) : null,
5340
+ eligibilitySource: eligibility ?? "force-allow",
5341
+ identityOrigin: identityMeta2?.origin ?? null,
5342
+ editorMeta: surfaceMeta
5343
+ };
5344
+ }
5285
5345
  function useElementScanner(enabled) {
5286
5346
  const [elements, setElements] = (0, import_react6.useState)([]);
5287
5347
  const rafRef = (0, import_react6.useRef)(0);
@@ -5298,30 +5358,10 @@ function useElementScanner(enabled) {
5298
5358
  const seenEls = new Set(scanned.map((s) => s.element));
5299
5359
  for (const el of Array.from(document.querySelectorAll("[data-dnd-moved]"))) {
5300
5360
  if (seenEls.has(el)) continue;
5301
- const rect = el.getBoundingClientRect();
5302
- if (rect.width === 0 || rect.height === 0) continue;
5303
- const nodeId = resolveStableNodeId(el, usedNodeIds);
5304
- usedNodeIds.add(nodeId);
5305
- el.setAttribute(DND_NODE_ID_ATTR, nodeId);
5306
- const parentEl = el.parentElement;
5307
- const surfaceMeta = getDirectEditorMeta(el) ?? void 0;
5308
- const ownerMeta = getEditorMeta(el);
5309
- const selector = getUniqueSelector(el);
5310
- scanned.push({
5311
- nodeId,
5312
- element: el,
5313
- selector,
5314
- draggable: !nodeId.startsWith("runtime:"),
5315
- sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5316
- resolvedHostNodeId: nodeId,
5317
- resolvedHostSelector: selector,
5318
- resolutionKind: ownerMeta && ownerMeta.componentName[0] !== ownerMeta.componentName[0].toLowerCase() ? "wrapper-to-host" : "direct-host",
5319
- label: getLabel(el),
5320
- rect,
5321
- containerStrategy: parentEl && isFlexOrGridParent(parentEl) ? resolveContainerStrategy(parentEl) : null,
5322
- eligibilitySource: isInspectorEligible(el) ?? "force-allow",
5323
- editorMeta: surfaceMeta
5324
- });
5361
+ const entry = buildMovedElementEntry(el, usedNodeIds);
5362
+ if (!entry) continue;
5363
+ usedNodeIds.add(entry.nodeId);
5364
+ scanned.push(entry);
5325
5365
  }
5326
5366
  const seen = /* @__PURE__ */ new Set();
5327
5367
  setElements(scanned.filter((el) => {
@@ -6696,6 +6736,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6696
6736
  const lastIntentRef = (0, import_react11.useRef)(null);
6697
6737
  const visualIntentRef = (0, import_react11.useRef)(null);
6698
6738
  const sourceContainerIdRef = (0, import_react11.useRef)(null);
6739
+ const rowLockedRef = (0, import_react11.useRef)(false);
6699
6740
  const dragSessionAnchorRef = (0, import_react11.useRef)(null);
6700
6741
  const lockRef = (0, import_react11.useRef)({ containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null });
6701
6742
  const targetPlaneRef = (0, import_react11.useRef)("lane");
@@ -6727,6 +6768,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6727
6768
  lastIntentRef,
6728
6769
  visualIntentRef,
6729
6770
  sourceContainerIdRef,
6771
+ rowLockedRef,
6730
6772
  dragSessionAnchorRef,
6731
6773
  lockRef,
6732
6774
  targetPlaneRef,
@@ -7632,6 +7674,12 @@ function buildPreviewLayout({
7632
7674
  var CONTAINER_SWITCH_HYSTERESIS = 12;
7633
7675
  var SMALL_NODE_NEST_DWELL_MS = 100;
7634
7676
  var LARGE_NODE_NEST_DWELL_MS = 320;
7677
+ function computeAllowNest(nestPreview, isLargeDraggedNode, rowLocked) {
7678
+ if (rowLocked) return false;
7679
+ if (!nestPreview) return false;
7680
+ const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7681
+ return !!childId && !isLargeDraggedNode;
7682
+ }
7635
7683
  function useDragVisualLoop({
7636
7684
  activeDrag,
7637
7685
  refs,
@@ -7681,11 +7729,7 @@ function useDragVisualLoop({
7681
7729
  const adjustedPy = py + scrollDy;
7682
7730
  const nestPreview = engine.peekNestIntent(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7683
7731
  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
- }
7732
+ const allowNest = computeAllowNest(nestPreview, isLargeDraggedNode, !!refs.rowLockedRef.current);
7689
7733
  if (!allowNest) {
7690
7734
  refs.nestCandidateRef.current = null;
7691
7735
  refs.nestTargetRef.current = null;
@@ -7870,6 +7914,18 @@ function useDragVisualLoop({
7870
7914
 
7871
7915
  // src/client/dnd/design-mode/useDragLifecycle.ts
7872
7916
  var import_react13 = require("react");
7917
+ function resolveLaneAllowedContainers(anchorElement, srcContainerId) {
7918
+ if (!srcContainerId) return null;
7919
+ if (!resolveRowBinding(anchorElement)) return null;
7920
+ return /* @__PURE__ */ new Set([srcContainerId]);
7921
+ }
7922
+ function buildDragIntentEngineOptions(anchorElement, srcContainerId, blockedContainerIds) {
7923
+ return {
7924
+ laneRootContainerId: srcContainerId,
7925
+ laneAllowedContainers: resolveLaneAllowedContainers(anchorElement, srcContainerId),
7926
+ blockedContainerIds
7927
+ };
7928
+ }
7873
7929
  function buildRuntimeIdentityLocal(nodeId, resolveNodeElement, effectiveLaneSnapshot) {
7874
7930
  if (!nodeId) return null;
7875
7931
  const element = resolveNodeElement(nodeId);
@@ -8069,6 +8125,7 @@ function useDragLifecycle({
8069
8125
  refs.lastIntentRef.current = null;
8070
8126
  refs.visualIntentRef.current = null;
8071
8127
  refs.sourceContainerIdRef.current = null;
8128
+ refs.rowLockedRef.current = false;
8072
8129
  refs.dragSessionAnchorRef.current = null;
8073
8130
  refs.lockRef.current = { containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8074
8131
  refs.targetPlaneRef.current = "lane";
@@ -8124,10 +8181,9 @@ function useDragLifecycle({
8124
8181
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8125
8182
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8126
8183
  refs.blockedContainerIdsRef.current = blockedContainerIds;
8127
- refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, {
8128
- laneRootContainerId: srcContainerId,
8129
- blockedContainerIds
8130
- });
8184
+ const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8185
+ refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8186
+ refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
8131
8187
  refs.engineRef.current.setTargetPlane("lane");
8132
8188
  refs.targetPlaneRef.current = "lane";
8133
8189
  refs.sourceContainerIdRef.current = srcContainerId;
@@ -8510,7 +8566,9 @@ function useExternalDragLoop({
8510
8566
  externalDrag,
8511
8567
  refs,
8512
8568
  resolveNodeElement,
8513
- setDropLine
8569
+ setDropLine,
8570
+ scrollX,
8571
+ scrollY
8514
8572
  );
8515
8573
  return;
8516
8574
  }
@@ -8543,10 +8601,8 @@ function useExternalDragLoop({
8543
8601
  return () => cancelAnimationFrame(rafId);
8544
8602
  }, [externalDrag, elements, refs, resolveNodeElement, setDropLine]);
8545
8603
  }
8546
- function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, setDropLine) {
8547
- const scrollX = window.scrollX;
8548
- const scrollY = window.scrollY;
8549
- const intent = engine.resolve(x, y, scrollX, scrollY, scrollX, scrollY);
8604
+ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, setDropLine, startScrollX, startScrollY) {
8605
+ const intent = engine.resolve(x, y, window.scrollX, window.scrollY, startScrollX, startScrollY);
8550
8606
  if (!intent || intent.mode !== "reorder") {
8551
8607
  useDesignModeStore.getState().cancelExternalDrag();
8552
8608
  setDropLine(null);
@@ -4125,6 +4125,7 @@ var FIBER_KEY_PREFIX = "__reactFiber$";
4125
4125
  var PROPS_KEY_PREFIX = "__reactProps$";
4126
4126
  var metaCache = /* @__PURE__ */ new WeakMap();
4127
4127
  var directMetaCache = /* @__PURE__ */ new WeakMap();
4128
+ var usageMetaCache = /* @__PURE__ */ new WeakMap();
4128
4129
  function readEditorMetaValue(value) {
4129
4130
  if (typeof value === "function") {
4130
4131
  return value();
@@ -4289,6 +4290,34 @@ function getSingleHostElement(owner) {
4289
4290
  }
4290
4291
  return hosts.length === 1 ? hosts[0] : null;
4291
4292
  }
4293
+ function getUsageMetaOwnerFiber(el) {
4294
+ let fiber = getFiberSelectionFromElement(el).fiber;
4295
+ while (fiber) {
4296
+ const meta = readEditorMetaValue(fiber.memoizedProps?.__editorMeta);
4297
+ if (meta && meta.origin !== "kit") return fiber;
4298
+ fiber = fiber.return;
4299
+ }
4300
+ return null;
4301
+ }
4302
+ function projectFromOwner(owner, el) {
4303
+ if (!owner) return null;
4304
+ if (getSingleHostElement(owner) !== el) return null;
4305
+ return readEditorMetaValue(owner.memoizedProps?.__editorMeta);
4306
+ }
4307
+ function projectUsageMeta(el) {
4308
+ const selection = getFiberSelectionFromElement(el);
4309
+ const cached = readFiberAwareCache(usageMetaCache, el, selection);
4310
+ if (cached !== void 0) return cached;
4311
+ return writeFiberAwareCache(usageMetaCache, el, selection, computeUsageMeta(el));
4312
+ }
4313
+ function computeUsageMeta(el) {
4314
+ const direct = getDirectEditorMeta(el);
4315
+ if (direct && direct.origin !== "kit") return null;
4316
+ const usage = projectFromOwner(getUsageMetaOwnerFiber(el), el);
4317
+ if (usage) return usage;
4318
+ if (direct) return null;
4319
+ return projectFromOwner(getEditorMetaOwnerFiber(el), el);
4320
+ }
4292
4321
  function findMetaOwnerElement(el) {
4293
4322
  let current = el;
4294
4323
  while (current) {
@@ -4356,10 +4385,13 @@ function getRuntimeKey(el) {
4356
4385
  _runtimeKeyMap.set(el, key2);
4357
4386
  return key2;
4358
4387
  }
4388
+ function identityMeta(host) {
4389
+ return projectUsageMeta(host) ?? getEditorMeta(host);
4390
+ }
4359
4391
  function getNodeKeyFromElement(el) {
4360
4392
  const resolved = resolveDragSurface(el);
4361
4393
  const host = resolved.host ?? el;
4362
- const meta = getEditorMeta(host);
4394
+ const meta = identityMeta(host);
4363
4395
  if (meta) return meta.nodeId;
4364
4396
  const persisted = host.getAttribute("data-dnd-node-id");
4365
4397
  if (persisted) return persisted;
@@ -4368,7 +4400,7 @@ function getNodeKeyFromElement(el) {
4368
4400
  function getInspectorRefFromElement(el) {
4369
4401
  const resolved = resolveDragSurface(el);
4370
4402
  const host = resolved.host ?? el;
4371
- const meta = getEditorMeta(host);
4403
+ const meta = identityMeta(host);
4372
4404
  if (!meta) return null;
4373
4405
  return { relativePath: meta.file, line: meta.line, column: meta.col };
4374
4406
  }
@@ -5046,13 +5078,6 @@ function isInspectorEligible(el) {
5046
5078
  if (el.hasAttribute("data-dnd-force-allow")) return "force-allow";
5047
5079
  return null;
5048
5080
  }
5049
- function projectUsageMeta(el) {
5050
- if (getDirectEditorMeta(el)) return null;
5051
- const owner = getEditorMetaOwnerFiber(el);
5052
- if (!owner) return null;
5053
- if (getSingleHostElement(owner) !== el) return null;
5054
- return readEditorMetaValue(owner.memoizedProps?.__editorMeta);
5055
- }
5056
5081
  function isRuntimeVisualInternal(el) {
5057
5082
  if (shouldIgnoreAsVirtualRuntime(el)) return true;
5058
5083
  const tag = el.tagName.toLowerCase();
@@ -5082,10 +5107,10 @@ function ensureUniqueNodeId(base, used) {
5082
5107
  function resolveStableNodeId(el, used) {
5083
5108
  const persisted = el.getAttribute(DND_NODE_ID_ATTR);
5084
5109
  if (persisted && !used.has(persisted)) return persisted;
5085
- const metaId = getEditorMetaNodeId(el);
5086
- if (metaId) return ensureUniqueNodeId(metaId, used);
5087
5110
  const projected = projectUsageMeta(el);
5088
5111
  if (projected) return ensureUniqueNodeId(projected.nodeId, used);
5112
+ const metaId = getEditorMetaNodeId(el);
5113
+ if (metaId) return ensureUniqueNodeId(metaId, used);
5089
5114
  return ensureUniqueNodeId(getRuntimeNodeId(el), used);
5090
5115
  }
5091
5116
  function getUniqueSelector(el) {
@@ -5224,13 +5249,15 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5224
5249
  const surfaceMeta = hostEl instanceof HTMLElement ? getDirectEditorMeta(hostEl) ?? void 0 : void 0;
5225
5250
  const ownerMeta = dragSurface.metaOwner && dragSurface.metaOwner instanceof HTMLElement ? getEditorMeta(dragSurface.metaOwner) : null;
5226
5251
  const resolvedSelector = getUniqueSelector(hostEl);
5227
- const draggable = inferredEligibility === "force-allow" || !nodeId.startsWith("runtime:");
5252
+ const usageMeta = hostEl instanceof HTMLElement ? projectUsageMeta(hostEl) : null;
5253
+ const identityMeta2 = usageMeta ?? ownerMeta ?? surfaceMeta;
5254
+ const draggable = inferredEligibility === "force-allow" || !nodeId.startsWith("runtime:") && (identityMeta2?.origin !== "kit" || resolveRowBinding(hostEl) !== null);
5228
5255
  results.push({
5229
5256
  nodeId,
5230
5257
  element: hostEl,
5231
5258
  selector: resolvedSelector,
5232
5259
  draggable,
5233
- sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5260
+ sourceMetaNodeId: identityMeta2?.nodeId ?? null,
5234
5261
  resolvedHostNodeId: nodeId,
5235
5262
  resolvedHostSelector: resolvedSelector,
5236
5263
  resolutionKind: dragSurface.kind,
@@ -5238,6 +5265,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5238
5265
  rect: hostRect,
5239
5266
  containerStrategy,
5240
5267
  eligibilitySource: inferredEligibility,
5268
+ identityOrigin: identityMeta2?.origin ?? null,
5241
5269
  editorMeta: surfaceMeta
5242
5270
  });
5243
5271
  if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
@@ -5247,6 +5275,38 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5247
5275
  }
5248
5276
  return results;
5249
5277
  }
5278
+ function buildMovedElementEntry(el, usedNodeIds) {
5279
+ const rect = el.getBoundingClientRect();
5280
+ if (rect.width === 0 || rect.height === 0) return null;
5281
+ const nodeId = resolveStableNodeId(el, usedNodeIds);
5282
+ el.setAttribute(DND_NODE_ID_ATTR, nodeId);
5283
+ const parentEl = el.parentElement;
5284
+ const surfaceMeta = getDirectEditorMeta(el) ?? void 0;
5285
+ const ownerMeta = getEditorMeta(el);
5286
+ const identityMeta2 = projectUsageMeta(el) ?? ownerMeta ?? surfaceMeta;
5287
+ const selector = getUniqueSelector(el);
5288
+ const eligibility = isInspectorEligible(el);
5289
+ return {
5290
+ nodeId,
5291
+ element: el,
5292
+ selector,
5293
+ // Same draggable rule as the main scan, INCLUDING the force-allow
5294
+ // escape — an element that was draggable when the user moved it must
5295
+ // not silently lose draggability for having landed below MAX_DEPTH.
5296
+ // Same kit-origin bound-row exemption as the main scan (see there).
5297
+ draggable: eligibility === "force-allow" || !nodeId.startsWith("runtime:") && (identityMeta2?.origin !== "kit" || resolveRowBinding(el) !== null),
5298
+ sourceMetaNodeId: identityMeta2?.nodeId ?? null,
5299
+ resolvedHostNodeId: nodeId,
5300
+ resolvedHostSelector: selector,
5301
+ resolutionKind: ownerMeta && ownerMeta.componentName[0] !== ownerMeta.componentName[0].toLowerCase() ? "wrapper-to-host" : "direct-host",
5302
+ label: getLabel(el),
5303
+ rect,
5304
+ containerStrategy: parentEl && isFlexOrGridParent(parentEl) ? resolveContainerStrategy(parentEl) : null,
5305
+ eligibilitySource: eligibility ?? "force-allow",
5306
+ identityOrigin: identityMeta2?.origin ?? null,
5307
+ editorMeta: surfaceMeta
5308
+ };
5309
+ }
5250
5310
  function useElementScanner(enabled) {
5251
5311
  const [elements, setElements] = useState4([]);
5252
5312
  const rafRef = useRef4(0);
@@ -5263,30 +5323,10 @@ function useElementScanner(enabled) {
5263
5323
  const seenEls = new Set(scanned.map((s) => s.element));
5264
5324
  for (const el of Array.from(document.querySelectorAll("[data-dnd-moved]"))) {
5265
5325
  if (seenEls.has(el)) continue;
5266
- const rect = el.getBoundingClientRect();
5267
- if (rect.width === 0 || rect.height === 0) continue;
5268
- const nodeId = resolveStableNodeId(el, usedNodeIds);
5269
- usedNodeIds.add(nodeId);
5270
- el.setAttribute(DND_NODE_ID_ATTR, nodeId);
5271
- const parentEl = el.parentElement;
5272
- const surfaceMeta = getDirectEditorMeta(el) ?? void 0;
5273
- const ownerMeta = getEditorMeta(el);
5274
- const selector = getUniqueSelector(el);
5275
- scanned.push({
5276
- nodeId,
5277
- element: el,
5278
- selector,
5279
- draggable: !nodeId.startsWith("runtime:"),
5280
- sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5281
- resolvedHostNodeId: nodeId,
5282
- resolvedHostSelector: selector,
5283
- resolutionKind: ownerMeta && ownerMeta.componentName[0] !== ownerMeta.componentName[0].toLowerCase() ? "wrapper-to-host" : "direct-host",
5284
- label: getLabel(el),
5285
- rect,
5286
- containerStrategy: parentEl && isFlexOrGridParent(parentEl) ? resolveContainerStrategy(parentEl) : null,
5287
- eligibilitySource: isInspectorEligible(el) ?? "force-allow",
5288
- editorMeta: surfaceMeta
5289
- });
5326
+ const entry = buildMovedElementEntry(el, usedNodeIds);
5327
+ if (!entry) continue;
5328
+ usedNodeIds.add(entry.nodeId);
5329
+ scanned.push(entry);
5290
5330
  }
5291
5331
  const seen = /* @__PURE__ */ new Set();
5292
5332
  setElements(scanned.filter((el) => {
@@ -6661,6 +6701,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6661
6701
  const lastIntentRef = useRef8(null);
6662
6702
  const visualIntentRef = useRef8(null);
6663
6703
  const sourceContainerIdRef = useRef8(null);
6704
+ const rowLockedRef = useRef8(false);
6664
6705
  const dragSessionAnchorRef = useRef8(null);
6665
6706
  const lockRef = useRef8({ containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null });
6666
6707
  const targetPlaneRef = useRef8("lane");
@@ -6692,6 +6733,7 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6692
6733
  lastIntentRef,
6693
6734
  visualIntentRef,
6694
6735
  sourceContainerIdRef,
6736
+ rowLockedRef,
6695
6737
  dragSessionAnchorRef,
6696
6738
  lockRef,
6697
6739
  targetPlaneRef,
@@ -7597,6 +7639,12 @@ function buildPreviewLayout({
7597
7639
  var CONTAINER_SWITCH_HYSTERESIS = 12;
7598
7640
  var SMALL_NODE_NEST_DWELL_MS = 100;
7599
7641
  var LARGE_NODE_NEST_DWELL_MS = 320;
7642
+ function computeAllowNest(nestPreview, isLargeDraggedNode, rowLocked) {
7643
+ if (rowLocked) return false;
7644
+ if (!nestPreview) return false;
7645
+ const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7646
+ return !!childId && !isLargeDraggedNode;
7647
+ }
7600
7648
  function useDragVisualLoop({
7601
7649
  activeDrag,
7602
7650
  refs,
@@ -7646,11 +7694,7 @@ function useDragVisualLoop({
7646
7694
  const adjustedPy = py + scrollDy;
7647
7695
  const nestPreview = engine.peekNestIntent(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7648
7696
  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
- }
7697
+ const allowNest = computeAllowNest(nestPreview, isLargeDraggedNode, !!refs.rowLockedRef.current);
7654
7698
  if (!allowNest) {
7655
7699
  refs.nestCandidateRef.current = null;
7656
7700
  refs.nestTargetRef.current = null;
@@ -7835,6 +7879,18 @@ function useDragVisualLoop({
7835
7879
 
7836
7880
  // src/client/dnd/design-mode/useDragLifecycle.ts
7837
7881
  import { useState as useState5, useCallback as useCallback6 } from "react";
7882
+ function resolveLaneAllowedContainers(anchorElement, srcContainerId) {
7883
+ if (!srcContainerId) return null;
7884
+ if (!resolveRowBinding(anchorElement)) return null;
7885
+ return /* @__PURE__ */ new Set([srcContainerId]);
7886
+ }
7887
+ function buildDragIntentEngineOptions(anchorElement, srcContainerId, blockedContainerIds) {
7888
+ return {
7889
+ laneRootContainerId: srcContainerId,
7890
+ laneAllowedContainers: resolveLaneAllowedContainers(anchorElement, srcContainerId),
7891
+ blockedContainerIds
7892
+ };
7893
+ }
7838
7894
  function buildRuntimeIdentityLocal(nodeId, resolveNodeElement, effectiveLaneSnapshot) {
7839
7895
  if (!nodeId) return null;
7840
7896
  const element = resolveNodeElement(nodeId);
@@ -8034,6 +8090,7 @@ function useDragLifecycle({
8034
8090
  refs.lastIntentRef.current = null;
8035
8091
  refs.visualIntentRef.current = null;
8036
8092
  refs.sourceContainerIdRef.current = null;
8093
+ refs.rowLockedRef.current = false;
8037
8094
  refs.dragSessionAnchorRef.current = null;
8038
8095
  refs.lockRef.current = { containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8039
8096
  refs.targetPlaneRef.current = "lane";
@@ -8089,10 +8146,9 @@ function useDragLifecycle({
8089
8146
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8090
8147
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8091
8148
  refs.blockedContainerIdsRef.current = blockedContainerIds;
8092
- refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, {
8093
- laneRootContainerId: srcContainerId,
8094
- blockedContainerIds
8095
- });
8149
+ const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8150
+ refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8151
+ refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
8096
8152
  refs.engineRef.current.setTargetPlane("lane");
8097
8153
  refs.targetPlaneRef.current = "lane";
8098
8154
  refs.sourceContainerIdRef.current = srcContainerId;
@@ -8475,7 +8531,9 @@ function useExternalDragLoop({
8475
8531
  externalDrag,
8476
8532
  refs,
8477
8533
  resolveNodeElement,
8478
- setDropLine
8534
+ setDropLine,
8535
+ scrollX,
8536
+ scrollY
8479
8537
  );
8480
8538
  return;
8481
8539
  }
@@ -8508,10 +8566,8 @@ function useExternalDragLoop({
8508
8566
  return () => cancelAnimationFrame(rafId);
8509
8567
  }, [externalDrag, elements, refs, resolveNodeElement, setDropLine]);
8510
8568
  }
8511
- function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, setDropLine) {
8512
- const scrollX = window.scrollX;
8513
- const scrollY = window.scrollY;
8514
- const intent = engine.resolve(x, y, scrollX, scrollY, scrollX, scrollY);
8569
+ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, setDropLine, startScrollX, startScrollY) {
8570
+ const intent = engine.resolve(x, y, window.scrollX, window.scrollY, startScrollX, startScrollY);
8515
8571
  if (!intent || intent.mode !== "reorder") {
8516
8572
  useDesignModeStore.getState().cancelExternalDrag();
8517
8573
  setDropLine(null);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  meaningfulChildren
3
- } from "./chunk-4VGQJ3PW.mjs";
3
+ } from "./chunk-CO6SWEYJ.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
@@ -11,6 +11,7 @@ import { types, PluginObj } from '@babel/core';
11
11
  * The injected prop contains:
12
12
  * nodeId, file, line, col, componentName, staticProps, movable, parentNodeId
13
13
  * + (ARCH-883 F1) slot, propBindings, propOwner
14
+ * + (ARCH-883-KOI) origin
14
15
  *
15
16
  * At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
16
17
  * element identity without any DOM attributes.
@@ -30,6 +31,10 @@ import { types, PluginObj } from '@babel/core';
30
31
  * 3. `movable: false` for kit-origin nodes — reordering inside a kit template
31
32
  * affects N usages, a permanent non-objective (PRD §1.3).
32
33
  *
34
+ * ARCH-883-KOI (kit-origin-identity, CREW round) adds a FOURTH: `origin: 'kit'`
35
+ * on every kit-origin node (see `types/editor-meta.ts`'s docstring on that
36
+ * field for why it exists separately from `movable`).
37
+ *
33
38
  * `parentNodeId` stays INTRA-FILE by construction (it only ever walks JSX
34
39
  * ancestors within the file being transformed), so it never links a kit template
35
40
  * node to a screen usage; per SDD §2.3 it must not be used across that seam.
@@ -174,6 +179,52 @@ interface EditorMeta {
174
179
  * DOM. The root node of a kit template emits `null` here.
175
180
  */
176
181
  parentNodeId: string | null;
182
+ /**
183
+ * `'kit'` when the node comes from a kit template — emitted with
184
+ * `origin: 'kit'`, or carrying a slot (which makes it kit-origin by
185
+ * definition). Absent means screen-origin.
186
+ *
187
+ * WHY THIS EXISTS SEPARATELY FROM `movable`. Both are `false`/`'kit'` for a
188
+ * kit node, so it looks redundant, and it is not: `movable` is ALSO `false`
189
+ * for map-callback roots, fragment children and expression-container siblings
190
+ * in ordinary SCREENS (RFC E2.4 §2.7). A consumer that wants "may this node
191
+ * be reordered at all, by any write path" must not read `movable` — it would
192
+ * over-block exactly the list instances that reorder through their backing
193
+ * data. `origin` answers the narrower, structural question: this node is one
194
+ * template behind N usages, so no write path can ever reorder it (PRD §1.3).
195
+ *
196
+ * ABSENCE IS AMBIGUOUS, NOT SAFE, on today's fleet — this is NOT the same
197
+ * claim as "reads the same as screen-origin, the pre-existing meaning of
198
+ * every meta" (an earlier version of this doc said exactly that, and it is
199
+ * false for the current deployed fleet; kept here as the record of the
200
+ * CREW correction, ARCH-883-KOI F4). Two axes move independently:
201
+ *
202
+ * 1. Whether kit COMPONENTS render instrumented (kit-runtime's late-
203
+ * binding fix — a separate repo/change from this one).
204
+ * 2. Whether the `@archie/devtools` version that built a given bundle
205
+ * emits `origin` at all (this field).
206
+ *
207
+ * A page can be on axis-1-yes / axis-2-no TODAY: kit-runtime already
208
+ * instruments kit components on pages whose screen (or kit) build used a
209
+ * pre-`origin` `@archie/devtools`. On such a page a kit-internal host's
210
+ * template meta is present (per the P0-1 thunk) but carries no `origin`
211
+ * key — indistinguishable from a genuine screen-origin meta by this field
212
+ * alone. A consumer that trusts absence-means-screen-origin on that page
213
+ * reproduces the exact F1 bug this change fixes, silently.
214
+ *
215
+ * There is no per-node way to close this gap (a missing field cannot prove
216
+ * its own absence is meaningful). The mitigation is PAGE-LEVEL: the
217
+ * standalone inspector script's `INSPECTOR_SCRIPT_LOADED` handshake reports
218
+ * a `kitOriginIdentity` capability bit (see `inspector.standalone.js`) that
219
+ * tells the host "this SCRIPT understands `origin` and projects across the
220
+ * kit boundary" — it does NOT and cannot tell the host whether the PAGE's
221
+ * own plugin build emitted the field, which is the acg/kitgen build's
222
+ * `@archie/devtools` version, an axis this package has no visibility into.
223
+ * Hosts that need a hard guarantee must track that build version
224
+ * themselves; this capability bit only rules out "the runtime code is too
225
+ * old to know what `origin` means" as a failure mode.
226
+ */
227
+ origin?: 'kit';
177
228
  /**
178
229
  * Curated kit slot label, e.g. `"AuthFrame/aside/headline"` (SDD §2.6).
179
230
  *
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ import { types, PluginObj } from '@babel/core';
11
11
  * The injected prop contains:
12
12
  * nodeId, file, line, col, componentName, staticProps, movable, parentNodeId
13
13
  * + (ARCH-883 F1) slot, propBindings, propOwner
14
+ * + (ARCH-883-KOI) origin
14
15
  *
15
16
  * At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
16
17
  * element identity without any DOM attributes.
@@ -30,6 +31,10 @@ import { types, PluginObj } from '@babel/core';
30
31
  * 3. `movable: false` for kit-origin nodes — reordering inside a kit template
31
32
  * affects N usages, a permanent non-objective (PRD §1.3).
32
33
  *
34
+ * ARCH-883-KOI (kit-origin-identity, CREW round) adds a FOURTH: `origin: 'kit'`
35
+ * on every kit-origin node (see `types/editor-meta.ts`'s docstring on that
36
+ * field for why it exists separately from `movable`).
37
+ *
33
38
  * `parentNodeId` stays INTRA-FILE by construction (it only ever walks JSX
34
39
  * ancestors within the file being transformed), so it never links a kit template
35
40
  * node to a screen usage; per SDD §2.3 it must not be used across that seam.
@@ -174,6 +179,52 @@ interface EditorMeta {
174
179
  * DOM. The root node of a kit template emits `null` here.
175
180
  */
176
181
  parentNodeId: string | null;
182
+ /**
183
+ * `'kit'` when the node comes from a kit template — emitted with
184
+ * `origin: 'kit'`, or carrying a slot (which makes it kit-origin by
185
+ * definition). Absent means screen-origin.
186
+ *
187
+ * WHY THIS EXISTS SEPARATELY FROM `movable`. Both are `false`/`'kit'` for a
188
+ * kit node, so it looks redundant, and it is not: `movable` is ALSO `false`
189
+ * for map-callback roots, fragment children and expression-container siblings
190
+ * in ordinary SCREENS (RFC E2.4 §2.7). A consumer that wants "may this node
191
+ * be reordered at all, by any write path" must not read `movable` — it would
192
+ * over-block exactly the list instances that reorder through their backing
193
+ * data. `origin` answers the narrower, structural question: this node is one
194
+ * template behind N usages, so no write path can ever reorder it (PRD §1.3).
195
+ *
196
+ * ABSENCE IS AMBIGUOUS, NOT SAFE, on today's fleet — this is NOT the same
197
+ * claim as "reads the same as screen-origin, the pre-existing meaning of
198
+ * every meta" (an earlier version of this doc said exactly that, and it is
199
+ * false for the current deployed fleet; kept here as the record of the
200
+ * CREW correction, ARCH-883-KOI F4). Two axes move independently:
201
+ *
202
+ * 1. Whether kit COMPONENTS render instrumented (kit-runtime's late-
203
+ * binding fix — a separate repo/change from this one).
204
+ * 2. Whether the `@archie/devtools` version that built a given bundle
205
+ * emits `origin` at all (this field).
206
+ *
207
+ * A page can be on axis-1-yes / axis-2-no TODAY: kit-runtime already
208
+ * instruments kit components on pages whose screen (or kit) build used a
209
+ * pre-`origin` `@archie/devtools`. On such a page a kit-internal host's
210
+ * template meta is present (per the P0-1 thunk) but carries no `origin`
211
+ * key — indistinguishable from a genuine screen-origin meta by this field
212
+ * alone. A consumer that trusts absence-means-screen-origin on that page
213
+ * reproduces the exact F1 bug this change fixes, silently.
214
+ *
215
+ * There is no per-node way to close this gap (a missing field cannot prove
216
+ * its own absence is meaningful). The mitigation is PAGE-LEVEL: the
217
+ * standalone inspector script's `INSPECTOR_SCRIPT_LOADED` handshake reports
218
+ * a `kitOriginIdentity` capability bit (see `inspector.standalone.js`) that
219
+ * tells the host "this SCRIPT understands `origin` and projects across the
220
+ * kit boundary" — it does NOT and cannot tell the host whether the PAGE's
221
+ * own plugin build emitted the field, which is the acg/kitgen build's
222
+ * `@archie/devtools` version, an axis this package has no visibility into.
223
+ * Hosts that need a hard guarantee must track that build version
224
+ * themselves; this capability bit only rules out "the runtime code is too
225
+ * old to know what `origin` means" as a failure mode.
226
+ */
227
+ origin?: 'kit';
177
228
  /**
178
229
  * Curated kit slot label, e.g. `"AuthFrame/aside/headline"` (SDD §2.6).
179
230
  *
package/dist/index.js CHANGED
@@ -733,6 +733,11 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
733
733
  parentNodeId ? t.stringLiteral(parentNodeId) : t.nullLiteral()
734
734
  )
735
735
  ]);
736
+ if (isKitOrigin) {
737
+ metaObj.properties.push(
738
+ t.objectProperty(t.identifier("origin"), t.stringLiteral("kit"))
739
+ );
740
+ }
736
741
  if (slot !== null) {
737
742
  metaObj.properties.push(
738
743
  t.objectProperty(t.identifier("slot"), t.stringLiteral(slot))
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __dirname,
3
3
  babelPluginEditorMeta
4
- } from "./chunk-4VGQJ3PW.mjs";
4
+ } from "./chunk-CO6SWEYJ.mjs";
5
5
 
6
6
  // src/index.ts
7
7
  import { readFileSync } from "fs";
@@ -1,10 +1,4 @@
1
- /**
2
- * Archie Inspector - Standalone script for CDN injection.
3
- * Set before loading: window.__ARCHIE_INSPECTOR_TARGET_ORIGIN__, window.__ARCHIE_INSPECTOR_ALLOWED_ORIGINS__
4
- * Then load: <script src="https://your-cdn/inspector.standalone.js"></script>
5
- * Auto-runs if configured; or call window.__ARCHIE_INSPECTOR_RUN__().
6
- * Security: No eval(), no "*" origins; style values sanitized.
7
- */
1
+ /** Archie Inspector standalone: safe DOM inspector for CDN use. */
8
2
  ;(function () {
9
3
  "use strict"
10
4
  // Capture originals at module load time so the patch below is never recursive
@@ -95,19 +89,10 @@
95
89
  var FIBER_KEY_PREFIX = "__reactFiber$"
96
90
  var PROPS_KEY_PREFIX = "__reactProps$"
97
91
 
98
- // ARCH-883 P0-1: babel-plugin-editor-meta.ts (TypeScript context) now
99
- // emits __editorMeta as a zero-arg function, not the object literal
100
- // directly, so React never writes it into the DOM. Every extraction
101
- // site below must invoke the value when it is a function. Mirrors
102
- // readEditorMetaValue in fiber-bridge.ts — if this changes, update
103
- // that copy to maintain parity.
92
+ // __editorMeta is emitted as a thunk; invoke it (parity with fiber-bridge).
104
93
  function resolveEditorMetaValue(value) {
105
94
  if (typeof value === "function") {
106
- // Each call re-evaluates the plugin-emitted `() => ({...})`
107
- // closure and returns a FRESH object literal — never rely on
108
- // meta object identity (`===`) across two separate reads, even
109
- // within the same render (mirrors readEditorMetaValue in
110
- // fiber-bridge.ts).
95
+ // Invoke thunk and return a fresh meta object (do not rely on identity).
111
96
  return value()
112
97
  }
113
98
  // Twin parity with fiber-bridge.ts's readEditorMetaValue: only
@@ -338,19 +323,56 @@
338
323
  return hosts.length === 1 ? hosts[0] : null
339
324
  }
340
325
 
341
- // Vanilla twin of projectUsageMeta (useElementScanner.ts): the usage meta
342
- // when `el` is the SINGLE host root a component usage renders and that
343
- // component destructured its props, so the host fiber carries no meta.
344
- function projectUsageMetaFromElement(el) {
345
- if (getDirectEditorMetaFromElement(el)) return null
346
- var owner = getEditorMetaOwnerFiberFromElement(el)
326
+ // The fiber owning the nearest NON-kit __editorMeta for `el` — the usage
327
+ // written in a screen, reached across however many kit template frames
328
+ // sit in between. Vanilla twin of getUsageMetaOwnerFiber
329
+ // (fiber-bridge.ts); see that docstring for why the nearest meta is the
330
+ // wrong one once the kit itself is instrumented.
331
+ function getUsageMetaOwnerFiberFromElement(el) {
332
+ var fiber = getFiberFromElement(el)
333
+ while (fiber) {
334
+ if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
335
+ var meta = resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
336
+ if (meta && meta.origin !== "kit") return fiber
337
+ }
338
+ fiber = fiber["return"]
339
+ }
340
+ return null
341
+ }
342
+
343
+ // The meta `owner` carries, but only if `el` is the single host it renders.
344
+ function projectFromOwnerFiber(owner, el) {
347
345
  if (!owner) return null
348
346
  if (getSingleHostElementFromFiber(owner) !== el) return null
349
347
  return resolveEditorMetaValue(owner.memoizedProps.__editorMeta)
350
348
  }
351
349
 
352
- // The nodeId this element carries AS ITS OWN — direct fiber meta, or the
353
- // single-host projection. NEVER an inherited ancestor nodeId: walking the
350
+ // Vanilla twin of projectUsageMeta (fiber-bridge.ts): the usage meta when
351
+ // `el` is the SINGLE host root a component usage renders and that usage's
352
+ // meta never reached the host fiber — because the component destructured
353
+ // its props, or because the plugin sanitized it out of the spread (which
354
+ // is every kit component, once the kit is instrumented).
355
+ function projectUsageMetaFromElement(el) {
356
+ var direct = getDirectEditorMetaFromElement(el)
357
+ if (direct && direct.origin !== "kit") return null
358
+
359
+ var usage = projectFromOwnerFiber(
360
+ getUsageMetaOwnerFiberFromElement(el),
361
+ el
362
+ )
363
+ if (usage) return usage
364
+
365
+ // A kit-internal host already has its own identity; nothing to project.
366
+ if (direct) return null
367
+
368
+ return projectFromOwnerFiber(
369
+ getEditorMetaOwnerFiberFromElement(el),
370
+ el
371
+ )
372
+ }
373
+
374
+ // The nodeId this element carries AS ITS OWN — the single-host projection,
375
+ // then direct fiber meta. NEVER an inherited ancestor nodeId: walking the
354
376
  // fiber chain here made every UNSTAMPED subtree (any two siblings
355
377
  // rendered by a component the plugin never transformed — anything from
356
378
  // node_modules, and here SVG internals too, since this twin accepts any
@@ -358,13 +380,39 @@
358
380
  // direct-meta-ONLY rule would make every kit component that destructures
359
381
  // its props look like a singleton — which is the fail-OPEN mistake, and
360
382
  // would write a literal into a mapped usage, changing every row.
361
- // See instance-identity.ts for the full rationale.
383
+ // The projection is checked FIRST because an instrumented kit gives every
384
+ // <Card/> host the same template nodeId; two unrelated Cards under one
385
+ // parent would otherwise read as one repeated source position.
386
+ //
387
+ // ARCH-883-KOI F1 (CREW round): a direct KIT meta is the TEMPLATE's
388
+ // position, shared by every usage. When a screen USAGE exists above (the
389
+ // projection bailed only because `el` is a template-internal node BELOW
390
+ // the usage's single host root — a <Card/>'s inner <span>, not the outer
391
+ // <div> the projection already caught) the id CANNOT be the bare usage
392
+ // nodeId: every other template-internal node under that SAME usage (the
393
+ // sibling <p>) would walk to the identical usage fiber and report the
394
+ // identical id, collapsing two distinct template positions into one and
395
+ // making `findCollectionWindow` read them as a false repeated collection
396
+ // with no real key — refusing a legitimate kit-internal edit. The id is
397
+ // instead a COMPOSITE of the usage's nodeId and this node's OWN template
398
+ // position: stable across N repeats of the SAME position (the
399
+ // DataTable-row case — same usage, same template row id, correctly still
400
+ // a collection) while distinct across different positions under one
401
+ // usage (span vs p). Mirrors ownNodeId in instance-identity.ts — if this
402
+ // changes, update that copy to maintain parity.
362
403
  function ownMetaNodeId(el) {
363
404
  if (!el || el.nodeType !== 1) return null
364
- var direct = getDirectEditorMetaFromElement(el)
365
- if (direct && direct.nodeId) return direct.nodeId
366
405
  var projected = projectUsageMetaFromElement(el)
367
- return projected && projected.nodeId ? projected.nodeId : null
406
+ if (projected && projected.nodeId) return projected.nodeId
407
+ var direct = getDirectEditorMetaFromElement(el)
408
+ if (direct && direct.origin === "kit") {
409
+ var usageOwner = getUsageMetaOwnerFiberFromElement(el)
410
+ var usage =
411
+ usageOwner &&
412
+ resolveEditorMetaValue(usageOwner.memoizedProps.__editorMeta)
413
+ if (usage && usage.nodeId) return usage.nodeId + " " + direct.nodeId
414
+ }
415
+ return direct && direct.nodeId ? direct.nodeId : null
368
416
  }
369
417
 
370
418
  // The repeated-row boundary above `el`, or a search outcome explaining
@@ -786,6 +834,17 @@
786
834
  function getExactSourceSignatureFromElement(element) {
787
835
  if (!element) return ""
788
836
 
837
+ // The projected USAGE outranks the element's own meta. On an
838
+ // instrumented kit the host of a `<Card/>` carries the kit TEMPLATE's
839
+ // meta, so reading the direct one first signs the selection as
840
+ // `card.jsx` — a file the per-screen save path cannot write, which
841
+ // surfaces to the user as "no editable source for these changes".
842
+ var usageMeta = projectUsageMetaFromElement(element)
843
+ var usageSignature = usageMeta
844
+ ? getSharedSignatureFromMeta(usageMeta)
845
+ : ""
846
+ if (usageSignature) return usageSignature
847
+
789
848
  var directMeta = getDirectEditorMetaFromElement(element)
790
849
  var directSignature = directMeta
791
850
  ? getSharedSignatureFromMeta(directMeta)
@@ -1099,6 +1158,15 @@
1099
1158
  function getInspectorInfoFromElement(element) {
1100
1159
  if (!element) return null
1101
1160
 
1161
+ // Same precedence as getExactSourceSignatureFromElement: the usage the
1162
+ // screen wrote outranks the kit template that rendered the host. This
1163
+ // is the value that becomes `inspectorSource` in ELEMENT_SELECTED, so
1164
+ // getting it wrong is what makes the host app refuse the save.
1165
+ var usageMeta = projectUsageMetaFromElement(element)
1166
+ if (usageMeta) {
1167
+ return buildInspectorInfoFromMeta(usageMeta)
1168
+ }
1169
+
1102
1170
  var directMeta = getDirectEditorMetaFromElement(element)
1103
1171
  if (directMeta) {
1104
1172
  return buildInspectorInfoFromMeta(directMeta)
@@ -1350,7 +1418,11 @@
1350
1418
  // 2.3.0 (ARCH-1065 A17-support): additive only — `QUERY_COLLECTION_ORDER`
1351
1419
  // in the message protocol and `capabilities.collectionOrderQuery`
1352
1420
  // below. No existing field changed shape, so `protocolVersion` stays 1.
1353
- version: "2.3.0",
1421
+ // 2.4.0 (ARCH-883-KOI, CREW round): additive only — `origin` on
1422
+ // `EditorMeta`, the usage-projected `ownMetaNodeId`/`inspectorSource`/
1423
+ // `editorInstance` resolution, and `capabilities.kitOriginIdentity`
1424
+ // below. No existing field changed shape, so `protocolVersion` stays 1.
1425
+ version: "2.4.0",
1354
1426
  protocolVersion: 1,
1355
1427
  capabilities: {
1356
1428
  // 2.1.1: function-valued __editorMeta props are invoked before any
@@ -1391,6 +1463,24 @@
1391
1463
  // concept, singular mechanism — are treated as opaque strings
1392
1464
  // elsewhere in this file).
1393
1465
  bindingContract: readBindingContractMeta(),
1466
+ // ARCH-883-KOI F4 (v2.4.0) — this SCRIPT resolves `EditorMeta.origin`
1467
+ // and projects identity across an instrumented kit boundary
1468
+ // (`ownMetaNodeId`, `projectUsageMetaFromElement`,
1469
+ // `getInspectorInfoFromElement`, `resolveInstanceIdentity`). Hosts
1470
+ // MUST feature-detect on this flag before trusting `origin`
1471
+ // (absent or `editorMeta.origin === 'kit'`) as meaningful: a frozen
1472
+ // pre-2.4.0 inspector never reads the field at all, so its
1473
+ // `inspectorSource`/`editorInstance` on an instrumented-kit page
1474
+ // reproduce the exact F1 bug this version fixes.
1475
+ //
1476
+ // What this bit CANNOT tell the host: whether the PAGE's own build
1477
+ // emitted `origin` on its metas in the first place — that is the
1478
+ // `@archie/devtools` version the acg/kitgen esbuild path had
1479
+ // installed when the screen (or the kit) was built, a completely
1480
+ // separate axis from this CDN script's version (see the `origin`
1481
+ // field's docstring in `types/editor-meta.ts` for the full
1482
+ // reasoning, and why NO per-node signal can close that gap).
1483
+ kitOriginIdentity: true,
1394
1484
  },
1395
1485
  })
1396
1486
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
5
  "repository": {
6
6
  "type": "git",