@archie/devtools 0.0.42 → 0.0.44

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.
@@ -183,6 +183,146 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
183
183
  if (!t.isJSXText(only)) return "__dynamic__";
184
184
  return only.value.trim();
185
185
  }
186
+ function literalInitText(init) {
187
+ if (t.isStringLiteral(init)) return init.value;
188
+ if (t.isTemplateLiteral(init) && init.expressions.length === 0) {
189
+ const [quasi] = init.quasis;
190
+ if (!quasi) return null;
191
+ return quasi.value.cooked ?? quasi.value.raw;
192
+ }
193
+ return null;
194
+ }
195
+ function describeLocalConstSource(name, binding) {
196
+ const declarator = binding.path;
197
+ if (!declarator.isVariableDeclarator()) return null;
198
+ const declaration = declarator.parentPath;
199
+ if (!declaration?.isVariableDeclaration()) return null;
200
+ if (declaration.node.kind !== "const" && declaration.node.kind !== "let") return null;
201
+ const init = declarator.node.init;
202
+ if (!init) return null;
203
+ const literal = literalInitText(init);
204
+ if (literal === null) return null;
205
+ const loc = init.loc?.start;
206
+ if (!loc) return null;
207
+ return {
208
+ kind: "local-const",
209
+ name,
210
+ // The init lives in the SAME file being transformed by construction —
211
+ // `scope.getBinding` never crosses a file boundary — so the caller's
212
+ // own `file` (computed once per visited node) is correct here too.
213
+ file: currentFile,
214
+ line: loc.line,
215
+ col: loc.column + 1,
216
+ literal,
217
+ // FE MINOR-5: how many places in the FILE read this const — every
218
+ // reference, not only the one this call is describing. `refs > 1`
219
+ // means one initializer feeds more than one rendered element, so a
220
+ // consumer must warn/refuse rather than treat a rewrite as scoped to
221
+ // just this usage (see `PropSource`'s docstring for the full contract).
222
+ refs: binding.referencePaths.length
223
+ };
224
+ }
225
+ function resolvableSameFileBinding(name, scope) {
226
+ const binding = scope.getBinding(name);
227
+ if (!binding) return null;
228
+ if (!binding.constant) return null;
229
+ const bp = binding.path;
230
+ if (bp.isImportSpecifier() || bp.isImportDefaultSpecifier() || bp.isImportNamespaceSpecifier()) {
231
+ return null;
232
+ }
233
+ return binding;
234
+ }
235
+ function memberChain(expr) {
236
+ const path2 = [];
237
+ let current = expr;
238
+ while (t.isMemberExpression(current)) {
239
+ if (current.computed) return null;
240
+ if (!t.isIdentifier(current.property)) return null;
241
+ path2.unshift(current.property.name);
242
+ current = current.object;
243
+ }
244
+ if (!t.isIdentifier(current)) return null;
245
+ return { root: current.name, path: path2 };
246
+ }
247
+ let currentFile = "";
248
+ function describeSource(expr, scope) {
249
+ if (t.isIdentifier(expr)) {
250
+ const binding = resolvableSameFileBinding(expr.name, scope);
251
+ if (!binding) return { kind: "other" };
252
+ const local = describeLocalConstSource(expr.name, binding);
253
+ if (local) return local;
254
+ return { kind: "identifier", name: expr.name };
255
+ }
256
+ if (t.isMemberExpression(expr) && !expr.computed) {
257
+ const chain = memberChain(expr);
258
+ if (chain && resolvableSameFileBinding(chain.root, scope)) {
259
+ return { kind: "member", root: chain.root, path: chain.path };
260
+ }
261
+ }
262
+ return { kind: "other" };
263
+ }
264
+ function isStaticPropsLiteral(expr) {
265
+ return t.isStringLiteral(expr) || t.isNumericLiteral(expr) || t.isBooleanLiteral(expr) || t.isNullLiteral(expr);
266
+ }
267
+ function describeChildrenSource(opening) {
268
+ const elementPath = opening.parentPath;
269
+ if (!elementPath || !elementPath.isJSXElement()) return null;
270
+ const children = elementPath.node.children;
271
+ if (children.length !== 1) return null;
272
+ const [only] = children;
273
+ if (!t.isJSXExpressionContainer(only)) return null;
274
+ if (t.isJSXEmptyExpression(only.expression)) return null;
275
+ return describeSource(only.expression, opening.scope);
276
+ }
277
+ function collectPropSources(attrs, scope) {
278
+ const entries = [];
279
+ for (const attr of attrs) {
280
+ if (!t.isJSXAttribute(attr)) continue;
281
+ const name = t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
282
+ if (NON_BINDING_ATTRS.has(name)) continue;
283
+ const value = attr.value;
284
+ if (!t.isJSXExpressionContainer(value)) continue;
285
+ const expression = value.expression;
286
+ if (t.isJSXEmptyExpression(expression)) continue;
287
+ if (isStaticPropsLiteral(expression)) continue;
288
+ entries.push([name, describeSource(expression, scope)]);
289
+ }
290
+ return entries;
291
+ }
292
+ function propSourceToAst(source) {
293
+ switch (source.kind) {
294
+ case "identifier":
295
+ return t.objectExpression([
296
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("identifier")),
297
+ t.objectProperty(t.identifier("name"), t.stringLiteral(source.name))
298
+ ]);
299
+ case "local-const":
300
+ return t.objectExpression([
301
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("local-const")),
302
+ t.objectProperty(t.identifier("name"), t.stringLiteral(source.name)),
303
+ t.objectProperty(t.identifier("file"), t.stringLiteral(source.file)),
304
+ t.objectProperty(t.identifier("line"), t.numericLiteral(source.line)),
305
+ t.objectProperty(t.identifier("col"), t.numericLiteral(source.col)),
306
+ t.objectProperty(
307
+ t.identifier("literal"),
308
+ source.literal === null ? t.nullLiteral() : t.stringLiteral(source.literal)
309
+ ),
310
+ t.objectProperty(t.identifier("refs"), t.numericLiteral(source.refs))
311
+ ]);
312
+ case "member":
313
+ return t.objectExpression([
314
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("member")),
315
+ t.objectProperty(t.identifier("root"), t.stringLiteral(source.root)),
316
+ t.objectProperty(
317
+ t.identifier("path"),
318
+ t.arrayExpression(source.path.map((segment) => t.stringLiteral(segment)))
319
+ )
320
+ ]);
321
+ case "other":
322
+ default:
323
+ return t.objectExpression([t.objectProperty(t.identifier("kind"), t.stringLiteral("other"))]);
324
+ }
325
+ }
186
326
  function isMetaStripSpreadExpression(arg) {
187
327
  if (!t.isCallExpression(arg)) return false;
188
328
  if (!t.isArrowFunctionExpression(arg.callee)) return false;
@@ -574,6 +714,7 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
574
714
  if (!loc) return;
575
715
  const root = state.file.opts.root ?? "";
576
716
  const file = root ? nodePath.relative(root, filename) : filename;
717
+ currentFile = file;
577
718
  const line = loc.line;
578
719
  const col = loc.column + 1;
579
720
  const componentName = getJSXTagName(path2.node);
@@ -657,6 +798,12 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
657
798
  childrenText === null ? t.nullLiteral() : t.stringLiteral(childrenText)
658
799
  )
659
800
  );
801
+ const childrenSource = describeChildrenSource(path2);
802
+ if (childrenSource) {
803
+ metaObj.properties.push(
804
+ t.objectProperty(t.identifier("childrenSource"), propSourceToAst(childrenSource))
805
+ );
806
+ }
660
807
  }
661
808
  if (bindings.length > 0) {
662
809
  metaObj.properties.push(
@@ -669,6 +816,19 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
669
816
  )
670
817
  );
671
818
  }
819
+ const propSourceEntries = collectPropSources(path2.node.attributes, path2.scope);
820
+ if (propSourceEntries.length > 0) {
821
+ metaObj.properties.push(
822
+ t.objectProperty(
823
+ t.identifier("propSources"),
824
+ t.objectExpression(
825
+ propSourceEntries.map(
826
+ ([name, source]) => t.objectProperty(t.stringLiteral(name), propSourceToAst(source))
827
+ )
828
+ )
829
+ )
830
+ );
831
+ }
672
832
  if (isHostElement && includeHostSourceRef) {
673
833
  path2.node.attributes.push(
674
834
  t.jsxAttribute(
@@ -5259,8 +5259,10 @@ function ensureUniqueNodeId(base, used) {
5259
5259
  while (used.has(`${base}::${idx}`)) idx += 1;
5260
5260
  return `${base}::${idx}`;
5261
5261
  }
5262
+ var INSERT_NODE_ID_PREFIX = "insert:";
5262
5263
  function resolveStableNodeId(el, used) {
5263
5264
  const persisted = el.getAttribute(DND_NODE_ID_ATTR);
5265
+ if (persisted && persisted.startsWith(INSERT_NODE_ID_PREFIX)) return persisted;
5264
5266
  if (persisted && !used.has(persisted)) return persisted;
5265
5267
  const projected = projectUsageMeta(el);
5266
5268
  if (projected) return ensureUniqueNodeId(projected.nodeId, used);
@@ -5331,7 +5333,7 @@ function isFlexOrGridParent(el) {
5331
5333
  const d = getComputedStyle(el).display;
5332
5334
  return d === "flex" || d === "grid" || d === "inline-flex" || d === "inline-grid";
5333
5335
  }
5334
- function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(), seenResolvedHosts = /* @__PURE__ */ new WeakSet()) {
5336
+ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(), seenResolvedHosts = /* @__PURE__ */ new WeakSet(), resolvedUnreported = /* @__PURE__ */ new Set()) {
5335
5337
  if (depth > MAX_DEPTH) return [];
5336
5338
  const results = [];
5337
5339
  for (const child of Array.from(root.children)) {
@@ -5356,7 +5358,9 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5356
5358
  const nodeId2 = resolveStableNodeId(el, usedNodeIds);
5357
5359
  usedNodeIds.add(nodeId2);
5358
5360
  el.setAttribute(DND_NODE_ID_ATTR, nodeId2);
5359
- if (!isZeroRect) {
5361
+ if (isZeroRect) {
5362
+ resolvedUnreported.add(el);
5363
+ } else {
5360
5364
  seenResolvedHosts.add(el);
5361
5365
  const surfaceMeta2 = el instanceof HTMLElement ? getDirectEditorMeta(el) ?? void 0 : void 0;
5362
5366
  const identityMeta3 = projectUsageMeta(el) ?? surfaceMeta2;
@@ -5380,7 +5384,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5380
5384
  });
5381
5385
  }
5382
5386
  }
5383
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5387
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5384
5388
  continue;
5385
5389
  }
5386
5390
  if (isSvgRoot && isInspectorEligible(el) === null) {
@@ -5389,7 +5393,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5389
5393
  const eligibilitySource = isInspectorEligible(el);
5390
5394
  const inferredEligibility = eligibilitySource ?? inferEligibilityFromSortableParent(el) ?? (projectUsageMeta(el) ? "projected-usage" : null);
5391
5395
  if (inferredEligibility === null) {
5392
- const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts);
5396
+ const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported);
5393
5397
  results.push(...childResults);
5394
5398
  if (isFlexOrGridParent(el) && !seenResolvedHosts.has(el) && childResults.some((r) => r.draggable && r.element.parentElement === el)) {
5395
5399
  const nodeId2 = resolveStableNodeId(el, usedNodeIds);
@@ -5417,13 +5421,13 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5417
5421
  const dragSurface = resolveDragSurface(el);
5418
5422
  const hostEl = dragSurface.host;
5419
5423
  if (!hostEl) {
5420
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5424
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5421
5425
  continue;
5422
5426
  }
5423
5427
  const hostRect = hostEl.getBoundingClientRect();
5424
5428
  const nodeId = resolveStableNodeId(hostEl, usedNodeIds);
5425
5429
  if (seenResolvedHosts.has(hostEl)) {
5426
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5430
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5427
5431
  continue;
5428
5432
  }
5429
5433
  seenResolvedHosts.add(hostEl);
@@ -5453,9 +5457,9 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5453
5457
  identityOrigin: templateFrameLabel(identityMeta2),
5454
5458
  editorMeta: surfaceMeta
5455
5459
  });
5456
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5460
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5457
5461
  } else {
5458
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5462
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5459
5463
  }
5460
5464
  }
5461
5465
  return results;
@@ -5511,8 +5515,10 @@ function useElementScanner(enabled) {
5511
5515
  if (pausedRef.current) return;
5512
5516
  const root = document.getElementById("root") ?? document.body;
5513
5517
  const usedNodeIds = /* @__PURE__ */ new Set();
5514
- const scanned = scanElements(root, 0, usedNodeIds);
5518
+ const resolvedUnreported = /* @__PURE__ */ new Set();
5519
+ const scanned = scanElements(root, 0, usedNodeIds, void 0, resolvedUnreported);
5515
5520
  const seenEls = new Set(scanned.map((s) => s.element));
5521
+ for (const el of resolvedUnreported) seenEls.add(el);
5516
5522
  for (const el of Array.from(document.querySelectorAll("[data-dnd-moved]"))) {
5517
5523
  if (seenEls.has(el)) continue;
5518
5524
  const entry = buildMovedElementEntry(el, usedNodeIds);
@@ -5752,7 +5758,7 @@ function ElementHighlight({
5752
5758
  "data-outline-for": el.nodeId,
5753
5759
  "data-design-mode-ui": true,
5754
5760
  style: getOutlineStyle(rect, isSelected, inspectorTheme),
5755
- children: isSelected && HANDLES.map((dir) => {
5761
+ children: isSelected && el.draggable && HANDLES.map((dir) => {
5756
5762
  const pos = handlePosition(dir);
5757
5763
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5758
5764
  "div",
@@ -8519,7 +8525,9 @@ function useResizeSession({
8519
8525
  }) {
8520
8526
  const handleResizeHandlePointerDown = (0, import_react14.useCallback)((e, nodeId, dir) => {
8521
8527
  if (refs.resizeSessionRef.current) return;
8522
- const selectedEl = refs.nodeMapRef.current.get(nodeId)?.element ?? document.querySelector(`[data-dnd-node-id="${nodeId}"]`);
8528
+ const scanned = refs.nodeMapRef.current.get(nodeId);
8529
+ if (scanned && scanned.draggable === false) return;
8530
+ const selectedEl = scanned?.element ?? document.querySelector(`[data-dnd-node-id="${nodeId}"]`);
8523
8531
  if (!selectedEl) return;
8524
8532
  e.preventDefault();
8525
8533
  e.stopPropagation();
@@ -5224,8 +5224,10 @@ function ensureUniqueNodeId(base, used) {
5224
5224
  while (used.has(`${base}::${idx}`)) idx += 1;
5225
5225
  return `${base}::${idx}`;
5226
5226
  }
5227
+ var INSERT_NODE_ID_PREFIX = "insert:";
5227
5228
  function resolveStableNodeId(el, used) {
5228
5229
  const persisted = el.getAttribute(DND_NODE_ID_ATTR);
5230
+ if (persisted && persisted.startsWith(INSERT_NODE_ID_PREFIX)) return persisted;
5229
5231
  if (persisted && !used.has(persisted)) return persisted;
5230
5232
  const projected = projectUsageMeta(el);
5231
5233
  if (projected) return ensureUniqueNodeId(projected.nodeId, used);
@@ -5296,7 +5298,7 @@ function isFlexOrGridParent(el) {
5296
5298
  const d = getComputedStyle(el).display;
5297
5299
  return d === "flex" || d === "grid" || d === "inline-flex" || d === "inline-grid";
5298
5300
  }
5299
- function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(), seenResolvedHosts = /* @__PURE__ */ new WeakSet()) {
5301
+ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(), seenResolvedHosts = /* @__PURE__ */ new WeakSet(), resolvedUnreported = /* @__PURE__ */ new Set()) {
5300
5302
  if (depth > MAX_DEPTH) return [];
5301
5303
  const results = [];
5302
5304
  for (const child of Array.from(root.children)) {
@@ -5321,7 +5323,9 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5321
5323
  const nodeId2 = resolveStableNodeId(el, usedNodeIds);
5322
5324
  usedNodeIds.add(nodeId2);
5323
5325
  el.setAttribute(DND_NODE_ID_ATTR, nodeId2);
5324
- if (!isZeroRect) {
5326
+ if (isZeroRect) {
5327
+ resolvedUnreported.add(el);
5328
+ } else {
5325
5329
  seenResolvedHosts.add(el);
5326
5330
  const surfaceMeta2 = el instanceof HTMLElement ? getDirectEditorMeta(el) ?? void 0 : void 0;
5327
5331
  const identityMeta3 = projectUsageMeta(el) ?? surfaceMeta2;
@@ -5345,7 +5349,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5345
5349
  });
5346
5350
  }
5347
5351
  }
5348
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5352
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5349
5353
  continue;
5350
5354
  }
5351
5355
  if (isSvgRoot && isInspectorEligible(el) === null) {
@@ -5354,7 +5358,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5354
5358
  const eligibilitySource = isInspectorEligible(el);
5355
5359
  const inferredEligibility = eligibilitySource ?? inferEligibilityFromSortableParent(el) ?? (projectUsageMeta(el) ? "projected-usage" : null);
5356
5360
  if (inferredEligibility === null) {
5357
- const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts);
5361
+ const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported);
5358
5362
  results.push(...childResults);
5359
5363
  if (isFlexOrGridParent(el) && !seenResolvedHosts.has(el) && childResults.some((r) => r.draggable && r.element.parentElement === el)) {
5360
5364
  const nodeId2 = resolveStableNodeId(el, usedNodeIds);
@@ -5382,13 +5386,13 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5382
5386
  const dragSurface = resolveDragSurface(el);
5383
5387
  const hostEl = dragSurface.host;
5384
5388
  if (!hostEl) {
5385
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5389
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5386
5390
  continue;
5387
5391
  }
5388
5392
  const hostRect = hostEl.getBoundingClientRect();
5389
5393
  const nodeId = resolveStableNodeId(hostEl, usedNodeIds);
5390
5394
  if (seenResolvedHosts.has(hostEl)) {
5391
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5395
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5392
5396
  continue;
5393
5397
  }
5394
5398
  seenResolvedHosts.add(hostEl);
@@ -5418,9 +5422,9 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5418
5422
  identityOrigin: templateFrameLabel(identityMeta2),
5419
5423
  editorMeta: surfaceMeta
5420
5424
  });
5421
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5425
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5422
5426
  } else {
5423
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5427
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5424
5428
  }
5425
5429
  }
5426
5430
  return results;
@@ -5476,8 +5480,10 @@ function useElementScanner(enabled) {
5476
5480
  if (pausedRef.current) return;
5477
5481
  const root = document.getElementById("root") ?? document.body;
5478
5482
  const usedNodeIds = /* @__PURE__ */ new Set();
5479
- const scanned = scanElements(root, 0, usedNodeIds);
5483
+ const resolvedUnreported = /* @__PURE__ */ new Set();
5484
+ const scanned = scanElements(root, 0, usedNodeIds, void 0, resolvedUnreported);
5480
5485
  const seenEls = new Set(scanned.map((s) => s.element));
5486
+ for (const el of resolvedUnreported) seenEls.add(el);
5481
5487
  for (const el of Array.from(document.querySelectorAll("[data-dnd-moved]"))) {
5482
5488
  if (seenEls.has(el)) continue;
5483
5489
  const entry = buildMovedElementEntry(el, usedNodeIds);
@@ -5717,7 +5723,7 @@ function ElementHighlight({
5717
5723
  "data-outline-for": el.nodeId,
5718
5724
  "data-design-mode-ui": true,
5719
5725
  style: getOutlineStyle(rect, isSelected, inspectorTheme),
5720
- children: isSelected && HANDLES.map((dir) => {
5726
+ children: isSelected && el.draggable && HANDLES.map((dir) => {
5721
5727
  const pos = handlePosition(dir);
5722
5728
  return /* @__PURE__ */ jsx2(
5723
5729
  "div",
@@ -8484,7 +8490,9 @@ function useResizeSession({
8484
8490
  }) {
8485
8491
  const handleResizeHandlePointerDown = useCallback7((e, nodeId, dir) => {
8486
8492
  if (refs.resizeSessionRef.current) return;
8487
- const selectedEl = refs.nodeMapRef.current.get(nodeId)?.element ?? document.querySelector(`[data-dnd-node-id="${nodeId}"]`);
8493
+ const scanned = refs.nodeMapRef.current.get(nodeId);
8494
+ if (scanned && scanned.draggable === false) return;
8495
+ const selectedEl = scanned?.element ?? document.querySelector(`[data-dnd-node-id="${nodeId}"]`);
8488
8496
  if (!selectedEl) return;
8489
8497
  e.preventDefault();
8490
8498
  e.stopPropagation();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  meaningfulChildren
3
- } from "./chunk-O3UMTM4N.mjs";
3
+ } from "./chunk-P5XMHBFS.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
@@ -13,6 +13,9 @@ import { types, PluginObj } from '@babel/core';
13
13
  * + (ARCH-883 F1) slot, propBindings, propOwner
14
14
  * + (ARCH-883-KOI) origin
15
15
  * + (ARCH-1444) childrenText — non-host elements only
16
+ * + (ARCH-1450) childrenSource, propSources — same-file identifier tracing
17
+ * for values `propBindings` cannot reach (no destructured-prop component
18
+ * in scope, or a value that never touches a prop at all)
16
19
  *
17
20
  * At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
18
21
  * element identity without any DOM attributes.
@@ -216,6 +219,134 @@ interface PropBinding {
216
219
  */
217
220
  kind: 'direct' | 'derived' | 'formatted';
218
221
  }
222
+ /**
223
+ * ARCH-1450 — where a rendered EXPRESSION traces to in the SAME file, for the
224
+ * one case `propBindings` structurally cannot answer: a value that never
225
+ * touches a component prop at all.
226
+ *
227
+ * `propBindings`/`propOwner` trace an expression to a PROP of the enclosing
228
+ * (module-level, destructured) component — that machinery is silent whenever
229
+ * there is no such component (a screen's `export default function X()` with
230
+ * no destructured params) or whenever the expression bottoms out in a plain
231
+ * local rather than a prop (`const title = "Contenido"; … <Section
232
+ * title={title}>`). In both cases `collectPropBindings` returns `{bindings:
233
+ * [], owner: null}` and the identifier name never crosses the postMessage
234
+ * boundary in any form — the FE's `usage-prop-resolver` has nothing to walk.
235
+ * `childrenSource`/`propSources` close exactly that gap, independently of
236
+ * whether a `PropBinding` was also emitted for the same target: the two
237
+ * fields answer different questions ("is this a prop, and is it a safe
238
+ * rewrite" vs "what source-level thing does this identifier name") and a
239
+ * consumer may need either, or both, depending on what it already knows.
240
+ *
241
+ * THE FOUR ANSWERS:
242
+ *
243
+ * `identifier` — the expression is a bare identifier whose binding
244
+ * resolves (constant, same file, not an import) but is
245
+ * not a literal-init local — a destructured prop used
246
+ * directly, a plain function parameter, or a local
247
+ * `const`/`let` whose initializer is not itself a plain
248
+ * string. `name` is the LOCAL identifier as written at
249
+ * this usage — NOT necessarily the prop name `propBindings`
250
+ * would report for a renamed destructure
251
+ * (`{ headline: h }` reports `h` here, `headline` there).
252
+ * Consumers that need the true prop name still read
253
+ * `propBindings`; this field only says "this traces to a
254
+ * resolvable local named X", which is enough to tell a
255
+ * prop/parameter apart from a dead end.
256
+ * `local-const` — the identifier resolves to a `const`/`let` declared in
257
+ * THIS SAME FILE (by construction — scope resolution never
258
+ * crosses a file boundary) whose initializer is a
259
+ * `StringLiteral` or a `TemplateLiteral` with NO
260
+ * interpolations. `literal` is that string — the exact
261
+ * text a consumer would show/replace. `file`/`line`/`col`
262
+ * point at the INITIALIZER, not the identifier's usage —
263
+ * that is where a rewrite must land: replacing the
264
+ * identifier's occurrence would rename a variable, not
265
+ * change what it renders wherever else it is used.
266
+ * `refs` (FE MINOR-5) is `binding.referencePaths.length` —
267
+ * how many places in the file READ this const, counting
268
+ * every usage, not only the one this `PropSource` sits on.
269
+ * `refs > 1` means the SAME initializer feeds more than
270
+ * one rendered element (`const title = "X"; <A
271
+ * title={title}/><B title={title}/>` — both usages report
272
+ * `refs: 2`): rewriting the initializer changes what BOTH
273
+ * elements show, so a consumer must warn or refuse rather
274
+ * than treat the write as scoped to the one element the
275
+ * edit was made on. `refs === 1` is the only shape safe to
276
+ * treat as a single-target rewrite.
277
+ * `member` — a non-computed member-expression chain
278
+ * (`props.title`, `item.address.city`) whose ROOT resolves
279
+ * the same way `identifier`'s binding does (constant, same
280
+ * file, not an import). `root` is the base identifier;
281
+ * `path` is the property chain beyond it, in order
282
+ * (`['address', 'city']` for `item.address.city`).
283
+ * `other` — the honest gap marker, same role `formatted` plays for
284
+ * `PropBinding.kind` and `'__dynamic__'` plays for
285
+ * `childrenText`: an expression the plugin looked at and
286
+ * declined to trace — a computed member (`obj[key]`), a
287
+ * call, a template WITH interpolations, a conditional, an
288
+ * identifier with no binding (a free/global read), a
289
+ * binding that IS reassigned somewhere in the file, or one
290
+ * that comes from an `import`. NEVER inferred past any of
291
+ * those — a wrong trace is strictly worse than an honest
292
+ * gap.
293
+ *
294
+ * SCOPE — deliberately NARROW, unlike `childrenText`/`origin`: both fields
295
+ * are OMITTED (not stamped `other`) whenever there is nothing to say, the
296
+ * same convention `propBindings`/`propOwner`/`slot` already use ("emitted
297
+ * only when it carries information"). `childrenSource` is only computed when
298
+ * the element's children are EXACTLY one `JSXExpressionContainer` (the case
299
+ * that makes `childrenText === '__dynamic__'` for the single most common
300
+ * reason — `<h1>{title}</h1>` — and therefore the case where a source trace
301
+ * is actually useful); self-closing elements, plain-text children and
302
+ * mixed/multi children never get it. `propSources` only carries entries for
303
+ * attributes whose value is a `JSXExpressionContainer` wrapping something
304
+ * OTHER than a literal `extractStaticProps` already captures exactly
305
+ * (`staticProps` already has the true value for `title={"Static"}`; adding a
306
+ * `propSources.title: {kind:'other'}` next to it would look like a gap where
307
+ * there is none). Both fields are ABSENT — not `null`/`{}` — on every bundle
308
+ * built before ARCH-1450, the same absence-means-"cannot answer" law every
309
+ * other additive field in this file states.
310
+ *
311
+ * Emitted for BOTH host and non-host elements for `propSources` (attribute
312
+ * resolution does not depend on the element being a component usage — a host
313
+ * `<div title={title}>` traces exactly the same way `staticProps` treats host
314
+ * and component attributes identically). `childrenSource` follows
315
+ * `childrenText`'s own host exemption: a host element's children are the
316
+ * screen's own literal JSX, not a component usage, so there is no target for
317
+ * an editor-driven rewrite to route through in the first place.
318
+ *
319
+ * CROSS-REPO CONTRACT (ARCH-1450 CR MINOR-1): `usage-prop-resolver.ts`
320
+ * (archie-app) is the only consumer, and the two repos share no fixture of
321
+ * their own — a rename here (e.g. `literal` -> `value`) would leave both
322
+ * suites green while every real generated project broke. The published
323
+ * package ships `fixtures/prop-source.fixture.json` (see `files` in
324
+ * `package.json`) specifically so archie-app's tests can assert against the
325
+ * REAL emitted shape instead of a hand-typed copy of it. That file is
326
+ * GENERATED, never hand-edited — see `src/prop-source-fixture.generate.test.ts`,
327
+ * which regenerates it from the live plugin and fails loud the moment the
328
+ * committed copy drifts. Regenerate with `UPDATE_FIXTURES=1 npx vitest run
329
+ * src/prop-source-fixture.generate.test.ts`, review the diff, and bump
330
+ * archie-app's copy in the same change.
331
+ */
332
+ type PropSource = {
333
+ kind: 'identifier';
334
+ name: string;
335
+ } | {
336
+ kind: 'local-const';
337
+ name: string;
338
+ file: string;
339
+ line: number;
340
+ col: number;
341
+ literal: string | null;
342
+ refs: number;
343
+ } | {
344
+ kind: 'member';
345
+ root: string;
346
+ path: string[];
347
+ } | {
348
+ kind: 'other';
349
+ };
219
350
  /** Metadata injected into every JSX element by babel-plugin-editor-meta. */
220
351
  interface EditorMeta {
221
352
  /** Stable identifier: "file:line:col:componentName" */
@@ -398,6 +529,30 @@ interface EditorMeta {
398
529
  * not the usage's, and routing those would write the wrong prop. Mismatch = gap.
399
530
  */
400
531
  propOwner?: string | null;
532
+ /**
533
+ * ARCH-1450 — source trace for this element's SOLE children expression
534
+ * (`<h1>{title}</h1>`), independently of whether `propBindings` also has a
535
+ * `target: 'children'` entry for the same node. See `PropSource`'s own
536
+ * docstring for the full contract (the four kinds, the narrow emission
537
+ * scope, why it exists alongside `propBindings`).
538
+ *
539
+ * Absent when the element is self-closing, has plain-text children, has
540
+ * more than one child, or is a HOST element — see `PropSource`'s docstring.
541
+ * Absent on every bundle built before ARCH-1450.
542
+ */
543
+ childrenSource?: PropSource;
544
+ /**
545
+ * ARCH-1450 — source trace for each ATTRIBUTE whose value is a
546
+ * `JSXExpressionContainer`, keyed by the attribute name (never `attr:`
547
+ * prefixed — this is not `PropBinding.target`). See `PropSource`'s own
548
+ * docstring for the full contract.
549
+ *
550
+ * Emitted only when non-empty — read it as `meta.propSources ?? {}`. An
551
+ * attribute whose expression is a literal already exact in `staticProps`
552
+ * never gets an entry here (nothing to trace beyond what `staticProps`
553
+ * already says). Absent on every bundle built before ARCH-1450.
554
+ */
555
+ propSources?: Record<string, PropSource>;
401
556
  }
402
557
 
403
558
  interface ArchieDevToolsOptions {
package/dist/index.d.ts CHANGED
@@ -13,6 +13,9 @@ import { types, PluginObj } from '@babel/core';
13
13
  * + (ARCH-883 F1) slot, propBindings, propOwner
14
14
  * + (ARCH-883-KOI) origin
15
15
  * + (ARCH-1444) childrenText — non-host elements only
16
+ * + (ARCH-1450) childrenSource, propSources — same-file identifier tracing
17
+ * for values `propBindings` cannot reach (no destructured-prop component
18
+ * in scope, or a value that never touches a prop at all)
16
19
  *
17
20
  * At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
18
21
  * element identity without any DOM attributes.
@@ -216,6 +219,134 @@ interface PropBinding {
216
219
  */
217
220
  kind: 'direct' | 'derived' | 'formatted';
218
221
  }
222
+ /**
223
+ * ARCH-1450 — where a rendered EXPRESSION traces to in the SAME file, for the
224
+ * one case `propBindings` structurally cannot answer: a value that never
225
+ * touches a component prop at all.
226
+ *
227
+ * `propBindings`/`propOwner` trace an expression to a PROP of the enclosing
228
+ * (module-level, destructured) component — that machinery is silent whenever
229
+ * there is no such component (a screen's `export default function X()` with
230
+ * no destructured params) or whenever the expression bottoms out in a plain
231
+ * local rather than a prop (`const title = "Contenido"; … <Section
232
+ * title={title}>`). In both cases `collectPropBindings` returns `{bindings:
233
+ * [], owner: null}` and the identifier name never crosses the postMessage
234
+ * boundary in any form — the FE's `usage-prop-resolver` has nothing to walk.
235
+ * `childrenSource`/`propSources` close exactly that gap, independently of
236
+ * whether a `PropBinding` was also emitted for the same target: the two
237
+ * fields answer different questions ("is this a prop, and is it a safe
238
+ * rewrite" vs "what source-level thing does this identifier name") and a
239
+ * consumer may need either, or both, depending on what it already knows.
240
+ *
241
+ * THE FOUR ANSWERS:
242
+ *
243
+ * `identifier` — the expression is a bare identifier whose binding
244
+ * resolves (constant, same file, not an import) but is
245
+ * not a literal-init local — a destructured prop used
246
+ * directly, a plain function parameter, or a local
247
+ * `const`/`let` whose initializer is not itself a plain
248
+ * string. `name` is the LOCAL identifier as written at
249
+ * this usage — NOT necessarily the prop name `propBindings`
250
+ * would report for a renamed destructure
251
+ * (`{ headline: h }` reports `h` here, `headline` there).
252
+ * Consumers that need the true prop name still read
253
+ * `propBindings`; this field only says "this traces to a
254
+ * resolvable local named X", which is enough to tell a
255
+ * prop/parameter apart from a dead end.
256
+ * `local-const` — the identifier resolves to a `const`/`let` declared in
257
+ * THIS SAME FILE (by construction — scope resolution never
258
+ * crosses a file boundary) whose initializer is a
259
+ * `StringLiteral` or a `TemplateLiteral` with NO
260
+ * interpolations. `literal` is that string — the exact
261
+ * text a consumer would show/replace. `file`/`line`/`col`
262
+ * point at the INITIALIZER, not the identifier's usage —
263
+ * that is where a rewrite must land: replacing the
264
+ * identifier's occurrence would rename a variable, not
265
+ * change what it renders wherever else it is used.
266
+ * `refs` (FE MINOR-5) is `binding.referencePaths.length` —
267
+ * how many places in the file READ this const, counting
268
+ * every usage, not only the one this `PropSource` sits on.
269
+ * `refs > 1` means the SAME initializer feeds more than
270
+ * one rendered element (`const title = "X"; <A
271
+ * title={title}/><B title={title}/>` — both usages report
272
+ * `refs: 2`): rewriting the initializer changes what BOTH
273
+ * elements show, so a consumer must warn or refuse rather
274
+ * than treat the write as scoped to the one element the
275
+ * edit was made on. `refs === 1` is the only shape safe to
276
+ * treat as a single-target rewrite.
277
+ * `member` — a non-computed member-expression chain
278
+ * (`props.title`, `item.address.city`) whose ROOT resolves
279
+ * the same way `identifier`'s binding does (constant, same
280
+ * file, not an import). `root` is the base identifier;
281
+ * `path` is the property chain beyond it, in order
282
+ * (`['address', 'city']` for `item.address.city`).
283
+ * `other` — the honest gap marker, same role `formatted` plays for
284
+ * `PropBinding.kind` and `'__dynamic__'` plays for
285
+ * `childrenText`: an expression the plugin looked at and
286
+ * declined to trace — a computed member (`obj[key]`), a
287
+ * call, a template WITH interpolations, a conditional, an
288
+ * identifier with no binding (a free/global read), a
289
+ * binding that IS reassigned somewhere in the file, or one
290
+ * that comes from an `import`. NEVER inferred past any of
291
+ * those — a wrong trace is strictly worse than an honest
292
+ * gap.
293
+ *
294
+ * SCOPE — deliberately NARROW, unlike `childrenText`/`origin`: both fields
295
+ * are OMITTED (not stamped `other`) whenever there is nothing to say, the
296
+ * same convention `propBindings`/`propOwner`/`slot` already use ("emitted
297
+ * only when it carries information"). `childrenSource` is only computed when
298
+ * the element's children are EXACTLY one `JSXExpressionContainer` (the case
299
+ * that makes `childrenText === '__dynamic__'` for the single most common
300
+ * reason — `<h1>{title}</h1>` — and therefore the case where a source trace
301
+ * is actually useful); self-closing elements, plain-text children and
302
+ * mixed/multi children never get it. `propSources` only carries entries for
303
+ * attributes whose value is a `JSXExpressionContainer` wrapping something
304
+ * OTHER than a literal `extractStaticProps` already captures exactly
305
+ * (`staticProps` already has the true value for `title={"Static"}`; adding a
306
+ * `propSources.title: {kind:'other'}` next to it would look like a gap where
307
+ * there is none). Both fields are ABSENT — not `null`/`{}` — on every bundle
308
+ * built before ARCH-1450, the same absence-means-"cannot answer" law every
309
+ * other additive field in this file states.
310
+ *
311
+ * Emitted for BOTH host and non-host elements for `propSources` (attribute
312
+ * resolution does not depend on the element being a component usage — a host
313
+ * `<div title={title}>` traces exactly the same way `staticProps` treats host
314
+ * and component attributes identically). `childrenSource` follows
315
+ * `childrenText`'s own host exemption: a host element's children are the
316
+ * screen's own literal JSX, not a component usage, so there is no target for
317
+ * an editor-driven rewrite to route through in the first place.
318
+ *
319
+ * CROSS-REPO CONTRACT (ARCH-1450 CR MINOR-1): `usage-prop-resolver.ts`
320
+ * (archie-app) is the only consumer, and the two repos share no fixture of
321
+ * their own — a rename here (e.g. `literal` -> `value`) would leave both
322
+ * suites green while every real generated project broke. The published
323
+ * package ships `fixtures/prop-source.fixture.json` (see `files` in
324
+ * `package.json`) specifically so archie-app's tests can assert against the
325
+ * REAL emitted shape instead of a hand-typed copy of it. That file is
326
+ * GENERATED, never hand-edited — see `src/prop-source-fixture.generate.test.ts`,
327
+ * which regenerates it from the live plugin and fails loud the moment the
328
+ * committed copy drifts. Regenerate with `UPDATE_FIXTURES=1 npx vitest run
329
+ * src/prop-source-fixture.generate.test.ts`, review the diff, and bump
330
+ * archie-app's copy in the same change.
331
+ */
332
+ type PropSource = {
333
+ kind: 'identifier';
334
+ name: string;
335
+ } | {
336
+ kind: 'local-const';
337
+ name: string;
338
+ file: string;
339
+ line: number;
340
+ col: number;
341
+ literal: string | null;
342
+ refs: number;
343
+ } | {
344
+ kind: 'member';
345
+ root: string;
346
+ path: string[];
347
+ } | {
348
+ kind: 'other';
349
+ };
219
350
  /** Metadata injected into every JSX element by babel-plugin-editor-meta. */
220
351
  interface EditorMeta {
221
352
  /** Stable identifier: "file:line:col:componentName" */
@@ -398,6 +529,30 @@ interface EditorMeta {
398
529
  * not the usage's, and routing those would write the wrong prop. Mismatch = gap.
399
530
  */
400
531
  propOwner?: string | null;
532
+ /**
533
+ * ARCH-1450 — source trace for this element's SOLE children expression
534
+ * (`<h1>{title}</h1>`), independently of whether `propBindings` also has a
535
+ * `target: 'children'` entry for the same node. See `PropSource`'s own
536
+ * docstring for the full contract (the four kinds, the narrow emission
537
+ * scope, why it exists alongside `propBindings`).
538
+ *
539
+ * Absent when the element is self-closing, has plain-text children, has
540
+ * more than one child, or is a HOST element — see `PropSource`'s docstring.
541
+ * Absent on every bundle built before ARCH-1450.
542
+ */
543
+ childrenSource?: PropSource;
544
+ /**
545
+ * ARCH-1450 — source trace for each ATTRIBUTE whose value is a
546
+ * `JSXExpressionContainer`, keyed by the attribute name (never `attr:`
547
+ * prefixed — this is not `PropBinding.target`). See `PropSource`'s own
548
+ * docstring for the full contract.
549
+ *
550
+ * Emitted only when non-empty — read it as `meta.propSources ?? {}`. An
551
+ * attribute whose expression is a literal already exact in `staticProps`
552
+ * never gets an entry here (nothing to trace beyond what `staticProps`
553
+ * already says). Absent on every bundle built before ARCH-1450.
554
+ */
555
+ propSources?: Record<string, PropSource>;
401
556
  }
402
557
 
403
558
  interface ArchieDevToolsOptions {
package/dist/index.js CHANGED
@@ -382,6 +382,146 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
382
382
  if (!t.isJSXText(only)) return "__dynamic__";
383
383
  return only.value.trim();
384
384
  }
385
+ function literalInitText(init) {
386
+ if (t.isStringLiteral(init)) return init.value;
387
+ if (t.isTemplateLiteral(init) && init.expressions.length === 0) {
388
+ const [quasi] = init.quasis;
389
+ if (!quasi) return null;
390
+ return quasi.value.cooked ?? quasi.value.raw;
391
+ }
392
+ return null;
393
+ }
394
+ function describeLocalConstSource(name, binding) {
395
+ const declarator = binding.path;
396
+ if (!declarator.isVariableDeclarator()) return null;
397
+ const declaration = declarator.parentPath;
398
+ if (!declaration?.isVariableDeclaration()) return null;
399
+ if (declaration.node.kind !== "const" && declaration.node.kind !== "let") return null;
400
+ const init = declarator.node.init;
401
+ if (!init) return null;
402
+ const literal = literalInitText(init);
403
+ if (literal === null) return null;
404
+ const loc = init.loc?.start;
405
+ if (!loc) return null;
406
+ return {
407
+ kind: "local-const",
408
+ name,
409
+ // The init lives in the SAME file being transformed by construction —
410
+ // `scope.getBinding` never crosses a file boundary — so the caller's
411
+ // own `file` (computed once per visited node) is correct here too.
412
+ file: currentFile,
413
+ line: loc.line,
414
+ col: loc.column + 1,
415
+ literal,
416
+ // FE MINOR-5: how many places in the FILE read this const — every
417
+ // reference, not only the one this call is describing. `refs > 1`
418
+ // means one initializer feeds more than one rendered element, so a
419
+ // consumer must warn/refuse rather than treat a rewrite as scoped to
420
+ // just this usage (see `PropSource`'s docstring for the full contract).
421
+ refs: binding.referencePaths.length
422
+ };
423
+ }
424
+ function resolvableSameFileBinding(name, scope) {
425
+ const binding = scope.getBinding(name);
426
+ if (!binding) return null;
427
+ if (!binding.constant) return null;
428
+ const bp = binding.path;
429
+ if (bp.isImportSpecifier() || bp.isImportDefaultSpecifier() || bp.isImportNamespaceSpecifier()) {
430
+ return null;
431
+ }
432
+ return binding;
433
+ }
434
+ function memberChain(expr) {
435
+ const path = [];
436
+ let current = expr;
437
+ while (t.isMemberExpression(current)) {
438
+ if (current.computed) return null;
439
+ if (!t.isIdentifier(current.property)) return null;
440
+ path.unshift(current.property.name);
441
+ current = current.object;
442
+ }
443
+ if (!t.isIdentifier(current)) return null;
444
+ return { root: current.name, path };
445
+ }
446
+ let currentFile = "";
447
+ function describeSource(expr, scope) {
448
+ if (t.isIdentifier(expr)) {
449
+ const binding = resolvableSameFileBinding(expr.name, scope);
450
+ if (!binding) return { kind: "other" };
451
+ const local = describeLocalConstSource(expr.name, binding);
452
+ if (local) return local;
453
+ return { kind: "identifier", name: expr.name };
454
+ }
455
+ if (t.isMemberExpression(expr) && !expr.computed) {
456
+ const chain = memberChain(expr);
457
+ if (chain && resolvableSameFileBinding(chain.root, scope)) {
458
+ return { kind: "member", root: chain.root, path: chain.path };
459
+ }
460
+ }
461
+ return { kind: "other" };
462
+ }
463
+ function isStaticPropsLiteral(expr) {
464
+ return t.isStringLiteral(expr) || t.isNumericLiteral(expr) || t.isBooleanLiteral(expr) || t.isNullLiteral(expr);
465
+ }
466
+ function describeChildrenSource(opening) {
467
+ const elementPath = opening.parentPath;
468
+ if (!elementPath || !elementPath.isJSXElement()) return null;
469
+ const children = elementPath.node.children;
470
+ if (children.length !== 1) return null;
471
+ const [only] = children;
472
+ if (!t.isJSXExpressionContainer(only)) return null;
473
+ if (t.isJSXEmptyExpression(only.expression)) return null;
474
+ return describeSource(only.expression, opening.scope);
475
+ }
476
+ function collectPropSources(attrs, scope) {
477
+ const entries = [];
478
+ for (const attr of attrs) {
479
+ if (!t.isJSXAttribute(attr)) continue;
480
+ const name = t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
481
+ if (NON_BINDING_ATTRS.has(name)) continue;
482
+ const value = attr.value;
483
+ if (!t.isJSXExpressionContainer(value)) continue;
484
+ const expression = value.expression;
485
+ if (t.isJSXEmptyExpression(expression)) continue;
486
+ if (isStaticPropsLiteral(expression)) continue;
487
+ entries.push([name, describeSource(expression, scope)]);
488
+ }
489
+ return entries;
490
+ }
491
+ function propSourceToAst(source) {
492
+ switch (source.kind) {
493
+ case "identifier":
494
+ return t.objectExpression([
495
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("identifier")),
496
+ t.objectProperty(t.identifier("name"), t.stringLiteral(source.name))
497
+ ]);
498
+ case "local-const":
499
+ return t.objectExpression([
500
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("local-const")),
501
+ t.objectProperty(t.identifier("name"), t.stringLiteral(source.name)),
502
+ t.objectProperty(t.identifier("file"), t.stringLiteral(source.file)),
503
+ t.objectProperty(t.identifier("line"), t.numericLiteral(source.line)),
504
+ t.objectProperty(t.identifier("col"), t.numericLiteral(source.col)),
505
+ t.objectProperty(
506
+ t.identifier("literal"),
507
+ source.literal === null ? t.nullLiteral() : t.stringLiteral(source.literal)
508
+ ),
509
+ t.objectProperty(t.identifier("refs"), t.numericLiteral(source.refs))
510
+ ]);
511
+ case "member":
512
+ return t.objectExpression([
513
+ t.objectProperty(t.identifier("kind"), t.stringLiteral("member")),
514
+ t.objectProperty(t.identifier("root"), t.stringLiteral(source.root)),
515
+ t.objectProperty(
516
+ t.identifier("path"),
517
+ t.arrayExpression(source.path.map((segment) => t.stringLiteral(segment)))
518
+ )
519
+ ]);
520
+ case "other":
521
+ default:
522
+ return t.objectExpression([t.objectProperty(t.identifier("kind"), t.stringLiteral("other"))]);
523
+ }
524
+ }
385
525
  function isMetaStripSpreadExpression(arg) {
386
526
  if (!t.isCallExpression(arg)) return false;
387
527
  if (!t.isArrowFunctionExpression(arg.callee)) return false;
@@ -773,6 +913,7 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
773
913
  if (!loc) return;
774
914
  const root = state.file.opts.root ?? "";
775
915
  const file = root ? nodePath.relative(root, filename) : filename;
916
+ currentFile = file;
776
917
  const line = loc.line;
777
918
  const col = loc.column + 1;
778
919
  const componentName = getJSXTagName(path.node);
@@ -856,6 +997,12 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
856
997
  childrenText === null ? t.nullLiteral() : t.stringLiteral(childrenText)
857
998
  )
858
999
  );
1000
+ const childrenSource = describeChildrenSource(path);
1001
+ if (childrenSource) {
1002
+ metaObj.properties.push(
1003
+ t.objectProperty(t.identifier("childrenSource"), propSourceToAst(childrenSource))
1004
+ );
1005
+ }
859
1006
  }
860
1007
  if (bindings.length > 0) {
861
1008
  metaObj.properties.push(
@@ -868,6 +1015,19 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
868
1015
  )
869
1016
  );
870
1017
  }
1018
+ const propSourceEntries = collectPropSources(path.node.attributes, path.scope);
1019
+ if (propSourceEntries.length > 0) {
1020
+ metaObj.properties.push(
1021
+ t.objectProperty(
1022
+ t.identifier("propSources"),
1023
+ t.objectExpression(
1024
+ propSourceEntries.map(
1025
+ ([name, source]) => t.objectProperty(t.stringLiteral(name), propSourceToAst(source))
1026
+ )
1027
+ )
1028
+ )
1029
+ );
1030
+ }
871
1031
  if (isHostElement && includeHostSourceRef) {
872
1032
  path.node.attributes.push(
873
1033
  t.jsxAttribute(
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __dirname,
3
3
  babelPluginEditorMeta
4
- } from "./chunk-O3UMTM4N.mjs";
4
+ } from "./chunk-P5XMHBFS.mjs";
5
5
 
6
6
  // src/index.ts
7
7
  import { readFileSync } from "fs";
@@ -0,0 +1,310 @@
1
+ [
2
+ {
3
+ "id": "attr-local-const-string",
4
+ "description": "const local con init string, sin props desestructuradas — atributo de la USAGE",
5
+ "code": "export default function Screen() {\n const title = \"Contenido\";\n return <Section title={title} />;\n}",
6
+ "usageComponentName": "Section",
7
+ "editorMeta": {
8
+ "nodeId": "src/components.jsx:3:10:Section",
9
+ "file": "src/components.jsx",
10
+ "line": 3,
11
+ "col": 10,
12
+ "componentName": "Section",
13
+ "staticProps": {
14
+ "title": "__dynamic__"
15
+ },
16
+ "movable": false,
17
+ "parentNodeId": null,
18
+ "childrenText": null,
19
+ "propSources": {
20
+ "title": {
21
+ "kind": "local-const",
22
+ "name": "title",
23
+ "file": "src/components.jsx",
24
+ "line": 2,
25
+ "col": 17,
26
+ "literal": "Contenido",
27
+ "refs": 1
28
+ }
29
+ }
30
+ }
31
+ },
32
+ {
33
+ "id": "attr-local-const-template-no-interp",
34
+ "description": "const local con init template literal SIN interpolaciones",
35
+ "code": "export default function Screen() {\n const title = `Contenido fijo`;\n return <Section title={title} />;\n}",
36
+ "usageComponentName": "Section",
37
+ "editorMeta": {
38
+ "nodeId": "src/components.jsx:3:10:Section",
39
+ "file": "src/components.jsx",
40
+ "line": 3,
41
+ "col": 10,
42
+ "componentName": "Section",
43
+ "staticProps": {
44
+ "title": "__dynamic__"
45
+ },
46
+ "movable": false,
47
+ "parentNodeId": null,
48
+ "childrenText": null,
49
+ "propSources": {
50
+ "title": {
51
+ "kind": "local-const",
52
+ "name": "title",
53
+ "file": "src/components.jsx",
54
+ "line": 2,
55
+ "col": 17,
56
+ "literal": "Contenido fijo",
57
+ "refs": 1
58
+ }
59
+ }
60
+ }
61
+ },
62
+ {
63
+ "id": "attr-local-const-refs-2",
64
+ "description": "FE MINOR-5: el MISMO const alimenta dos elementos — refs:2 en ambas usages",
65
+ "code": "export default function Screen() {\n const title = \"Contenido\";\n return (\n <div>\n <Section title={title} />\n <Aside title={title} />\n </div>\n );\n}",
66
+ "usageComponentName": "Section",
67
+ "editorMeta": {
68
+ "nodeId": "src/components.jsx:5:7:Section",
69
+ "file": "src/components.jsx",
70
+ "line": 5,
71
+ "col": 7,
72
+ "componentName": "Section",
73
+ "staticProps": {
74
+ "title": "__dynamic__"
75
+ },
76
+ "movable": true,
77
+ "parentNodeId": "src/components.jsx:4:5:div",
78
+ "childrenText": null,
79
+ "propSources": {
80
+ "title": {
81
+ "kind": "local-const",
82
+ "name": "title",
83
+ "file": "src/components.jsx",
84
+ "line": 2,
85
+ "col": 17,
86
+ "literal": "Contenido",
87
+ "refs": 2
88
+ }
89
+ }
90
+ }
91
+ },
92
+ {
93
+ "id": "attr-identifier-destructured-prop",
94
+ "description": "identificador que es una prop desestructurada del componente",
95
+ "code": "function Screen({ title }) { return <Section title={title} />; }",
96
+ "usageComponentName": "Section",
97
+ "editorMeta": {
98
+ "nodeId": "src/components.jsx:1:37:Section",
99
+ "file": "src/components.jsx",
100
+ "line": 1,
101
+ "col": 37,
102
+ "componentName": "Section",
103
+ "staticProps": {
104
+ "title": "__dynamic__"
105
+ },
106
+ "movable": false,
107
+ "parentNodeId": null,
108
+ "childrenText": null,
109
+ "propBindings": [
110
+ {
111
+ "target": "attr:title",
112
+ "prop": "title",
113
+ "kind": "direct"
114
+ }
115
+ ],
116
+ "propOwner": "Screen",
117
+ "propSources": {
118
+ "title": {
119
+ "kind": "identifier",
120
+ "name": "title"
121
+ }
122
+ }
123
+ }
124
+ },
125
+ {
126
+ "id": "attr-identifier-plain-param",
127
+ "description": "identificador que es un parametro NO desestructurado",
128
+ "code": "function Screen(props) { return <Section title={props} />; }",
129
+ "usageComponentName": "Section",
130
+ "editorMeta": {
131
+ "nodeId": "src/components.jsx:1:33:Section",
132
+ "file": "src/components.jsx",
133
+ "line": 1,
134
+ "col": 33,
135
+ "componentName": "Section",
136
+ "staticProps": {
137
+ "title": "__dynamic__"
138
+ },
139
+ "movable": false,
140
+ "parentNodeId": null,
141
+ "childrenText": null,
142
+ "propSources": {
143
+ "title": {
144
+ "kind": "identifier",
145
+ "name": "props"
146
+ }
147
+ }
148
+ }
149
+ },
150
+ {
151
+ "id": "attr-member-props-title",
152
+ "description": "props.title — parametro plano accedido por propiedad",
153
+ "code": "function Screen(props) { return <Section title={props.title} />; }",
154
+ "usageComponentName": "Section",
155
+ "editorMeta": {
156
+ "nodeId": "src/components.jsx:1:33:Section",
157
+ "file": "src/components.jsx",
158
+ "line": 1,
159
+ "col": 33,
160
+ "componentName": "Section",
161
+ "staticProps": {
162
+ "title": "__dynamic__"
163
+ },
164
+ "movable": false,
165
+ "parentNodeId": null,
166
+ "childrenText": null,
167
+ "propSources": {
168
+ "title": {
169
+ "kind": "member",
170
+ "root": "props",
171
+ "path": [
172
+ "title"
173
+ ]
174
+ }
175
+ }
176
+ }
177
+ },
178
+ {
179
+ "id": "attr-member-deep-chain",
180
+ "description": "item.address.city — cadena de propiedades, en orden",
181
+ "code": "function Screen({ item }) { return <Section title={item.address.city} />; }",
182
+ "usageComponentName": "Section",
183
+ "editorMeta": {
184
+ "nodeId": "src/components.jsx:1:36:Section",
185
+ "file": "src/components.jsx",
186
+ "line": 1,
187
+ "col": 36,
188
+ "componentName": "Section",
189
+ "staticProps": {
190
+ "title": "__dynamic__"
191
+ },
192
+ "movable": false,
193
+ "parentNodeId": null,
194
+ "childrenText": null,
195
+ "propBindings": [
196
+ {
197
+ "target": "attr:title",
198
+ "prop": "item",
199
+ "kind": "formatted"
200
+ }
201
+ ],
202
+ "propOwner": "Screen",
203
+ "propSources": {
204
+ "title": {
205
+ "kind": "member",
206
+ "root": "item",
207
+ "path": [
208
+ "address",
209
+ "city"
210
+ ]
211
+ }
212
+ }
213
+ }
214
+ },
215
+ {
216
+ "id": "attr-other-reassigned",
217
+ "description": "binding reasignado en el fichero — nunca se traza",
218
+ "code": "export default function Screen() {\n let title = \"Contenido\";\n title = \"Otro valor\";\n return <Section title={title} />;\n}",
219
+ "usageComponentName": "Section",
220
+ "editorMeta": {
221
+ "nodeId": "src/components.jsx:4:10:Section",
222
+ "file": "src/components.jsx",
223
+ "line": 4,
224
+ "col": 10,
225
+ "componentName": "Section",
226
+ "staticProps": {
227
+ "title": "__dynamic__"
228
+ },
229
+ "movable": false,
230
+ "parentNodeId": null,
231
+ "childrenText": null,
232
+ "propSources": {
233
+ "title": {
234
+ "kind": "other"
235
+ }
236
+ }
237
+ }
238
+ },
239
+ {
240
+ "id": "attr-other-cross-module-import",
241
+ "description": "identificador importado de otro modulo — nunca se traza",
242
+ "code": "import { title } from './constants';\nexport default function Screen() {\n return <Section title={title} />;\n}",
243
+ "usageComponentName": "Section",
244
+ "editorMeta": {
245
+ "nodeId": "src/components.jsx:3:10:Section",
246
+ "file": "src/components.jsx",
247
+ "line": 3,
248
+ "col": 10,
249
+ "componentName": "Section",
250
+ "staticProps": {
251
+ "title": "__dynamic__"
252
+ },
253
+ "movable": false,
254
+ "parentNodeId": null,
255
+ "childrenText": null,
256
+ "propSources": {
257
+ "title": {
258
+ "kind": "other"
259
+ }
260
+ }
261
+ }
262
+ },
263
+ {
264
+ "id": "children-local-const",
265
+ "description": "const local trazado a traves de la SOLA expresion hija",
266
+ "code": "export default function Screen() {\n const title = \"Contenido\";\n return <Section>{title}</Section>;\n}",
267
+ "usageComponentName": "Section",
268
+ "editorMeta": {
269
+ "nodeId": "src/components.jsx:3:10:Section",
270
+ "file": "src/components.jsx",
271
+ "line": 3,
272
+ "col": 10,
273
+ "componentName": "Section",
274
+ "staticProps": null,
275
+ "movable": false,
276
+ "parentNodeId": null,
277
+ "childrenText": "__dynamic__",
278
+ "childrenSource": {
279
+ "kind": "local-const",
280
+ "name": "title",
281
+ "file": "src/components.jsx",
282
+ "line": 2,
283
+ "col": 17,
284
+ "literal": "Contenido",
285
+ "refs": 1
286
+ }
287
+ }
288
+ },
289
+ {
290
+ "id": "children-shadowing-map-callback-param",
291
+ "description": "MINOR-1: un const de modulo con el mismo nombre NUNCA gana — el parametro del callback de .map() shadowea",
292
+ "code": "const title = \"modulo\";\nexport default function Screen({ items }) {\n return items.map(({ title }) => <CardTitle>{title}</CardTitle>);\n}",
293
+ "usageComponentName": "CardTitle",
294
+ "editorMeta": {
295
+ "nodeId": "src/components.jsx:3:35:CardTitle",
296
+ "file": "src/components.jsx",
297
+ "line": 3,
298
+ "col": 35,
299
+ "componentName": "CardTitle",
300
+ "staticProps": null,
301
+ "movable": false,
302
+ "parentNodeId": null,
303
+ "childrenText": "__dynamic__",
304
+ "childrenSource": {
305
+ "kind": "identifier",
306
+ "name": "title"
307
+ }
308
+ }
309
+ }
310
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,8 @@
40
40
  "release": "node publish.js"
41
41
  },
42
42
  "files": [
43
- "dist/**/*"
43
+ "dist/**/*",
44
+ "fixtures/**/*"
44
45
  ],
45
46
  "keywords": [
46
47
  "vite",