@archie/devtools 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/client/client.js +10 -4
- package/dist/client/client.mjs +10 -4
- package/dist/index.d.mts +182 -1
- package/dist/index.d.ts +182 -1
- package/dist/index.js +322 -38
- package/dist/index.mjs +322 -38
- package/dist/inspector.standalone.js +223 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -178,6 +178,36 @@ function babelPluginDndAuto({ types: t }, opts = {}) {
|
|
|
178
178
|
|
|
179
179
|
// src/babel-plugin-editor-meta.ts
|
|
180
180
|
import * as nodePath from "path";
|
|
181
|
+
var KIT_SLOT_ATTR = "data-kit-slot";
|
|
182
|
+
var MAX_SLOT_SEGMENTS = 4;
|
|
183
|
+
var NON_BINDING_ATTRS = /* @__PURE__ */ new Set(["__editorMeta", "key", "ref", KIT_SLOT_ATTR]);
|
|
184
|
+
var STRUCTURAL_ATTRS = /* @__PURE__ */ new Set([
|
|
185
|
+
"id",
|
|
186
|
+
"htmlFor",
|
|
187
|
+
"slot",
|
|
188
|
+
"name",
|
|
189
|
+
"type",
|
|
190
|
+
"role",
|
|
191
|
+
// The camelCase carrier of the SAME durable key as `data-agent-id`: kit
|
|
192
|
+
// components take `agentId` and render it as the data attribute. Found on the
|
|
193
|
+
// real corpus (×4) — blocking only the dashed form would have left the prop
|
|
194
|
+
// form routable, and a durable key with a string-literal usage value sails
|
|
195
|
+
// straight through the usage-value law.
|
|
196
|
+
"agentId"
|
|
197
|
+
]);
|
|
198
|
+
function isNonContentAttribute(name) {
|
|
199
|
+
if (/^on[A-Z]/.test(name)) return true;
|
|
200
|
+
if (name.startsWith("data-")) return true;
|
|
201
|
+
return STRUCTURAL_ATTRS.has(name);
|
|
202
|
+
}
|
|
203
|
+
function normalizeSlot(raw) {
|
|
204
|
+
const value = raw.trim();
|
|
205
|
+
if (!value) return null;
|
|
206
|
+
const segments = value.split("/");
|
|
207
|
+
if (segments.length > MAX_SLOT_SEGMENTS) return null;
|
|
208
|
+
if (segments.some((segment) => segment.trim() === "")) return null;
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
181
211
|
function isPackageImport(src) {
|
|
182
212
|
if (src.startsWith(".") || src.startsWith("/")) return false;
|
|
183
213
|
if (src.startsWith("@")) {
|
|
@@ -193,7 +223,8 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
193
223
|
const {
|
|
194
224
|
fileExclude = /node_modules/,
|
|
195
225
|
instrumentImported = true,
|
|
196
|
-
includeHostSourceRef = false
|
|
226
|
+
includeHostSourceRef = false,
|
|
227
|
+
origin = "screen"
|
|
197
228
|
} = opts;
|
|
198
229
|
function getJSXTagName(el) {
|
|
199
230
|
const { name } = el;
|
|
@@ -306,29 +337,275 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
306
337
|
}
|
|
307
338
|
return false;
|
|
308
339
|
}
|
|
340
|
+
function buildPayloadFilter(payload) {
|
|
341
|
+
return t.callExpression(
|
|
342
|
+
t.arrowFunctionExpression(
|
|
343
|
+
[
|
|
344
|
+
t.objectPattern([
|
|
345
|
+
t.objectProperty(
|
|
346
|
+
t.identifier("__editorMeta"),
|
|
347
|
+
t.identifier("_$em"),
|
|
348
|
+
false,
|
|
349
|
+
false
|
|
350
|
+
),
|
|
351
|
+
t.objectProperty(
|
|
352
|
+
t.stringLiteral(KIT_SLOT_ATTR),
|
|
353
|
+
t.identifier("_$ks"),
|
|
354
|
+
false,
|
|
355
|
+
false
|
|
356
|
+
),
|
|
357
|
+
t.restElement(t.identifier("_$r"))
|
|
358
|
+
])
|
|
359
|
+
],
|
|
360
|
+
t.identifier("_$r")
|
|
361
|
+
),
|
|
362
|
+
[t.objectExpression([t.spreadElement(payload)])]
|
|
363
|
+
);
|
|
364
|
+
}
|
|
309
365
|
function sanitizeSpreadAttributes(attrs) {
|
|
310
366
|
for (const attr of attrs) {
|
|
311
367
|
if (!t.isJSXSpreadAttribute(attr)) continue;
|
|
312
368
|
if (isMetaStripSpreadExpression(attr.argument)) continue;
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
|
|
369
|
+
attr.argument = buildPayloadFilter(attr.argument);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function consumeKitSlot(attrs) {
|
|
373
|
+
let slot = null;
|
|
374
|
+
let found = false;
|
|
375
|
+
for (const attr of attrs) {
|
|
376
|
+
if (!t.isJSXAttribute(attr)) continue;
|
|
377
|
+
if (!t.isJSXIdentifier(attr.name, { name: KIT_SLOT_ATTR })) continue;
|
|
378
|
+
found = true;
|
|
379
|
+
if (slot !== null) continue;
|
|
380
|
+
const { value } = attr;
|
|
381
|
+
if (t.isStringLiteral(value)) {
|
|
382
|
+
slot = normalizeSlot(value.value);
|
|
383
|
+
} else if (t.isJSXExpressionContainer(value) && t.isStringLiteral(value.expression)) {
|
|
384
|
+
slot = normalizeSlot(value.expression.value);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (!found) return { slot: null, found: false, attrs };
|
|
388
|
+
return {
|
|
389
|
+
slot,
|
|
390
|
+
found: true,
|
|
391
|
+
attrs: attrs.filter(
|
|
392
|
+
(attr) => !(t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: KIT_SLOT_ATTR }))
|
|
393
|
+
)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function getFunctionName(fn) {
|
|
397
|
+
const node = fn.node;
|
|
398
|
+
if (t.isFunctionDeclaration(node) && node.id) return node.id.name;
|
|
399
|
+
if (t.isFunctionExpression(node) && node.id) return node.id.name;
|
|
400
|
+
const parent = fn.parentPath;
|
|
401
|
+
if (parent?.isVariableDeclarator() && t.isIdentifier(parent.node.id)) {
|
|
402
|
+
return parent.node.id.name;
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
function buildPropMap(pattern) {
|
|
407
|
+
const map = /* @__PURE__ */ new Map();
|
|
408
|
+
for (const prop of pattern.properties) {
|
|
409
|
+
if (!t.isObjectProperty(prop) || prop.computed) continue;
|
|
410
|
+
const key = t.isIdentifier(prop.key) ? prop.key.name : t.isStringLiteral(prop.key) ? prop.key.value : null;
|
|
411
|
+
if (!key) continue;
|
|
412
|
+
const value = prop.value;
|
|
413
|
+
if (t.isIdentifier(value)) {
|
|
414
|
+
map.set(value.name, key);
|
|
415
|
+
} else if (t.isAssignmentPattern(value) && t.isIdentifier(value.left)) {
|
|
416
|
+
map.set(value.left.name, key);
|
|
417
|
+
}
|
|
331
418
|
}
|
|
419
|
+
return map;
|
|
420
|
+
}
|
|
421
|
+
function resolvePropContext(path2) {
|
|
422
|
+
let fn = path2.getFunctionParent();
|
|
423
|
+
while (fn) {
|
|
424
|
+
const [first] = fn.node.params;
|
|
425
|
+
if (first && t.isObjectPattern(first)) {
|
|
426
|
+
if (fn.scope.parent !== fn.scope.getProgramParent()) return null;
|
|
427
|
+
return {
|
|
428
|
+
componentScope: fn.scope,
|
|
429
|
+
propMap: buildPropMap(first),
|
|
430
|
+
componentName: getFunctionName(fn)
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
fn = fn.getFunctionParent();
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
function resolveProp(name, scope, ctx) {
|
|
438
|
+
const prop = ctx.propMap.get(name);
|
|
439
|
+
if (!prop) return null;
|
|
440
|
+
const binding = scope.getBinding(name);
|
|
441
|
+
if (!binding) return null;
|
|
442
|
+
if (binding.scope !== ctx.componentScope) return null;
|
|
443
|
+
if (!binding.constant) return null;
|
|
444
|
+
return prop;
|
|
445
|
+
}
|
|
446
|
+
function isScopeWithinComponent(scope, ctx) {
|
|
447
|
+
let current = scope;
|
|
448
|
+
while (current) {
|
|
449
|
+
if (current === ctx.componentScope) return true;
|
|
450
|
+
current = current.parent;
|
|
451
|
+
}
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
function resolveLocalConstInit(name, scope, ctx) {
|
|
455
|
+
const binding = scope.getBinding(name);
|
|
456
|
+
if (!binding) return null;
|
|
457
|
+
if (!binding.constant) return null;
|
|
458
|
+
const declarator = binding.path;
|
|
459
|
+
if (!declarator.isVariableDeclarator()) return null;
|
|
460
|
+
if (!isScopeWithinComponent(binding.scope, ctx)) return null;
|
|
461
|
+
const init = declarator.node.init;
|
|
462
|
+
if (!init || !t.isExpression(init)) return null;
|
|
463
|
+
return { init, scope: declarator.scope };
|
|
464
|
+
}
|
|
465
|
+
function isStaticStringLiteral(node) {
|
|
466
|
+
if (t.isStringLiteral(node)) return true;
|
|
467
|
+
return t.isTemplateLiteral(node) && node.expressions.length === 0;
|
|
468
|
+
}
|
|
469
|
+
function collectPropIdentifiers(node, scope, ctx, out = []) {
|
|
470
|
+
if (!node) return out;
|
|
471
|
+
const visit = (child) => collectPropIdentifiers(child, scope, ctx, out);
|
|
472
|
+
if (t.isIdentifier(node)) {
|
|
473
|
+
const prop = resolveProp(node.name, scope, ctx);
|
|
474
|
+
if (prop && !out.includes(prop)) out.push(prop);
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
478
|
+
visit(node.object);
|
|
479
|
+
if (node.computed) visit(node.property);
|
|
480
|
+
return out;
|
|
481
|
+
}
|
|
482
|
+
if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
|
|
483
|
+
visit(node.callee);
|
|
484
|
+
node.arguments.forEach((arg) => visit(arg));
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
if (t.isTemplateLiteral(node)) {
|
|
488
|
+
node.expressions.forEach((expr) => visit(expr));
|
|
489
|
+
return out;
|
|
490
|
+
}
|
|
491
|
+
if (t.isConditionalExpression(node)) {
|
|
492
|
+
visit(node.test);
|
|
493
|
+
visit(node.consequent);
|
|
494
|
+
visit(node.alternate);
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
if (t.isLogicalExpression(node) || t.isBinaryExpression(node)) {
|
|
498
|
+
visit(node.left);
|
|
499
|
+
visit(node.right);
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
if (t.isUnaryExpression(node) || t.isAwaitExpression(node)) {
|
|
503
|
+
visit(node.argument);
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
506
|
+
if (t.isArrayExpression(node)) {
|
|
507
|
+
node.elements.forEach((el) => visit(el));
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
if (t.isObjectExpression(node)) {
|
|
511
|
+
for (const prop of node.properties) {
|
|
512
|
+
if (t.isObjectProperty(prop)) {
|
|
513
|
+
if (prop.computed) visit(prop.key);
|
|
514
|
+
visit(prop.value);
|
|
515
|
+
} else if (t.isSpreadElement(prop)) {
|
|
516
|
+
visit(prop.argument);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return out;
|
|
520
|
+
}
|
|
521
|
+
if (t.isSpreadElement(node)) {
|
|
522
|
+
visit(node.argument);
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
if (t.isTSAsExpression(node) || t.isTSNonNullExpression(node) || t.isTypeCastExpression(node)) {
|
|
526
|
+
visit(node.expression);
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
529
|
+
return out;
|
|
530
|
+
}
|
|
531
|
+
function classifyExpression(expr, scope, ctx, hop) {
|
|
532
|
+
if (t.isIdentifier(expr)) {
|
|
533
|
+
const prop = resolveProp(expr.name, scope, ctx);
|
|
534
|
+
if (prop) return { kind: "direct", prop };
|
|
535
|
+
if (hop > 0) return null;
|
|
536
|
+
const local = resolveLocalConstInit(expr.name, scope, ctx);
|
|
537
|
+
if (!local) return null;
|
|
538
|
+
const inner = classifyExpression(local.init, local.scope, ctx, hop + 1);
|
|
539
|
+
if (!inner) return null;
|
|
540
|
+
return { kind: inner.kind === "formatted" ? "formatted" : "derived", prop: inner.prop };
|
|
541
|
+
}
|
|
542
|
+
if (t.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??") && isStaticStringLiteral(expr.right)) {
|
|
543
|
+
const left = classifyExpression(expr.left, scope, ctx, hop);
|
|
544
|
+
if (!left) return null;
|
|
545
|
+
return { kind: left.kind === "formatted" ? "formatted" : "derived", prop: left.prop };
|
|
546
|
+
}
|
|
547
|
+
const props = collectPropIdentifiers(expr, scope, ctx);
|
|
548
|
+
return props.length > 0 ? { kind: "formatted", prop: props[0] } : null;
|
|
549
|
+
}
|
|
550
|
+
function meaningfulChildren(children) {
|
|
551
|
+
return children.filter((child) => {
|
|
552
|
+
if (t.isJSXText(child)) return child.value.trim() !== "";
|
|
553
|
+
if (t.isJSXExpressionContainer(child)) return !t.isJSXEmptyExpression(child.expression);
|
|
554
|
+
return true;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
function collectPropBindings(path2, attrs) {
|
|
558
|
+
const ctx = resolvePropContext(path2);
|
|
559
|
+
if (!ctx || ctx.propMap.size === 0) return { bindings: [], owner: null };
|
|
560
|
+
if (ctx.componentName === null) return { bindings: [], owner: null };
|
|
561
|
+
const scope = path2.scope;
|
|
562
|
+
const bindings = [];
|
|
563
|
+
const elementPath = path2.parentPath;
|
|
564
|
+
if (elementPath && elementPath.isJSXElement()) {
|
|
565
|
+
const children = meaningfulChildren(elementPath.node.children);
|
|
566
|
+
const isSoleChild = children.length === 1;
|
|
567
|
+
for (const child of children) {
|
|
568
|
+
if (!t.isJSXExpressionContainer(child)) continue;
|
|
569
|
+
const expression = child.expression;
|
|
570
|
+
if (t.isJSXEmptyExpression(expression)) continue;
|
|
571
|
+
const verdict = classifyExpression(expression, scope, ctx, 0);
|
|
572
|
+
if (!verdict) continue;
|
|
573
|
+
bindings.push({
|
|
574
|
+
target: "children",
|
|
575
|
+
prop: verdict.prop,
|
|
576
|
+
kind: isSoleChild ? verdict.kind : "formatted"
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const attr of attrs) {
|
|
581
|
+
if (!t.isJSXAttribute(attr)) continue;
|
|
582
|
+
const name = t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
583
|
+
if (NON_BINDING_ATTRS.has(name)) continue;
|
|
584
|
+
const value = attr.value;
|
|
585
|
+
if (!t.isJSXExpressionContainer(value)) continue;
|
|
586
|
+
const expression = value.expression;
|
|
587
|
+
if (t.isJSXEmptyExpression(expression)) continue;
|
|
588
|
+
const verdict = classifyExpression(expression, scope, ctx, 0);
|
|
589
|
+
if (!verdict) continue;
|
|
590
|
+
bindings.push({
|
|
591
|
+
target: `attr:${name}`,
|
|
592
|
+
prop: verdict.prop,
|
|
593
|
+
// M2b: machine-contract attributes are reported, never offered.
|
|
594
|
+
kind: isNonContentAttribute(name) ? "formatted" : verdict.kind
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
return { bindings, owner: bindings.length > 0 ? ctx.componentName : null };
|
|
598
|
+
}
|
|
599
|
+
function propBindingsToAst(bindings) {
|
|
600
|
+
return t.arrayExpression(
|
|
601
|
+
bindings.map(
|
|
602
|
+
(binding) => t.objectExpression([
|
|
603
|
+
t.objectProperty(t.identifier("target"), t.stringLiteral(binding.target)),
|
|
604
|
+
t.objectProperty(t.identifier("prop"), t.stringLiteral(binding.prop)),
|
|
605
|
+
t.objectProperty(t.identifier("kind"), t.stringLiteral(binding.kind))
|
|
606
|
+
])
|
|
607
|
+
)
|
|
608
|
+
);
|
|
332
609
|
}
|
|
333
610
|
return {
|
|
334
611
|
name: "babel-plugin-editor-meta",
|
|
@@ -356,25 +633,13 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
356
633
|
if (!propsArg || t.isNullLiteral(propsArg)) return;
|
|
357
634
|
if (!t.isExpression(propsArg)) return;
|
|
358
635
|
if (isMetaStripSpreadExpression(propsArg)) return;
|
|
359
|
-
args[1] =
|
|
360
|
-
t.arrowFunctionExpression(
|
|
361
|
-
[
|
|
362
|
-
t.objectPattern([
|
|
363
|
-
t.objectProperty(
|
|
364
|
-
t.identifier("__editorMeta"),
|
|
365
|
-
t.identifier("_$em"),
|
|
366
|
-
false,
|
|
367
|
-
false
|
|
368
|
-
),
|
|
369
|
-
t.restElement(t.identifier("_$r"))
|
|
370
|
-
])
|
|
371
|
-
],
|
|
372
|
-
t.identifier("_$r")
|
|
373
|
-
),
|
|
374
|
-
[t.objectExpression([t.spreadElement(propsArg)])]
|
|
375
|
-
);
|
|
636
|
+
args[1] = buildPayloadFilter(propsArg);
|
|
376
637
|
},
|
|
377
638
|
JSXOpeningElement(path2, state) {
|
|
639
|
+
const consumed = consumeKitSlot(path2.node.attributes);
|
|
640
|
+
path2.node.attributes = consumed.attrs;
|
|
641
|
+
const slot = consumed.slot;
|
|
642
|
+
const hasSlotAttr = consumed.found;
|
|
378
643
|
const filename = state.filename ?? "";
|
|
379
644
|
if (fileExclude.test(filename)) return;
|
|
380
645
|
const existing = path2.node.attributes.find(
|
|
@@ -420,9 +685,11 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
420
685
|
}
|
|
421
686
|
}
|
|
422
687
|
const nodeId = computeNodeId(file, line, col, componentName);
|
|
423
|
-
const
|
|
688
|
+
const isKitOrigin = origin === "kit" || hasSlotAttr;
|
|
689
|
+
const movable = isKitOrigin ? false : isMovable(path2);
|
|
424
690
|
const parentNodeId = getParentNodeId(path2, file);
|
|
425
691
|
const staticPropsObj = extractStaticProps(path2.node.attributes);
|
|
692
|
+
const { bindings, owner } = collectPropBindings(path2, path2.node.attributes);
|
|
426
693
|
const metaObj = t.objectExpression([
|
|
427
694
|
t.objectProperty(t.identifier("nodeId"), t.stringLiteral(nodeId)),
|
|
428
695
|
t.objectProperty(t.identifier("file"), t.stringLiteral(file)),
|
|
@@ -439,6 +706,22 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
439
706
|
parentNodeId ? t.stringLiteral(parentNodeId) : t.nullLiteral()
|
|
440
707
|
)
|
|
441
708
|
]);
|
|
709
|
+
if (slot !== null) {
|
|
710
|
+
metaObj.properties.push(
|
|
711
|
+
t.objectProperty(t.identifier("slot"), t.stringLiteral(slot))
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
if (bindings.length > 0) {
|
|
715
|
+
metaObj.properties.push(
|
|
716
|
+
t.objectProperty(t.identifier("propBindings"), propBindingsToAst(bindings))
|
|
717
|
+
);
|
|
718
|
+
metaObj.properties.push(
|
|
719
|
+
t.objectProperty(
|
|
720
|
+
t.identifier("propOwner"),
|
|
721
|
+
owner ? t.stringLiteral(owner) : t.nullLiteral()
|
|
722
|
+
)
|
|
723
|
+
);
|
|
724
|
+
}
|
|
442
725
|
if (isHostElement && includeHostSourceRef) {
|
|
443
726
|
path2.node.attributes.push(
|
|
444
727
|
t.jsxAttribute(
|
|
@@ -447,10 +730,11 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
|
|
|
447
730
|
)
|
|
448
731
|
);
|
|
449
732
|
}
|
|
733
|
+
const metaThunk = t.arrowFunctionExpression([], metaObj);
|
|
450
734
|
path2.node.attributes.push(
|
|
451
735
|
t.jsxAttribute(
|
|
452
736
|
t.jsxIdentifier("__editorMeta"),
|
|
453
|
-
t.jsxExpressionContainer(
|
|
737
|
+
t.jsxExpressionContainer(metaThunk)
|
|
454
738
|
)
|
|
455
739
|
);
|
|
456
740
|
}
|
|
@@ -95,6 +95,28 @@
|
|
|
95
95
|
var FIBER_KEY_PREFIX = "__reactFiber$"
|
|
96
96
|
var PROPS_KEY_PREFIX = "__reactProps$"
|
|
97
97
|
|
|
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.
|
|
104
|
+
function resolveEditorMetaValue(value) {
|
|
105
|
+
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).
|
|
111
|
+
return value()
|
|
112
|
+
}
|
|
113
|
+
// Twin parity with fiber-bridge.ts's readEditorMetaValue: only
|
|
114
|
+
// undefined/null is a miss, not "any falsy value" — __editorMeta is
|
|
115
|
+
// always an object or a function in practice, so this is a no-op
|
|
116
|
+
// today, but keeps the two implementations provably equivalent.
|
|
117
|
+
return value === undefined || value === null ? null : value
|
|
118
|
+
}
|
|
119
|
+
|
|
98
120
|
function hasEditorMetaInFiberChain(fiber) {
|
|
99
121
|
var current = fiber
|
|
100
122
|
while (current) {
|
|
@@ -195,7 +217,7 @@
|
|
|
195
217
|
var fiber = getFiberFromElement(el)
|
|
196
218
|
while (fiber) {
|
|
197
219
|
if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
|
|
198
|
-
return fiber.memoizedProps.__editorMeta
|
|
220
|
+
return resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
|
|
199
221
|
}
|
|
200
222
|
fiber = fiber["return"]
|
|
201
223
|
}
|
|
@@ -205,7 +227,7 @@
|
|
|
205
227
|
function getDirectEditorMetaFromElement(el) {
|
|
206
228
|
var fiber = getFiberFromElement(el)
|
|
207
229
|
if (fiber && fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
|
|
208
|
-
return fiber.memoizedProps.__editorMeta
|
|
230
|
+
return resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
|
|
209
231
|
}
|
|
210
232
|
return null
|
|
211
233
|
}
|
|
@@ -231,7 +253,7 @@
|
|
|
231
253
|
break
|
|
232
254
|
}
|
|
233
255
|
if (current.memoizedProps && current.memoizedProps.__editorMeta) {
|
|
234
|
-
return current.memoizedProps.__editorMeta
|
|
256
|
+
return resolveEditorMetaValue(current.memoizedProps.__editorMeta)
|
|
235
257
|
}
|
|
236
258
|
current = current["return"]
|
|
237
259
|
}
|
|
@@ -252,6 +274,156 @@
|
|
|
252
274
|
return null
|
|
253
275
|
}
|
|
254
276
|
|
|
277
|
+
// ── ARCH-883 F2 — usage chain + instance identity ───────────────────
|
|
278
|
+
// Vanilla twins of collectEditorMetaChain (fiber-bridge.ts) and
|
|
279
|
+
// resolveInstanceIdentity (instance-identity.ts). Those files carry the
|
|
280
|
+
// full rationale; keep the three in step.
|
|
281
|
+
var MAX_META_CHAIN = 24
|
|
282
|
+
|
|
283
|
+
// Every __editorMeta on the fiber return chain, innermost first. The host
|
|
284
|
+
// needs the FULL metas (staticProps/propBindings/propOwner), not the
|
|
285
|
+
// narrowed inspectorSource shape: routing a kit-internal node means
|
|
286
|
+
// finding the USAGE above it and reading the prop value AT that usage.
|
|
287
|
+
function collectEditorMetaChain(el) {
|
|
288
|
+
var chain = []
|
|
289
|
+
var fiber = getFiberFromElement(el)
|
|
290
|
+
while (fiber && chain.length < MAX_META_CHAIN) {
|
|
291
|
+
if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
|
|
292
|
+
var meta = resolveEditorMetaValue(fiber.memoizedProps.__editorMeta)
|
|
293
|
+
if (meta) chain.push(meta)
|
|
294
|
+
}
|
|
295
|
+
fiber = fiber["return"]
|
|
296
|
+
}
|
|
297
|
+
return chain
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
var MAX_ROW_ROOT_DEPTH = 12
|
|
301
|
+
var MAX_KEY_WALK = 40
|
|
302
|
+
|
|
303
|
+
// The fiber that OWNS the nearest __editorMeta for `el`. Vanilla twin of
|
|
304
|
+
// getEditorMetaOwnerFiber (fiber-bridge.ts).
|
|
305
|
+
function getEditorMetaOwnerFiberFromElement(el) {
|
|
306
|
+
var fiber = getFiberFromElement(el)
|
|
307
|
+
while (fiber) {
|
|
308
|
+
if (fiber.memoizedProps && fiber.memoizedProps.__editorMeta) {
|
|
309
|
+
return fiber
|
|
310
|
+
}
|
|
311
|
+
fiber = fiber["return"]
|
|
312
|
+
}
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// The SINGLE host element `owner` renders, or null when it renders zero
|
|
317
|
+
// or several, or crosses a HostPortal (tag 4). Vanilla twin of
|
|
318
|
+
// getSingleHostElement (fiber-bridge.ts) — conservative by design.
|
|
319
|
+
function getSingleHostElementFromFiber(owner) {
|
|
320
|
+
var hosts = []
|
|
321
|
+
var stack = owner && owner.child ? [owner.child] : []
|
|
322
|
+
while (stack.length > 0) {
|
|
323
|
+
var fiber = stack.pop()
|
|
324
|
+
if (!fiber) continue
|
|
325
|
+
if (fiber.tag === 4) return null
|
|
326
|
+
if (
|
|
327
|
+
typeof fiber.type === "string" &&
|
|
328
|
+
fiber.stateNode &&
|
|
329
|
+
fiber.stateNode.nodeType === 1
|
|
330
|
+
) {
|
|
331
|
+
hosts.push(fiber.stateNode)
|
|
332
|
+
if (hosts.length > 1) return null
|
|
333
|
+
} else if (fiber.child) {
|
|
334
|
+
stack.push(fiber.child)
|
|
335
|
+
}
|
|
336
|
+
if (fiber.sibling) stack.push(fiber.sibling)
|
|
337
|
+
}
|
|
338
|
+
return hosts.length === 1 ? hosts[0] : null
|
|
339
|
+
}
|
|
340
|
+
|
|
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)
|
|
347
|
+
if (!owner) return null
|
|
348
|
+
if (getSingleHostElementFromFiber(owner) !== el) return null
|
|
349
|
+
return resolveEditorMetaValue(owner.memoizedProps.__editorMeta)
|
|
350
|
+
}
|
|
351
|
+
|
|
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
|
|
354
|
+
// fiber chain here made every UNSTAMPED subtree (any two siblings
|
|
355
|
+
// rendered by a component the plugin never transformed — anything from
|
|
356
|
+
// node_modules, and here SVG internals too, since this twin accepts any
|
|
357
|
+
// element node) look like a repeated collection. Meanwhile a
|
|
358
|
+
// direct-meta-ONLY rule would make every kit component that destructures
|
|
359
|
+
// its props look like a singleton — which is the fail-OPEN mistake, and
|
|
360
|
+
// would write a literal into a mapped usage, changing every row.
|
|
361
|
+
// See instance-identity.ts for the full rationale.
|
|
362
|
+
function ownMetaNodeId(el) {
|
|
363
|
+
if (!el || el.nodeType !== 1) return null
|
|
364
|
+
var direct = getDirectEditorMetaFromElement(el)
|
|
365
|
+
if (direct && direct.nodeId) return direct.nodeId
|
|
366
|
+
var projected = projectUsageMetaFromElement(el)
|
|
367
|
+
return projected && projected.nodeId ? projected.nodeId : null
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// The repeated-row boundary above `el`, or null. INNERMOST repetition
|
|
371
|
+
// wins (a tag inside a card inside a list is identified by the tag's key).
|
|
372
|
+
function findCollectionWindow(el) {
|
|
373
|
+
var node = el
|
|
374
|
+
for (var depth = 0; depth < MAX_ROW_ROOT_DEPTH; depth++) {
|
|
375
|
+
var parent = node.parentElement
|
|
376
|
+
if (!parent) return null
|
|
377
|
+
|
|
378
|
+
var nodeId = ownMetaNodeId(node)
|
|
379
|
+
if (nodeId !== null) {
|
|
380
|
+
var twins = []
|
|
381
|
+
for (var i = 0; i < parent.children.length; i++) {
|
|
382
|
+
var child = parent.children[i]
|
|
383
|
+
if (ownMetaNodeId(child) === nodeId) twins.push(child)
|
|
384
|
+
}
|
|
385
|
+
if (twins.length >= 2) {
|
|
386
|
+
return {
|
|
387
|
+
rowRoot: node,
|
|
388
|
+
container: parent,
|
|
389
|
+
ordinal: twins.indexOf(node),
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
node = parent
|
|
395
|
+
}
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Nearest React key BETWEEN the row root and the collection container.
|
|
400
|
+
// Stopping at the container is the point: a keyed ancestor above it
|
|
401
|
+
// identifies a page, not a row.
|
|
402
|
+
function keyWithinWindow(rowRoot, container) {
|
|
403
|
+
var fiber = getFiberFromElement(rowRoot)
|
|
404
|
+
for (var steps = 0; fiber && steps < MAX_KEY_WALK; steps++) {
|
|
405
|
+
if (fiber.stateNode === container) return null
|
|
406
|
+
var key = fiber.key
|
|
407
|
+
if (typeof key === "string" && key.length > 0) return key
|
|
408
|
+
if (typeof key === "number") return String(key)
|
|
409
|
+
fiber = fiber["return"]
|
|
410
|
+
}
|
|
411
|
+
return null
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// WHICH instance of a possibly-repeated node this element is.
|
|
415
|
+
// `scanOrdinal` is telemetry/UI only — the host must REJECT an intent
|
|
416
|
+
// with inCollection && fiberKey === null, never fall back to it.
|
|
417
|
+
function resolveInstanceIdentity(el) {
|
|
418
|
+
var win = findCollectionWindow(el)
|
|
419
|
+
if (!win) return { fiberKey: null, scanOrdinal: 0, inCollection: false }
|
|
420
|
+
return {
|
|
421
|
+
fiberKey: keyWithinWindow(win.rowRoot, win.container),
|
|
422
|
+
scanOrdinal: win.ordinal,
|
|
423
|
+
inCollection: true,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
255
427
|
if (window._inspectorSharedTargetObserver) {
|
|
256
428
|
try {
|
|
257
429
|
window._inspectorSharedTargetObserver.disconnect()
|
|
@@ -864,9 +1036,14 @@
|
|
|
864
1036
|
postMessageToParent({
|
|
865
1037
|
type: "INSPECTOR_SCRIPT_LOADED",
|
|
866
1038
|
timestamp: Date.now(),
|
|
867
|
-
version: "2.1.
|
|
1039
|
+
version: "2.1.1",
|
|
868
1040
|
protocolVersion: 1,
|
|
869
1041
|
capabilities: {
|
|
1042
|
+
// 2.1.1: function-valued __editorMeta props are invoked before any
|
|
1043
|
+
// postMessage, so meta payloads stay structured-cloneable. Hosts can
|
|
1044
|
+
// gate on this bit to confirm the deployed CDN artifact carries the
|
|
1045
|
+
// fix (the 2.1.0 URL is overwritten in place on publish).
|
|
1046
|
+
functionValuedEditorMeta: true,
|
|
870
1047
|
inspect: true,
|
|
871
1048
|
dndMove: true,
|
|
872
1049
|
dndResize: true,
|
|
@@ -2020,7 +2197,49 @@
|
|
|
2020
2197
|
: null,
|
|
2021
2198
|
attributes: attributes,
|
|
2022
2199
|
inspectorSource: inspectorInfo,
|
|
2200
|
+
// ARCH-883 F1 — FORWARD THE META WHOLE. The plugin now also emits
|
|
2201
|
+
// `slot`, `propBindings` and `propOwner` (SDD §2.3/§2.6) and the editor
|
|
2202
|
+
// host routes edits off them; field-picking here would silently strip
|
|
2203
|
+
// them and turn every kit-internal edit into a gap. `inspectorSource`
|
|
2204
|
+
// above is the deliberately narrow structure — this one is not.
|
|
2205
|
+
//
|
|
2206
|
+
// Unlike P0-1 this needs no deploy ordering: the extra keys are plain
|
|
2207
|
+
// data, so an already-published standalone.js forwards them unchanged
|
|
2208
|
+
// through `postMessage`'s structured clone. Whether a given PAGE has
|
|
2209
|
+
// them depends on the plugin that built it, which the host resolves via
|
|
2210
|
+
// `deriveEditorCapabilities(kitRuntimeVersion)` (SDD §2.5) — never from
|
|
2211
|
+
// this script's version. The slot string is opaque here; validating it
|
|
2212
|
+
// against kit-runtime's `slot-vocabulary` is the host's job.
|
|
2023
2213
|
editorMeta: editorMeta ? editorMeta.meta : null,
|
|
2214
|
+
// ARCH-883 F2 — the two things the host cannot derive from
|
|
2215
|
+
// `editorMeta` alone (SDD §2.9):
|
|
2216
|
+
//
|
|
2217
|
+
// editorMetaChain: routing a kit-internal node needs the USAGE
|
|
2218
|
+
// (`<Kit.AuthFrame headline="…"/>` in screens/<slug>.jsx), which
|
|
2219
|
+
// is NOT the nearest meta — the nearest meta is the <h1> inside
|
|
2220
|
+
// AuthFrame's own template. The host locates the usage by
|
|
2221
|
+
// matching `propOwner` against a chain entry's componentName and
|
|
2222
|
+
// applies the usage-value law to that entry's staticProps. Both
|
|
2223
|
+
// fields live only on the full meta.
|
|
2224
|
+
//
|
|
2225
|
+
// editorInstance: WHICH row of a repeated node was clicked. The
|
|
2226
|
+
// nodeId is a source position and collides across instances; the
|
|
2227
|
+
// only identity that survives an insert above the edited row is
|
|
2228
|
+
// React's `key`, which exists on the fiber and nowhere else.
|
|
2229
|
+
//
|
|
2230
|
+
// Both are plain data, so an already-published copy of this script
|
|
2231
|
+
// forwards them through structured clone unchanged.
|
|
2232
|
+
//
|
|
2233
|
+
// The chain starts at the element the meta was FOUND on (the same
|
|
2234
|
+
// element `editorMeta` above describes) — a clicked node with no
|
|
2235
|
+
// fiber of its own would otherwise yield an empty chain while
|
|
2236
|
+
// reporting a meta resolved from its parent. The instance walk starts
|
|
2237
|
+
// at the CLICKED element, so an inner collection wins over an outer
|
|
2238
|
+
// one (a tag inside a card inside a list).
|
|
2239
|
+
editorMetaChain: collectEditorMetaChain(
|
|
2240
|
+
editorMeta ? editorMeta.element : element
|
|
2241
|
+
),
|
|
2242
|
+
editorInstance: resolveInstanceIdentity(element),
|
|
2024
2243
|
computedStyles: {
|
|
2025
2244
|
display: computed.display,
|
|
2026
2245
|
position: computed.position,
|