@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/dist/index.js CHANGED
@@ -205,6 +205,7 @@ function babelPluginDndAuto({ types: t }, opts = {}) {
205
205
  }
206
206
 
207
207
  // src/babel-plugin-editor-meta.ts
208
+ var import_editor_protocol = require("@8base-archie/editor-protocol");
208
209
  var nodePath = __toESM(require("path"));
209
210
  function meaningfulChildren(t, children) {
210
211
  return children.filter((child) => {
@@ -260,7 +261,8 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
260
261
  instrumentImported = true,
261
262
  includeHostSourceRef = false,
262
263
  origin = "screen",
263
- templateOrigin = false
264
+ templateOrigin = false,
265
+ allowLegacyIds = true
264
266
  } = opts;
265
267
  function getJSXTagName(el) {
266
268
  const { name } = el;
@@ -308,6 +310,77 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
308
310
  function computeNodeId(file, line, col, componentName) {
309
311
  return `${file}:${line}:${col}:${componentName}`;
310
312
  }
313
+ function readVidAttr(attrs) {
314
+ for (const attr of attrs) {
315
+ if (!t.isJSXAttribute(attr)) continue;
316
+ if (!t.isJSXIdentifier(attr.name, { name: import_editor_protocol.VID_ATTR })) continue;
317
+ const { value } = attr;
318
+ let raw = null;
319
+ if (t.isStringLiteral(value)) {
320
+ raw = value.value;
321
+ } else if (t.isJSXExpressionContainer(value) && t.isStringLiteral(value.expression)) {
322
+ raw = value.expression.value;
323
+ }
324
+ if (raw !== null && import_editor_protocol.VID_PATTERN.test(raw)) return raw;
325
+ return null;
326
+ }
327
+ return null;
328
+ }
329
+ function computeDynamicType(path) {
330
+ let current = path.parentPath;
331
+ while (current) {
332
+ if (current.isJSXExpressionContainer()) {
333
+ const expr = current.node.expression;
334
+ if (t.isConditionalExpression(expr) || t.isLogicalExpression(expr)) {
335
+ return "CONDITIONAL";
336
+ }
337
+ }
338
+ if (current.isArrowFunctionExpression() || current.isFunctionExpression()) {
339
+ const call = current.parentPath;
340
+ if (call?.isCallExpression()) {
341
+ const callee = call.node.callee;
342
+ if (t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property) && /^(map|flatMap|forEach)$/.test(callee.property.name)) {
343
+ return "ARRAY";
344
+ }
345
+ }
346
+ break;
347
+ }
348
+ if (current.isFunctionDeclaration()) break;
349
+ current = current.parentPath;
350
+ }
351
+ return "STATIC";
352
+ }
353
+ const SHELL_FILE_RE = /(^|\/)shell\.jsx$/;
354
+ const SHELL_DIR_RE = /(^|\/)components\/shell\//;
355
+ const CHROME_ANCESTOR_TAGS = /* @__PURE__ */ new Set(["nav", "aside", "header"]);
356
+ function computeIsChrome(file, path) {
357
+ if (SHELL_FILE_RE.test(file) || SHELL_DIR_RE.test(file)) return true;
358
+ let current = path.parentPath;
359
+ while (current) {
360
+ if (current.isJSXElement()) {
361
+ const tag = getJSXTagName(current.node.openingElement);
362
+ if (CHROME_ANCESTOR_TAGS.has(tag)) return true;
363
+ }
364
+ current = current.parentPath;
365
+ }
366
+ return false;
367
+ }
368
+ function computeContainerKind(path) {
369
+ const elPath = path.parentPath;
370
+ if (!elPath?.isJSXElement()) return "none";
371
+ const children = meaningfulChildren(t, elPath.node.children);
372
+ if (children.length === 0) return "none";
373
+ const allPlain = children.every((c) => t.isJSXElement(c) || t.isJSXText(c));
374
+ return allPlain ? "plain" : "expression";
375
+ }
376
+ function computeLockReason(isKitOrigin, isChrome, dynamicType, containerKind) {
377
+ if (isKitOrigin) return "KIT_INTERNAL";
378
+ if (isChrome) return "CHROME_STRUCTURAL";
379
+ if (dynamicType === "ARRAY") return "DYNAMIC_ARRAY";
380
+ if (dynamicType === "CONDITIONAL") return "DYNAMIC_CONDITIONAL";
381
+ if (containerKind === "expression") return "EXPRESSION_CONTAINER";
382
+ return void 0;
383
+ }
311
384
  function isMovable(path) {
312
385
  const elPath = path.parentPath;
313
386
  if (!elPath) return false;
@@ -325,6 +398,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
325
398
  while (ancestor) {
326
399
  if (ancestor.isJSXElement()) {
327
400
  const opening = ancestor.node.openingElement;
401
+ const parentVid = readVidAttr(opening.attributes);
402
+ if (parentVid) return parentVid;
403
+ if (!allowLegacyIds) return null;
328
404
  const loc = opening.loc?.start;
329
405
  if (loc) {
330
406
  const tag = getJSXTagName(opening);
@@ -951,7 +1027,9 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
951
1027
  return;
952
1028
  }
953
1029
  }
954
- const nodeId = computeNodeId(file, line, col, componentName);
1030
+ const vid = readVidAttr(path.node.attributes);
1031
+ if (!vid && !allowLegacyIds) return;
1032
+ const nodeId = vid ?? computeNodeId(file, line, col, componentName);
955
1033
  const isKitOrigin = origin === "kit" || hasSlotAttr;
956
1034
  const movable = isKitOrigin ? false : isMovable(path);
957
1035
  const parentNodeId = getParentNodeId(path, file);
@@ -973,6 +1051,24 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
973
1051
  parentNodeId ? t.stringLiteral(parentNodeId) : t.nullLiteral()
974
1052
  )
975
1053
  ]);
1054
+ if (vid) {
1055
+ const dynamicType = computeDynamicType(path);
1056
+ const isChrome = computeIsChrome(file, path);
1057
+ const containerKind = computeContainerKind(path);
1058
+ const lockReason = computeLockReason(isKitOrigin, isChrome, dynamicType, containerKind);
1059
+ metaObj.properties.push(
1060
+ t.objectProperty(t.identifier("vid"), t.stringLiteral(vid)),
1061
+ t.objectProperty(t.identifier("isKitOrigin"), t.booleanLiteral(isKitOrigin)),
1062
+ t.objectProperty(t.identifier("dynamicType"), t.stringLiteral(dynamicType)),
1063
+ t.objectProperty(t.identifier("isChrome"), t.booleanLiteral(isChrome)),
1064
+ t.objectProperty(t.identifier("containerKind"), t.stringLiteral(containerKind))
1065
+ );
1066
+ if (lockReason) {
1067
+ metaObj.properties.push(
1068
+ t.objectProperty(t.identifier("lockReason"), t.stringLiteral(lockReason))
1069
+ );
1070
+ }
1071
+ }
976
1072
  if (isKitOrigin) {
977
1073
  if (templateOrigin) {
978
1074
  metaObj.properties.push(
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __dirname,
3
3
  babelPluginEditorMeta
4
- } from "./chunk-P5XMHBFS.mjs";
4
+ } from "./chunk-THKKWDMJ.mjs";
5
5
 
6
6
  // src/index.ts
7
7
  import { readFileSync } from "fs";
@@ -1725,10 +1725,96 @@
1725
1725
  // asking blindly (and treating silence as "still unknown", never
1726
1726
  // as "reported empty") is the correct posture.
1727
1727
  capabilitiesQuery: true,
1728
+ // vid-identity migration (protocol 1.x, `@8base-archie/editor-protocol`):
1729
+ // this script reads `data-vid`/`meta.vid` as the node's identity
1730
+ // (`resolveNodeVid` below) and assembles a `NodeMeta` object for
1731
+ // `ELEMENT_SELECTED`, and it emits `REFUSED` instead of silently
1732
+ // accepting a drag/drop this package's own policy would reject.
1733
+ // Hosts MUST feature-detect all three: a frozen pre-migration
1734
+ // inspector has none of them, and its `ELEMENT_SELECTED` has no
1735
+ // `vid`/`nodeMeta` fields at all.
1736
+ vidIdentity: true,
1737
+ nodeMeta: true,
1738
+ refusals: true,
1728
1739
  },
1729
1740
  }
1730
1741
  }
1731
1742
 
1743
+ // `@8base-archie/editor-protocol`'s `PROTOCOL_VERSION` (semver,
1744
+ // major-only compatibility). This script cannot `import` the shared
1745
+ // package (vanilla IIFE, CDN-served) — keep this literal in sync BY
1746
+ // HAND with that package's `src/version.ts` whenever the wire shape of
1747
+ // `RuntimeMessage`/`NodeMeta`/`Op` changes. Reported as a SEPARATE key
1748
+ // from the existing numeric `protocolVersion` above (never overwritten
1749
+ // — archie-app's `runtime-compatibility.ts` parses that field as a
1750
+ // number and compares it with strict equality; colliding the two would
1751
+ // silently break every existing compatibility check).
1752
+ var EDITOR_PROTOCOL_VERSION = "1.0.0"
1753
+
1754
+ // vid-identity migration: `/^[a-z0-9]{6,8}$/`, mirrors
1755
+ // `@8base-archie/editor-protocol`'s `VID_PATTERN` — kept in parity by
1756
+ // hand for the same reason as `EDITOR_PROTOCOL_VERSION` above.
1757
+ var VID_PATTERN = /^[a-z0-9]{6,8}$/
1758
+
1759
+ /**
1760
+ * This node's `vid` — the fiber-resolved (usage-projected) meta's
1761
+ * `vid` first, the DOM `data-vid` attribute next, or null. Mirrors
1762
+ * `node-key.ts`'s `getNodeKeyFromElement` precedence for the vid-only
1763
+ * portion of it (this script has no legacy-nodeId/runtime-key
1764
+ * fallback to offer here — callers that need a key for every element,
1765
+ * vid or not, keep using their own `inspectorSource` resolution).
1766
+ */
1767
+ function resolveNodeVid(el, meta) {
1768
+ if (meta && typeof meta.vid === "string" && VID_PATTERN.test(meta.vid)) {
1769
+ return meta.vid
1770
+ }
1771
+ if (el && typeof el.getAttribute === "function") {
1772
+ var domVid = el.getAttribute("data-vid")
1773
+ if (domVid && VID_PATTERN.test(domVid)) return domVid
1774
+ }
1775
+ return null
1776
+ }
1777
+
1778
+ /**
1779
+ * Assemble this node's `NodeMeta` (`@8base-archie/editor-protocol`)
1780
+ * from its `EditorMeta`, or null when there is no `vid` to key it by.
1781
+ * Mirrors `fiber-bridge.ts`'s `getNodeMeta` — kept in parity by hand,
1782
+ * same reason as `EDITOR_PROTOCOL_VERSION` above (this script cannot
1783
+ * import the TS module or run its `NodeMetaSchema.safeParse`).
1784
+ */
1785
+ function resolveNodeMeta(el, meta) {
1786
+ var vid = resolveNodeVid(el, meta)
1787
+ if (!vid) return null
1788
+ var nodeMeta = {
1789
+ vid: vid,
1790
+ file: (meta && meta.file) || "",
1791
+ dynamicType: (meta && meta.dynamicType) || "STATIC",
1792
+ isKitOrigin: !!(meta && (meta.isKitOrigin || isTemplateFrame(meta))),
1793
+ isChrome: !!(meta && meta.isChrome),
1794
+ containerKind: (meta && meta.containerKind) || "none",
1795
+ }
1796
+ if (meta && meta.componentName) nodeMeta.component = meta.componentName
1797
+ if (meta && meta.lockReason) nodeMeta.lockReason = meta.lockReason
1798
+ return nodeMeta
1799
+ }
1800
+
1801
+ /**
1802
+ * Emit `REFUSED` (vid-identity migration) instead of letting a
1803
+ * drag/drop the runtime's own policy would reject proceed silently —
1804
+ * the gap `docs/deploy-order.md`-adjacent decision this capability bit
1805
+ * documents: "today the drop is accepted and the host discards it
1806
+ * after the fact; that must stop."
1807
+ */
1808
+ function refuseNode(code, cause, vid) {
1809
+ postMessageToParent({
1810
+ source: "archie-devtools",
1811
+ type: "REFUSED",
1812
+ code: code,
1813
+ cause: cause,
1814
+ vid: vid || undefined,
1815
+ })
1816
+ }
1817
+
1732
1818
  /**
1733
1819
  * Emit the capabilities payload as `CAPABILITIES_RESULT`.
1734
1820
  *
@@ -1745,6 +1831,7 @@
1745
1831
  reason: typeof reason === "string" && reason !== "" ? reason : null,
1746
1832
  version: handshake.version,
1747
1833
  protocolVersion: handshake.protocolVersion,
1834
+ editorProtocolVersion: EDITOR_PROTOCOL_VERSION,
1748
1835
  capabilities: handshake.capabilities,
1749
1836
  })
1750
1837
  }
@@ -1755,6 +1842,7 @@
1755
1842
  timestamp: Date.now(),
1756
1843
  version: initialHandshake.version,
1757
1844
  protocolVersion: initialHandshake.protocolVersion,
1845
+ editorProtocolVersion: EDITOR_PROTOCOL_VERSION,
1758
1846
  capabilities: initialHandshake.capabilities,
1759
1847
  })
1760
1848
 
@@ -3681,6 +3769,12 @@
3681
3769
  // this script's version. The slot string is opaque here; validating it
3682
3770
  // against kit-runtime's `slot-vocabulary` is the host's job.
3683
3771
  editorMeta: editorMeta ? editorMeta.meta : null,
3772
+ // vid-identity migration — `vid`/`nodeMeta` derived from the SAME
3773
+ // meta as `editorMeta` above (`resolveNodeVid`/`resolveNodeMeta`,
3774
+ // mirroring `node-key.ts`/`fiber-bridge.ts`'s `getNodeMeta`). Both
3775
+ // are null for a legacy node with no `data-vid`.
3776
+ vid: resolveNodeVid(element, editorMeta ? editorMeta.meta : null),
3777
+ nodeMeta: resolveNodeMeta(element, editorMeta ? editorMeta.meta : null),
3684
3778
  // ARCH-883 F2 — the two things the host cannot derive from
3685
3779
  // `editorMeta` alone (SDD §2.9):
3686
3780
  //
@@ -4219,6 +4313,45 @@
4219
4313
  return
4220
4314
  }
4221
4315
 
4316
+ // `data-vid` identity migration — the DnD runtime's `drag-started`/
4317
+ // `drag-refused` events (`useDragLifecycle.ts`, `host-bridge.ts`)
4318
+ // otherwise die inside the iframe: nothing relayed them to the host.
4319
+ // Reuses `refuseNode` for `REFUSED` so the wire shape is the SAME
4320
+ // one produced by every other refusal path in this file (never a
4321
+ // second, drifting literal) and validates against
4322
+ // `RuntimeMessageSchema` (`@8base-archie/editor-protocol`).
4323
+ if (detail.type === "drag-refused") {
4324
+ refuseNode(detail.code, detail.cause, detail.vid)
4325
+ return
4326
+ }
4327
+
4328
+ if (detail.type === "drag-started") {
4329
+ // `DragStartMessageSchema.targetVid` is REQUIRED (not optional) —
4330
+ // unlike `REFUSED.vid`. A legacy (no `data-vid`) page's dragged
4331
+ // node has no vid to report, so the message cannot be made to
4332
+ // satisfy the schema; skip the relay rather than send a shape
4333
+ // that fails validation on the host. `droppable` is filtered to
4334
+ // well-formed vids for the same reason — the projection's own ids
4335
+ // can be legacy `file:line:col:componentName` strings, which the
4336
+ // schema's `z.array(Vid)` would reject.
4337
+ if (typeof detail.targetVid !== "string" || !VID_PATTERN.test(detail.targetVid)) {
4338
+ veTrace("runtime-event:drag-started:skipped-no-vid", {})
4339
+ return
4340
+ }
4341
+ var droppableVids = Array.isArray(detail.droppable)
4342
+ ? detail.droppable.filter(function (id) {
4343
+ return typeof id === "string" && VID_PATTERN.test(id)
4344
+ })
4345
+ : []
4346
+ postMessageToParent({
4347
+ source: "archie-devtools",
4348
+ type: "DRAG_START",
4349
+ targetVid: detail.targetVid,
4350
+ droppable: droppableVids,
4351
+ })
4352
+ return
4353
+ }
4354
+
4222
4355
  // ARCH-1367 F6 — attach the drop container's editor meta chain to a
4223
4356
  // container descriptor ({selector, nodeId, …}). Shared by the
4224
4357
  // `element-inserted` relay AND the `arrange-session-changed` relay:
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.48",
3
+ "version": "0.1.1",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
+ "sideEffects": false,
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/8base-archie/archie-devtools.git"
@@ -76,7 +77,9 @@
76
77
  "react-dom": ">=18.0.0"
77
78
  },
78
79
  "dependencies": {
80
+ "@8base-archie/editor-protocol": "1.0.0-rc.1",
79
81
  "@babel/core": "7.29.0",
80
- "@babel/types": "7.29.0"
82
+ "@babel/types": "7.29.0",
83
+ "zod": "^4.6.5"
81
84
  }
82
85
  }