@archie/devtools 0.0.35 → 0.0.37
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/client/client.js +163 -2
- package/dist/client/client.mjs +163 -2
- package/dist/inspector.standalone.js +940 -74
- package/package.json +1 -1
|
@@ -1258,6 +1258,11 @@
|
|
|
1258
1258
|
// make this redundant for new indexes, but an already-built index may
|
|
1259
1259
|
// still hold a stale group.)
|
|
1260
1260
|
if (isInsertedSubtreeElement(inspectTarget)) {
|
|
1261
|
+
veTrace("shared-targets", {
|
|
1262
|
+
via: "inserted-subtree",
|
|
1263
|
+
count: 1,
|
|
1264
|
+
nodeId: inspectTarget.getAttribute("data-dnd-node-id") || null,
|
|
1265
|
+
})
|
|
1261
1266
|
return [inspectTarget]
|
|
1262
1267
|
}
|
|
1263
1268
|
|
|
@@ -1268,14 +1273,32 @@
|
|
|
1268
1273
|
inspectTarget,
|
|
1269
1274
|
sharedTargetIndex.sharedBySignature.get(signature) || []
|
|
1270
1275
|
)
|
|
1271
|
-
if (sharedTargets.length > 0)
|
|
1276
|
+
if (sharedTargets.length > 0) {
|
|
1277
|
+
veTrace("shared-targets", {
|
|
1278
|
+
via: "signature",
|
|
1279
|
+
signature: String(signature).slice(0, 160),
|
|
1280
|
+
count: sharedTargets.length,
|
|
1281
|
+
nodeId: inspectTarget.getAttribute("data-dnd-node-id") || null,
|
|
1282
|
+
})
|
|
1283
|
+
return sharedTargets
|
|
1284
|
+
}
|
|
1272
1285
|
}
|
|
1273
1286
|
|
|
1274
1287
|
var legacyTargets = resolveLegacySharedTargets(inspectTarget)
|
|
1275
1288
|
if (legacyTargets.length > 0) {
|
|
1289
|
+
veTrace("shared-targets", {
|
|
1290
|
+
via: "legacy",
|
|
1291
|
+
count: legacyTargets.length,
|
|
1292
|
+
nodeId: inspectTarget.getAttribute("data-dnd-node-id") || null,
|
|
1293
|
+
})
|
|
1276
1294
|
return legacyTargets
|
|
1277
1295
|
}
|
|
1278
1296
|
|
|
1297
|
+
veTrace("shared-targets", {
|
|
1298
|
+
via: "self-only",
|
|
1299
|
+
count: 1,
|
|
1300
|
+
nodeId: inspectTarget.getAttribute("data-dnd-node-id") || null,
|
|
1301
|
+
})
|
|
1279
1302
|
return [inspectTarget]
|
|
1280
1303
|
}
|
|
1281
1304
|
|
|
@@ -1379,10 +1402,24 @@
|
|
|
1379
1402
|
|
|
1380
1403
|
syncBridgeStateFromWindow()
|
|
1381
1404
|
if (!bridgeState.targetOrigin || bridgeState.targetOrigin === "*") {
|
|
1405
|
+
if (msg && msg.type && msg.type !== "ELEMENT_POSITION_UPDATE") {
|
|
1406
|
+
veTrace("out:" + msg.type + ":queued", {
|
|
1407
|
+
requestId: (msg && msg.requestId) || null,
|
|
1408
|
+
})
|
|
1409
|
+
}
|
|
1382
1410
|
bridgeState.pendingParentMessages.push(msg)
|
|
1383
1411
|
return
|
|
1384
1412
|
}
|
|
1385
1413
|
|
|
1414
|
+
if (msg && msg.type && msg.type !== "ELEMENT_POSITION_UPDATE") {
|
|
1415
|
+
veTrace("out:" + msg.type, {
|
|
1416
|
+
requestId: (msg && msg.requestId) || null,
|
|
1417
|
+
operationId:
|
|
1418
|
+
(msg && msg.operationId) ||
|
|
1419
|
+
(msg && msg.payload && msg.payload.operationId) ||
|
|
1420
|
+
null,
|
|
1421
|
+
})
|
|
1422
|
+
}
|
|
1386
1423
|
window.parent.postMessage(msg, bridgeState.targetOrigin)
|
|
1387
1424
|
}
|
|
1388
1425
|
|
|
@@ -1455,80 +1492,207 @@
|
|
|
1455
1492
|
}
|
|
1456
1493
|
}
|
|
1457
1494
|
|
|
1495
|
+
/**
|
|
1496
|
+
* Always-on trace ring buffer, shared shape with the design-mode engine
|
|
1497
|
+
* (`debug-trace.ts`): reproduce a broken gesture, then read
|
|
1498
|
+
* `window.__ARCHIE_VE_TRACE__` / `window.__ARCHIE_VE_TRACE_DUMP__()` in
|
|
1499
|
+
* THIS frame's console. Console echo only with `_inspectorDebug` /
|
|
1500
|
+
* `__ARCHIE_VE_DEBUG__`. Payloads must be small summaries — ids, types,
|
|
1501
|
+
* refs — never elements or full HTML.
|
|
1502
|
+
*/
|
|
1503
|
+
var VE_TRACE_MAX = 800
|
|
1504
|
+
function veTrace(event, data) {
|
|
1505
|
+
try {
|
|
1506
|
+
var buffer = window.__ARCHIE_VE_TRACE__
|
|
1507
|
+
if (!buffer) {
|
|
1508
|
+
buffer = window.__ARCHIE_VE_TRACE__ = []
|
|
1509
|
+
}
|
|
1510
|
+
var entry = { t: Date.now(), layer: "inspector", event: event }
|
|
1511
|
+
if (typeof data !== "undefined") entry.data = data
|
|
1512
|
+
buffer.push(entry)
|
|
1513
|
+
if (buffer.length > VE_TRACE_MAX) {
|
|
1514
|
+
buffer.splice(0, buffer.length - VE_TRACE_MAX)
|
|
1515
|
+
}
|
|
1516
|
+
if (!window.__ARCHIE_VE_TRACE_DUMP__) {
|
|
1517
|
+
window.__ARCHIE_VE_TRACE_DUMP__ = function () {
|
|
1518
|
+
return JSON.stringify(window.__ARCHIE_VE_TRACE__ || [])
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (window._inspectorDebug || window.__ARCHIE_VE_DEBUG__ === true) {
|
|
1522
|
+
// eslint-disable-next-line no-console
|
|
1523
|
+
console.log("[archie-ve:inspector]", event, data || "")
|
|
1524
|
+
}
|
|
1525
|
+
} catch (e) {
|
|
1526
|
+
// Tracing must never break the inspector.
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1531
|
+
// THE HANDSHAKE PAYLOAD — ONE BUILDER, THREE EMISSIONS.
|
|
1532
|
+
//
|
|
1533
|
+
// `INSPECTOR_SCRIPT_LOADED` is emitted exactly ONCE per execution of
|
|
1534
|
+
// this script and is never replayed. A host whose `message` listener
|
|
1535
|
+
// is not attached yet when it fires loses it IRRECOVERABLY, and with
|
|
1536
|
+
// it every `capabilities` bit — including `bindingContract`, the one
|
|
1537
|
+
// that decides whether a text edit on a data row is writable or gets
|
|
1538
|
+
// the "Regenerate this prototype" refusal. That race is real and
|
|
1539
|
+
// routinely lost after a host page refresh: this page is served warm
|
|
1540
|
+
// from the CDN while the host still resolves the async probe that
|
|
1541
|
+
// decides whether to mount its bridge at all.
|
|
1542
|
+
//
|
|
1543
|
+
// The cure is to make the announcement RECOVERABLE, by two
|
|
1544
|
+
// independent routes that both reuse THIS builder (never a
|
|
1545
|
+
// second, drifting literal):
|
|
1546
|
+
// 1. PULL — the host asks (`QUERY_CAPABILITIES`) and gets
|
|
1547
|
+
// `CAPABILITIES_RESULT` back, correlated by `requestId`.
|
|
1548
|
+
// 2. PUSH — every `SET_EDITOR_MODE`/`SYNC_EDITOR_STATE` re-announces
|
|
1549
|
+
// `CAPABILITIES_RESULT` (unsolicited, `requestId: null`) BEFORE
|
|
1550
|
+
// its `EDITOR_MODE_CHANGED`, so a host that lost the handshake
|
|
1551
|
+
// is healed by the message it was already going to send, without
|
|
1552
|
+
// knowing the new message exists.
|
|
1553
|
+
// Both are idempotent: they report state, they never mutate it.
|
|
1554
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1555
|
+
function buildInspectorHandshakePayload() {
|
|
1556
|
+
return {
|
|
1557
|
+
// 2.2.0 (ARCH-1065): additive only — `bindingChain` on element data,
|
|
1558
|
+
// `QUERY_SLICE_FIELD` in the message protocol, and
|
|
1559
|
+
// `capabilities.bindingContract` below. No existing field changed
|
|
1560
|
+
// shape, so `protocolVersion` stays 1.
|
|
1561
|
+
// 2.3.0 (ARCH-1065 A17-support): additive only — `QUERY_COLLECTION_ORDER`
|
|
1562
|
+
// in the message protocol and `capabilities.collectionOrderQuery`
|
|
1563
|
+
// below. No existing field changed shape, so `protocolVersion` stays 1.
|
|
1564
|
+
// 2.4.0 (ARCH-883-KOI, CREW round): additive only — `origin` on
|
|
1565
|
+
// `EditorMeta`, the usage-projected `ownMetaNodeId`/`inspectorSource`/
|
|
1566
|
+
// `editorInstance` resolution, and `capabilities.kitOriginIdentity`
|
|
1567
|
+
// below. No existing field changed shape, so `protocolVersion` stays 1.
|
|
1568
|
+
// 2.5.0: additive only — `CONFIGURE_INSPECTOR` inbound (labels +
|
|
1569
|
+
// permissions), and the `CHAT_CONTEXT_REQUESTED` /
|
|
1570
|
+
// `ADD_COMMENT_REQUESTED` outbound pair behind the right-click menu
|
|
1571
|
+
// (`capabilities.contextMenuActions` below). No existing field
|
|
1572
|
+
// changed shape, so `protocolVersion` stays 1.
|
|
1573
|
+
// 2.6.0: additive only — `QUERY_CAPABILITIES` inbound, the
|
|
1574
|
+
// `CAPABILITIES_RESULT` outbound it answers with (also pushed
|
|
1575
|
+
// unsolicited on every editor-mode command), and
|
|
1576
|
+
// `capabilities.capabilitiesQuery` below. No existing field
|
|
1577
|
+
// changed shape, so `protocolVersion` stays 1.
|
|
1578
|
+
version: "2.6.0",
|
|
1579
|
+
protocolVersion: 1,
|
|
1580
|
+
capabilities: {
|
|
1581
|
+
// 2.1.1: function-valued __editorMeta props are invoked before any
|
|
1582
|
+
// postMessage, so meta payloads stay structured-cloneable. Hosts can
|
|
1583
|
+
// gate on this bit to confirm the deployed CDN artifact carries the
|
|
1584
|
+
// fix (the 2.1.0 URL is overwritten in place on publish).
|
|
1585
|
+
functionValuedEditorMeta: true,
|
|
1586
|
+
inspect: true,
|
|
1587
|
+
dndMove: true,
|
|
1588
|
+
dndResize: true,
|
|
1589
|
+
fiberInspectorSource: true,
|
|
1590
|
+
hostControlledHistory: true,
|
|
1591
|
+
arrangeStructuredHistory: true,
|
|
1592
|
+
arrangeStrongNodeIdentity: true,
|
|
1593
|
+
// Unified mode: SET_EDITOR_MODE "inspect" also enables the arrange
|
|
1594
|
+
// (design) runtime — selection and drag/resize coexist. Hosts MUST
|
|
1595
|
+
// feature-detect on this flag: a frozen 2.0.x inspector disables
|
|
1596
|
+
// design mode on "inspect" and needs the legacy "design" mode.
|
|
1597
|
+
unifiedInspectArrange: true,
|
|
1598
|
+
// ARCH-1065: the M2 codemod's data-agent-* grammar is readable
|
|
1599
|
+
// (`bindingChain` on element data) and slice-field-queryable
|
|
1600
|
+
// (`QUERY_SLICE_FIELD`). Hosts MUST feature-detect on this flag
|
|
1601
|
+
// before relying on either — a frozen pre-1065 inspector has
|
|
1602
|
+
// neither.
|
|
1603
|
+
collectionBinding: true,
|
|
1604
|
+
// ARCH-1065 A17-support (v2.3.0): `QUERY_COLLECTION_ORDER` resolves
|
|
1605
|
+
// the enclosing collection's order ("stable"/"derived") and row
|
|
1606
|
+
// ids/count for a target node, off the same data-agent-* grammar
|
|
1607
|
+
// as `collectionBinding`. Hosts MUST feature-detect on this flag —
|
|
1608
|
+
// a frozen pre-2.3.0 inspector has no handler for the message.
|
|
1609
|
+
collectionOrderQuery: true,
|
|
1610
|
+
// Host draw mode: the inspector can map a drawn rect to the
|
|
1611
|
+
// elements under it (`QUERY_ELEMENTS_IN_RECT` →
|
|
1612
|
+
// `ELEMENTS_IN_RECT_RESULT`) and rasterize its own viewport
|
|
1613
|
+
// (`CAPTURE_VIEWPORT` → `VIEWPORT_CAPTURED`). Hosts MUST
|
|
1614
|
+
// feature-detect: a frozen earlier inspector has no handler for
|
|
1615
|
+
// either message and would never reply.
|
|
1616
|
+
rectElementQuery: true,
|
|
1617
|
+
// Best-effort by construction — the foreignObject round-trip drops
|
|
1618
|
+
// web fonts, cross-origin images and <canvas> pixels, and answers
|
|
1619
|
+
// VIEWPORT_CAPTURE_FAILED when the rasterization throws.
|
|
1620
|
+
viewportCapture: true,
|
|
1621
|
+
// The BUILD-TIME contract version this specific page was stamped
|
|
1622
|
+
// with, forwarded verbatim from `<meta name="archie-binding-contract">`
|
|
1623
|
+
// when the kit build emitted one — null on any page built before
|
|
1624
|
+
// that meta tag existed, or that never carried collection-bound
|
|
1625
|
+
// data. Opaque to this script: validating/interpreting the value is
|
|
1626
|
+
// the host's job (mirrors how `slot` and `bindingContract` — plural
|
|
1627
|
+
// concept, singular mechanism — are treated as opaque strings
|
|
1628
|
+
// elsewhere in this file).
|
|
1629
|
+
bindingContract: readBindingContractMeta(),
|
|
1630
|
+
// ARCH-883-KOI F4 (v2.4.0) — this SCRIPT resolves `EditorMeta.origin`
|
|
1631
|
+
// and projects identity across an instrumented kit boundary
|
|
1632
|
+
// (`ownMetaNodeId`, `projectUsageMetaFromElement`,
|
|
1633
|
+
// `getInspectorInfoFromElement`, `resolveInstanceIdentity`). Hosts
|
|
1634
|
+
// MUST feature-detect on this flag before trusting `origin`
|
|
1635
|
+
// (absent or `editorMeta.origin === 'kit'`) as meaningful: a frozen
|
|
1636
|
+
// pre-2.4.0 inspector never reads the field at all, so its
|
|
1637
|
+
// `inspectorSource`/`editorInstance` on an instrumented-kit page
|
|
1638
|
+
// reproduce the exact F1 bug this version fixes.
|
|
1639
|
+
//
|
|
1640
|
+
// What this bit CANNOT tell the host: whether the PAGE's own build
|
|
1641
|
+
// emitted `origin` on its metas in the first place — that is the
|
|
1642
|
+
// `@archie/devtools` version the acg/kitgen esbuild path had
|
|
1643
|
+
// installed when the screen (or the kit) was built, a completely
|
|
1644
|
+
// separate axis from this CDN script's version (see the `origin`
|
|
1645
|
+
// field's docstring in `types/editor-meta.ts` for the full
|
|
1646
|
+
// reasoning, and why NO per-node signal can close that gap).
|
|
1647
|
+
kitOriginIdentity: true,
|
|
1648
|
+
// v2.5.0 — right-click menu in navigate AND inspect mode, emitting
|
|
1649
|
+
// `CHAT_CONTEXT_REQUESTED` (with the node's full metadata) and
|
|
1650
|
+
// `ADD_COMMENT_REQUESTED` (with the anchor target). Labels and the
|
|
1651
|
+
// chat-entry permission gate arrive via `CONFIGURE_INSPECTOR`.
|
|
1652
|
+
// Hosts MUST feature-detect: a frozen pre-2.5.0 inspector ignores
|
|
1653
|
+
// `CONFIGURE_INSPECTOR` and never emits either message.
|
|
1654
|
+
contextMenuActions: true,
|
|
1655
|
+
// v2.6.0 — the handshake is RECOVERABLE: `QUERY_CAPABILITIES`
|
|
1656
|
+
// answers with `CAPABILITIES_RESULT` carrying this exact payload,
|
|
1657
|
+
// and every `SET_EDITOR_MODE`/`SYNC_EDITOR_STATE` re-announces it
|
|
1658
|
+
// unsolicited. Hosts MUST feature-detect before WAITING on a
|
|
1659
|
+
// reply — a frozen pre-2.6.0 inspector has no handler for
|
|
1660
|
+
// `QUERY_CAPABILITIES` and never answers — but a host that lost
|
|
1661
|
+
// the one-shot handshake has no capabilities to detect ON, so
|
|
1662
|
+
// asking blindly (and treating silence as "still unknown", never
|
|
1663
|
+
// as "reported empty") is the correct posture.
|
|
1664
|
+
capabilitiesQuery: true,
|
|
1665
|
+
},
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/**
|
|
1670
|
+
* Emit the capabilities payload as `CAPABILITIES_RESULT`.
|
|
1671
|
+
*
|
|
1672
|
+
* `requestId` echoes the pull's correlation id, or is `null` for the
|
|
1673
|
+
* unsolicited push. `reason` names which route produced it, purely
|
|
1674
|
+
* for host-side tracing.
|
|
1675
|
+
*/
|
|
1676
|
+
function announceInspectorCapabilities(requestId, reason) {
|
|
1677
|
+
var handshake = buildInspectorHandshakePayload()
|
|
1678
|
+
postMessageToParent({
|
|
1679
|
+
type: "CAPABILITIES_RESULT",
|
|
1680
|
+
requestId:
|
|
1681
|
+
typeof requestId === "string" && requestId !== "" ? requestId : null,
|
|
1682
|
+
reason: typeof reason === "string" && reason !== "" ? reason : null,
|
|
1683
|
+
version: handshake.version,
|
|
1684
|
+
protocolVersion: handshake.protocolVersion,
|
|
1685
|
+
capabilities: handshake.capabilities,
|
|
1686
|
+
})
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
var initialHandshake = buildInspectorHandshakePayload()
|
|
1458
1690
|
postMessageToParent({
|
|
1459
1691
|
type: "INSPECTOR_SCRIPT_LOADED",
|
|
1460
1692
|
timestamp: Date.now(),
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
// shape, so `protocolVersion` stays 1.
|
|
1465
|
-
// 2.3.0 (ARCH-1065 A17-support): additive only — `QUERY_COLLECTION_ORDER`
|
|
1466
|
-
// in the message protocol and `capabilities.collectionOrderQuery`
|
|
1467
|
-
// below. No existing field changed shape, so `protocolVersion` stays 1.
|
|
1468
|
-
// 2.4.0 (ARCH-883-KOI, CREW round): additive only — `origin` on
|
|
1469
|
-
// `EditorMeta`, the usage-projected `ownMetaNodeId`/`inspectorSource`/
|
|
1470
|
-
// `editorInstance` resolution, and `capabilities.kitOriginIdentity`
|
|
1471
|
-
// below. No existing field changed shape, so `protocolVersion` stays 1.
|
|
1472
|
-
version: "2.4.0",
|
|
1473
|
-
protocolVersion: 1,
|
|
1474
|
-
capabilities: {
|
|
1475
|
-
// 2.1.1: function-valued __editorMeta props are invoked before any
|
|
1476
|
-
// postMessage, so meta payloads stay structured-cloneable. Hosts can
|
|
1477
|
-
// gate on this bit to confirm the deployed CDN artifact carries the
|
|
1478
|
-
// fix (the 2.1.0 URL is overwritten in place on publish).
|
|
1479
|
-
functionValuedEditorMeta: true,
|
|
1480
|
-
inspect: true,
|
|
1481
|
-
dndMove: true,
|
|
1482
|
-
dndResize: true,
|
|
1483
|
-
fiberInspectorSource: true,
|
|
1484
|
-
hostControlledHistory: true,
|
|
1485
|
-
arrangeStructuredHistory: true,
|
|
1486
|
-
arrangeStrongNodeIdentity: true,
|
|
1487
|
-
// Unified mode: SET_EDITOR_MODE "inspect" also enables the arrange
|
|
1488
|
-
// (design) runtime — selection and drag/resize coexist. Hosts MUST
|
|
1489
|
-
// feature-detect on this flag: a frozen 2.0.x inspector disables
|
|
1490
|
-
// design mode on "inspect" and needs the legacy "design" mode.
|
|
1491
|
-
unifiedInspectArrange: true,
|
|
1492
|
-
// ARCH-1065: the M2 codemod's data-agent-* grammar is readable
|
|
1493
|
-
// (`bindingChain` on element data) and slice-field-queryable
|
|
1494
|
-
// (`QUERY_SLICE_FIELD`). Hosts MUST feature-detect on this flag
|
|
1495
|
-
// before relying on either — a frozen pre-1065 inspector has
|
|
1496
|
-
// neither.
|
|
1497
|
-
collectionBinding: true,
|
|
1498
|
-
// ARCH-1065 A17-support (v2.3.0): `QUERY_COLLECTION_ORDER` resolves
|
|
1499
|
-
// the enclosing collection's order ("stable"/"derived") and row
|
|
1500
|
-
// ids/count for a target node, off the same data-agent-* grammar
|
|
1501
|
-
// as `collectionBinding`. Hosts MUST feature-detect on this flag —
|
|
1502
|
-
// a frozen pre-2.3.0 inspector has no handler for the message.
|
|
1503
|
-
collectionOrderQuery: true,
|
|
1504
|
-
// The BUILD-TIME contract version this specific page was stamped
|
|
1505
|
-
// with, forwarded verbatim from `<meta name="archie-binding-contract">`
|
|
1506
|
-
// when the kit build emitted one — null on any page built before
|
|
1507
|
-
// that meta tag existed, or that never carried collection-bound
|
|
1508
|
-
// data. Opaque to this script: validating/interpreting the value is
|
|
1509
|
-
// the host's job (mirrors how `slot` and `bindingContract` — plural
|
|
1510
|
-
// concept, singular mechanism — are treated as opaque strings
|
|
1511
|
-
// elsewhere in this file).
|
|
1512
|
-
bindingContract: readBindingContractMeta(),
|
|
1513
|
-
// ARCH-883-KOI F4 (v2.4.0) — this SCRIPT resolves `EditorMeta.origin`
|
|
1514
|
-
// and projects identity across an instrumented kit boundary
|
|
1515
|
-
// (`ownMetaNodeId`, `projectUsageMetaFromElement`,
|
|
1516
|
-
// `getInspectorInfoFromElement`, `resolveInstanceIdentity`). Hosts
|
|
1517
|
-
// MUST feature-detect on this flag before trusting `origin`
|
|
1518
|
-
// (absent or `editorMeta.origin === 'kit'`) as meaningful: a frozen
|
|
1519
|
-
// pre-2.4.0 inspector never reads the field at all, so its
|
|
1520
|
-
// `inspectorSource`/`editorInstance` on an instrumented-kit page
|
|
1521
|
-
// reproduce the exact F1 bug this version fixes.
|
|
1522
|
-
//
|
|
1523
|
-
// What this bit CANNOT tell the host: whether the PAGE's own build
|
|
1524
|
-
// emitted `origin` on its metas in the first place — that is the
|
|
1525
|
-
// `@archie/devtools` version the acg/kitgen esbuild path had
|
|
1526
|
-
// installed when the screen (or the kit) was built, a completely
|
|
1527
|
-
// separate axis from this CDN script's version (see the `origin`
|
|
1528
|
-
// field's docstring in `types/editor-meta.ts` for the full
|
|
1529
|
-
// reasoning, and why NO per-node signal can close that gap).
|
|
1530
|
-
kitOriginIdentity: true,
|
|
1531
|
-
},
|
|
1693
|
+
version: initialHandshake.version,
|
|
1694
|
+
protocolVersion: initialHandshake.protocolVersion,
|
|
1695
|
+
capabilities: initialHandshake.capabilities,
|
|
1532
1696
|
})
|
|
1533
1697
|
|
|
1534
1698
|
if (window._inspectorMessageHandler) {
|
|
@@ -1618,6 +1782,10 @@
|
|
|
1618
1782
|
element === selectedElementBadge ||
|
|
1619
1783
|
element.id === "inspector-ping-effect" ||
|
|
1620
1784
|
(element.closest && element.closest("[data-inspector-badge]")) ||
|
|
1785
|
+
// Right-click menu: its buttons must survive setupEventBlockers'
|
|
1786
|
+
// "click" blocker and never be treated as an inspect target.
|
|
1787
|
+
(element.closest &&
|
|
1788
|
+
element.closest("[data-inspector-context-menu]")) ||
|
|
1621
1789
|
// Design-mode (arrange) overlay elements: HitArea, resize handles,
|
|
1622
1790
|
// drop-line, etc. These belong to the unified visual editor and must
|
|
1623
1791
|
// pass mousedown/click events through to React (dnd-kit) instead of
|
|
@@ -2152,6 +2320,15 @@
|
|
|
2152
2320
|
}
|
|
2153
2321
|
|
|
2154
2322
|
function setEditorModeFromHost(mode, options) {
|
|
2323
|
+
// PUSH route of the recoverable handshake (see
|
|
2324
|
+
// `buildInspectorHandshakePayload`). Emitted BEFORE the
|
|
2325
|
+
// `EDITOR_MODE_CHANGED` echo below — deliberately, not incidentally:
|
|
2326
|
+
// a host that lost the one-shot `INSPECTOR_SCRIPT_LOADED` refuses
|
|
2327
|
+
// every inbound message until it knows who it is talking to, so the
|
|
2328
|
+
// capabilities have to land FIRST or the very `EDITOR_MODE_CHANGED`
|
|
2329
|
+
// this command exists to confirm would be the message dropped.
|
|
2330
|
+
announceInspectorCapabilities(null, "editor-mode")
|
|
2331
|
+
|
|
2155
2332
|
var historyMode =
|
|
2156
2333
|
options && options.hostControlledHistory ? "host" : "local"
|
|
2157
2334
|
|
|
@@ -2658,9 +2835,338 @@
|
|
|
2658
2835
|
} catch (e) {}
|
|
2659
2836
|
}
|
|
2660
2837
|
|
|
2838
|
+
veTrace("resolve-mutation-element", {
|
|
2839
|
+
selector: hasSelector ? selector : null,
|
|
2840
|
+
inspectorSource:
|
|
2841
|
+
inspectorSource && typeof inspectorSource === "object"
|
|
2842
|
+
? (inspectorSource.file || "") +
|
|
2843
|
+
":" +
|
|
2844
|
+
(inspectorSource.line || "") +
|
|
2845
|
+
":" +
|
|
2846
|
+
(inspectorSource.column || "")
|
|
2847
|
+
: null,
|
|
2848
|
+
matchedSelector: !!selectorElement,
|
|
2849
|
+
resolvedVia: !element
|
|
2850
|
+
? "none"
|
|
2851
|
+
: element === selectorElement
|
|
2852
|
+
? "selector"
|
|
2853
|
+
: "inspector-source-or-fallback",
|
|
2854
|
+
resolvedNodeId: element
|
|
2855
|
+
? element.getAttribute("data-dnd-node-id") || null
|
|
2856
|
+
: null,
|
|
2857
|
+
resolvedSourceRef: element
|
|
2858
|
+
? element.getAttribute("data-archie-source-ref") || null
|
|
2859
|
+
: null,
|
|
2860
|
+
resolvedTag: element ? String(element.tagName || "").toLowerCase() : null,
|
|
2861
|
+
})
|
|
2862
|
+
|
|
2661
2863
|
return element
|
|
2662
2864
|
}
|
|
2663
2865
|
|
|
2866
|
+
// ── Annotation support (host draw mode) ──────────────────────────────
|
|
2867
|
+
// Two OPTIONAL queries the host uses to turn a drawing over the preview
|
|
2868
|
+
// into actionable context. Both are advertised as capabilities
|
|
2869
|
+
// (`rectElementQuery`, `viewportCapture`); a host that sees neither
|
|
2870
|
+
// degrades to sending the raw drawing, so failures here must never
|
|
2871
|
+
// throw into the shared message handler.
|
|
2872
|
+
|
|
2873
|
+
var ANNOTATION_MAX_ELEMENTS = 8
|
|
2874
|
+
var ANNOTATION_CAPTURE_TIMEOUT_MS = 5000
|
|
2875
|
+
var ANNOTATION_MIN_SIDE_PX = 4
|
|
2876
|
+
var ANNOTATION_SKIP_TAGS = {
|
|
2877
|
+
HTML: true,
|
|
2878
|
+
HEAD: true,
|
|
2879
|
+
SCRIPT: true,
|
|
2880
|
+
STYLE: true,
|
|
2881
|
+
META: true,
|
|
2882
|
+
LINK: true,
|
|
2883
|
+
TITLE: true,
|
|
2884
|
+
BR: true,
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
function annotationIntersection(a, b) {
|
|
2888
|
+
var x = Math.max(a.left, b.x)
|
|
2889
|
+
var y = Math.max(a.top, b.y)
|
|
2890
|
+
var right = Math.min(a.right, b.x + b.width)
|
|
2891
|
+
var bottom = Math.min(a.bottom, b.y + b.height)
|
|
2892
|
+
if (right <= x || bottom <= y) return 0
|
|
2893
|
+
return (right - x) * (bottom - y)
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
/**
|
|
2897
|
+
* Elements intersecting a viewport-relative rect, most specific first.
|
|
2898
|
+
*
|
|
2899
|
+
* "Most specific" is deliberately NOT smallest-area: a mark drawn around
|
|
2900
|
+
* a card should report the card, not the innermost text node inside it.
|
|
2901
|
+
* Ranking therefore puts nodes carrying an inspector source (i.e. ones
|
|
2902
|
+
* the agent can actually edit) ahead of anonymous wrappers, then favours
|
|
2903
|
+
* higher coverage of the drawn rect.
|
|
2904
|
+
*/
|
|
2905
|
+
function collectElementsInRect(rect) {
|
|
2906
|
+
if (
|
|
2907
|
+
!rect ||
|
|
2908
|
+
typeof rect.x !== "number" ||
|
|
2909
|
+
typeof rect.y !== "number" ||
|
|
2910
|
+
!(rect.width > 0) ||
|
|
2911
|
+
!(rect.height > 0)
|
|
2912
|
+
) {
|
|
2913
|
+
return []
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
var all = _rawDocumentQuerySelectorAll.call(document, "body *")
|
|
2917
|
+
var scored = []
|
|
2918
|
+
|
|
2919
|
+
for (var i = 0; i < all.length; i++) {
|
|
2920
|
+
var el = all[i]
|
|
2921
|
+
if (ANNOTATION_SKIP_TAGS[el.tagName]) continue
|
|
2922
|
+
if (el.closest && el.closest("[data-inspector-badge]")) continue
|
|
2923
|
+
|
|
2924
|
+
var box = el.getBoundingClientRect()
|
|
2925
|
+
if (
|
|
2926
|
+
box.width < ANNOTATION_MIN_SIDE_PX ||
|
|
2927
|
+
box.height < ANNOTATION_MIN_SIDE_PX
|
|
2928
|
+
) {
|
|
2929
|
+
continue
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
var overlap = annotationIntersection(box, rect)
|
|
2933
|
+
if (overlap <= 0) continue
|
|
2934
|
+
|
|
2935
|
+
var style = window.getComputedStyle(el)
|
|
2936
|
+
if (
|
|
2937
|
+
style.visibility === "hidden" ||
|
|
2938
|
+
style.display === "none" ||
|
|
2939
|
+
style.opacity === "0"
|
|
2940
|
+
) {
|
|
2941
|
+
continue
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
var elementArea = box.width * box.height
|
|
2945
|
+
var rectArea = rect.width * rect.height
|
|
2946
|
+
// Skip nodes the mark barely grazes, and page-sized wrappers the
|
|
2947
|
+
// mark covers a negligible fraction of.
|
|
2948
|
+
if (overlap / elementArea < 0.25 && overlap / rectArea < 0.5) continue
|
|
2949
|
+
|
|
2950
|
+
var info = null
|
|
2951
|
+
try {
|
|
2952
|
+
info = getInspectorInfoFromElement(el)
|
|
2953
|
+
} catch (error) {
|
|
2954
|
+
info = null
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
scored.push({
|
|
2958
|
+
element: el,
|
|
2959
|
+
info: info,
|
|
2960
|
+
hasSource: info && info.file ? 1 : 0,
|
|
2961
|
+
coverage: overlap / rectArea,
|
|
2962
|
+
})
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
scored.sort(function (a, b) {
|
|
2966
|
+
if (a.hasSource !== b.hasSource) return b.hasSource - a.hasSource
|
|
2967
|
+
return b.coverage - a.coverage
|
|
2968
|
+
})
|
|
2969
|
+
|
|
2970
|
+
var result = []
|
|
2971
|
+
var seenSelectors = {}
|
|
2972
|
+
for (var j = 0; j < scored.length; j++) {
|
|
2973
|
+
if (result.length >= ANNOTATION_MAX_ELEMENTS) break
|
|
2974
|
+
var entry = scored[j]
|
|
2975
|
+
var selector = getStableSelector(entry.element)
|
|
2976
|
+
if (!selector || seenSelectors[selector]) continue
|
|
2977
|
+
seenSelectors[selector] = true
|
|
2978
|
+
|
|
2979
|
+
var box2 = entry.element.getBoundingClientRect()
|
|
2980
|
+
var text = entry.element.textContent
|
|
2981
|
+
result.push({
|
|
2982
|
+
cssSelector: selector,
|
|
2983
|
+
tagName: String(entry.element.tagName || "").toLowerCase(),
|
|
2984
|
+
textPreview: text ? text.trim().slice(0, 120) : "",
|
|
2985
|
+
sourceRef:
|
|
2986
|
+
entry.info && entry.info.file
|
|
2987
|
+
? entry.info.file +
|
|
2988
|
+
":" +
|
|
2989
|
+
(entry.info.line || 0) +
|
|
2990
|
+
":" +
|
|
2991
|
+
(entry.info.column || 0)
|
|
2992
|
+
: undefined,
|
|
2993
|
+
componentName:
|
|
2994
|
+
entry.info && entry.info.componentName
|
|
2995
|
+
? entry.info.componentName
|
|
2996
|
+
: undefined,
|
|
2997
|
+
boundingRect: {
|
|
2998
|
+
x: box2.x,
|
|
2999
|
+
y: box2.y,
|
|
3000
|
+
width: box2.width,
|
|
3001
|
+
height: box2.height,
|
|
3002
|
+
},
|
|
3003
|
+
})
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
return result
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
/** Same-origin CSS text, concatenated. Cross-origin sheets throw on
|
|
3010
|
+
* `cssRules` and are skipped — their styling is simply absent from the
|
|
3011
|
+
* capture, which is why the host treats it as best-effort. */
|
|
3012
|
+
function collectSameOriginCss() {
|
|
3013
|
+
var chunks = []
|
|
3014
|
+
for (var i = 0; i < document.styleSheets.length; i++) {
|
|
3015
|
+
var sheet = document.styleSheets[i]
|
|
3016
|
+
var rules = null
|
|
3017
|
+
try {
|
|
3018
|
+
rules = sheet.cssRules
|
|
3019
|
+
} catch (error) {
|
|
3020
|
+
continue
|
|
3021
|
+
}
|
|
3022
|
+
if (!rules) continue
|
|
3023
|
+
for (var j = 0; j < rules.length; j++) {
|
|
3024
|
+
chunks.push(rules[j].cssText)
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
return chunks.join("\n")
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
function fetchAsDataUrl(url) {
|
|
3031
|
+
return fetch(url, { mode: "same-origin" })
|
|
3032
|
+
.then(function (response) {
|
|
3033
|
+
if (!response.ok) throw new Error("bad status")
|
|
3034
|
+
return response.blob()
|
|
3035
|
+
})
|
|
3036
|
+
.then(function (blob) {
|
|
3037
|
+
return new Promise(function (resolve, reject) {
|
|
3038
|
+
var reader = new FileReader()
|
|
3039
|
+
reader.onload = function () {
|
|
3040
|
+
resolve(String(reader.result))
|
|
3041
|
+
}
|
|
3042
|
+
reader.onerror = reject
|
|
3043
|
+
reader.readAsDataURL(blob)
|
|
3044
|
+
})
|
|
3045
|
+
})
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
/** Rasterizing an SVG cannot fetch subresources, so same-origin <img>
|
|
3049
|
+
* sources are inlined first. Failures leave the image blank rather
|
|
3050
|
+
* than aborting the whole capture. */
|
|
3051
|
+
function inlineImages(root) {
|
|
3052
|
+
var images = root.querySelectorAll("img")
|
|
3053
|
+
var jobs = []
|
|
3054
|
+
for (var i = 0; i < images.length; i++) {
|
|
3055
|
+
;(function (img) {
|
|
3056
|
+
var src = img.getAttribute("src")
|
|
3057
|
+
if (!src || src.indexOf("data:") === 0) return
|
|
3058
|
+
var absolute
|
|
3059
|
+
try {
|
|
3060
|
+
absolute = new URL(src, document.baseURI)
|
|
3061
|
+
} catch (error) {
|
|
3062
|
+
return
|
|
3063
|
+
}
|
|
3064
|
+
if (absolute.origin !== window.location.origin) return
|
|
3065
|
+
jobs.push(
|
|
3066
|
+
fetchAsDataUrl(absolute.href)
|
|
3067
|
+
.then(function (dataUrl) {
|
|
3068
|
+
img.setAttribute("src", dataUrl)
|
|
3069
|
+
})
|
|
3070
|
+
.catch(function () {
|
|
3071
|
+
img.removeAttribute("src")
|
|
3072
|
+
})
|
|
3073
|
+
)
|
|
3074
|
+
})(images[i])
|
|
3075
|
+
}
|
|
3076
|
+
return Promise.all(jobs)
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
/**
|
|
3080
|
+
* Rasterizes the CURRENT viewport into a PNG data URL via an
|
|
3081
|
+
* `<svg><foreignObject>` round-trip. Best-effort by construction: web
|
|
3082
|
+
* fonts, cross-origin images and <canvas> pixels do not survive it. The
|
|
3083
|
+
* host composes the result with the user's drawing, and falls back to
|
|
3084
|
+
* the drawing alone when this rejects.
|
|
3085
|
+
*/
|
|
3086
|
+
function captureViewportDataUrl(pixelRatio) {
|
|
3087
|
+
var width = window.innerWidth
|
|
3088
|
+
var height = window.innerHeight
|
|
3089
|
+
var ratio = Math.min(Math.max(Number(pixelRatio) || 1, 1), 2)
|
|
3090
|
+
|
|
3091
|
+
var clone = document.documentElement.cloneNode(true)
|
|
3092
|
+
var strip = clone.querySelectorAll(
|
|
3093
|
+
"script, noscript, [data-inspector-badge], #archie-inspector-styles"
|
|
3094
|
+
)
|
|
3095
|
+
for (var i = 0; i < strip.length; i++) {
|
|
3096
|
+
if (strip[i].parentNode) strip[i].parentNode.removeChild(strip[i])
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
var styleEl = document.createElement("style")
|
|
3100
|
+
styleEl.textContent = collectSameOriginCss()
|
|
3101
|
+
var head = clone.querySelector("head")
|
|
3102
|
+
if (head) head.appendChild(styleEl)
|
|
3103
|
+
else clone.insertBefore(styleEl, clone.firstChild)
|
|
3104
|
+
|
|
3105
|
+
return inlineImages(clone).then(function () {
|
|
3106
|
+
var markup = new XMLSerializer().serializeToString(clone)
|
|
3107
|
+
var svg =
|
|
3108
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="' +
|
|
3109
|
+
width +
|
|
3110
|
+
'" height="' +
|
|
3111
|
+
height +
|
|
3112
|
+
'">' +
|
|
3113
|
+
'<foreignObject x="0" y="0" width="' +
|
|
3114
|
+
width +
|
|
3115
|
+
'" height="' +
|
|
3116
|
+
height +
|
|
3117
|
+
'">' +
|
|
3118
|
+
'<div xmlns="http://www.w3.org/1999/xhtml" style="width:' +
|
|
3119
|
+
width +
|
|
3120
|
+
"px;transform:translate(" +
|
|
3121
|
+
-window.scrollX +
|
|
3122
|
+
"px," +
|
|
3123
|
+
-window.scrollY +
|
|
3124
|
+
'px)">' +
|
|
3125
|
+
markup +
|
|
3126
|
+
"</div></foreignObject></svg>"
|
|
3127
|
+
|
|
3128
|
+
return new Promise(function (resolve, reject) {
|
|
3129
|
+
var image = new Image()
|
|
3130
|
+
// An SVG the browser refuses to decode can fire NEITHER onload nor
|
|
3131
|
+
// onerror (malformed serialization, blocked subresource). Without
|
|
3132
|
+
// this guard the host would sit on the request until its own
|
|
3133
|
+
// timeout with no diagnostic; the deadline turns that into an
|
|
3134
|
+
// explicit VIEWPORT_CAPTURE_FAILED.
|
|
3135
|
+
var settled = false
|
|
3136
|
+
var deadline = setTimeout(function () {
|
|
3137
|
+
if (settled) return
|
|
3138
|
+
settled = true
|
|
3139
|
+
reject(new Error("svg rasterization timed out"))
|
|
3140
|
+
}, ANNOTATION_CAPTURE_TIMEOUT_MS)
|
|
3141
|
+
var finish = function (fn, value) {
|
|
3142
|
+
if (settled) return
|
|
3143
|
+
settled = true
|
|
3144
|
+
clearTimeout(deadline)
|
|
3145
|
+
fn(value)
|
|
3146
|
+
}
|
|
3147
|
+
image.onload = function () {
|
|
3148
|
+
try {
|
|
3149
|
+
var canvas = document.createElement("canvas")
|
|
3150
|
+
canvas.width = Math.round(width * ratio)
|
|
3151
|
+
canvas.height = Math.round(height * ratio)
|
|
3152
|
+
var context = canvas.getContext("2d")
|
|
3153
|
+
context.fillStyle = "#ffffff"
|
|
3154
|
+
context.fillRect(0, 0, canvas.width, canvas.height)
|
|
3155
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height)
|
|
3156
|
+
finish(resolve, canvas.toDataURL("image/png"))
|
|
3157
|
+
} catch (error) {
|
|
3158
|
+
finish(reject, error)
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
image.onerror = function () {
|
|
3162
|
+
finish(reject, new Error("svg rasterization failed"))
|
|
3163
|
+
}
|
|
3164
|
+
image.src =
|
|
3165
|
+
"data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg)
|
|
3166
|
+
})
|
|
3167
|
+
})
|
|
3168
|
+
}
|
|
3169
|
+
|
|
2664
3170
|
function extractMetadata(element) {
|
|
2665
3171
|
if (!element) return null
|
|
2666
3172
|
|
|
@@ -3207,6 +3713,10 @@
|
|
|
3207
3713
|
return
|
|
3208
3714
|
}
|
|
3209
3715
|
|
|
3716
|
+
veTrace("runtime-event:" + detail.type, {
|
|
3717
|
+
operationId: detail.operationId || null,
|
|
3718
|
+
})
|
|
3719
|
+
|
|
3210
3720
|
if (detail.type === "design-mode-changed") {
|
|
3211
3721
|
// In unified inspect the arrange overlay flips its internal
|
|
3212
3722
|
// design-mode store as a side effect ("design" on mount, "off" on
|
|
@@ -3216,6 +3726,9 @@
|
|
|
3216
3726
|
// only needs runtime-originated mode changes in the legacy
|
|
3217
3727
|
// design-only flow.
|
|
3218
3728
|
if (hostCommandedEditorMode === "inspect") {
|
|
3729
|
+
veTrace("runtime-event:design-mode-changed:suppressed", {
|
|
3730
|
+
hostCommandedEditorMode: hostCommandedEditorMode,
|
|
3731
|
+
})
|
|
3219
3732
|
return
|
|
3220
3733
|
}
|
|
3221
3734
|
postMessageToParent({
|
|
@@ -3304,9 +3817,28 @@
|
|
|
3304
3817
|
event.data.type,
|
|
3305
3818
|
event.data
|
|
3306
3819
|
)
|
|
3820
|
+
if (event.data.type !== "EXTERNAL_DRAG_MOVE") {
|
|
3821
|
+
veTrace("in:" + event.data.type, {
|
|
3822
|
+
requestId: event.data.requestId || null,
|
|
3823
|
+
selector: event.data.selector || null,
|
|
3824
|
+
})
|
|
3825
|
+
}
|
|
3307
3826
|
}
|
|
3308
3827
|
|
|
3309
|
-
if (event.data && event.data.type === "
|
|
3828
|
+
if (event.data && event.data.type === "CONFIGURE_INSPECTOR") {
|
|
3829
|
+
// Origin adoption already happened above; this carries the
|
|
3830
|
+
// right-click menu's i18n labels and permission gate.
|
|
3831
|
+
applyInspectorConfiguration(event.data)
|
|
3832
|
+
} else if (event.data && event.data.type === "QUERY_CAPABILITIES") {
|
|
3833
|
+
// PULL route of the recoverable handshake (v2.6.0). Idempotent and
|
|
3834
|
+
// side-effect free: it reports the SAME payload
|
|
3835
|
+
// `INSPECTOR_SCRIPT_LOADED` carried, rebuilt from the one builder,
|
|
3836
|
+
// so a host that lost the one-shot announcement can recover it on
|
|
3837
|
+
// demand instead of living forever with "capabilities unknown".
|
|
3838
|
+
// Mirrors QUERY_SLICE_FIELD / QUERY_COLLECTION_ORDER's standalone
|
|
3839
|
+
// request/response envelope (`requestId` echoed on the reply).
|
|
3840
|
+
announceInspectorCapabilities(event.data.requestId, "query")
|
|
3841
|
+
} else if (event.data && event.data.type === "SET_EDITOR_MODE") {
|
|
3310
3842
|
setEditorModeFromHost(event.data.mode || "off", {
|
|
3311
3843
|
hostControlledHistory: event.data.hostControlledHistory !== false,
|
|
3312
3844
|
theme: event.data.theme || null,
|
|
@@ -3479,6 +4011,59 @@
|
|
|
3479
4011
|
},
|
|
3480
4012
|
})
|
|
3481
4013
|
}
|
|
4014
|
+
} else if (
|
|
4015
|
+
event.data &&
|
|
4016
|
+
event.data.type === "QUERY_ELEMENTS_IN_RECT"
|
|
4017
|
+
) {
|
|
4018
|
+
var rectRequestId = event.data.requestId
|
|
4019
|
+
var queryElements = []
|
|
4020
|
+
try {
|
|
4021
|
+
queryElements = collectElementsInRect(event.data.rect)
|
|
4022
|
+
var rectLimit = Number(event.data.limit)
|
|
4023
|
+
if (rectLimit > 0 && queryElements.length > rectLimit) {
|
|
4024
|
+
queryElements = queryElements.slice(0, rectLimit)
|
|
4025
|
+
}
|
|
4026
|
+
} catch (error) {
|
|
4027
|
+
inspectorWarn("[Inspector] QUERY_ELEMENTS_IN_RECT failed", error)
|
|
4028
|
+
queryElements = []
|
|
4029
|
+
}
|
|
4030
|
+
// Always answers — an empty list is a valid result, and the host's
|
|
4031
|
+
// pending request would otherwise sit until its timeout.
|
|
4032
|
+
postMessageToParent({
|
|
4033
|
+
type: "ELEMENTS_IN_RECT_RESULT",
|
|
4034
|
+
requestId: rectRequestId,
|
|
4035
|
+
elements: queryElements,
|
|
4036
|
+
})
|
|
4037
|
+
} else if (event.data && event.data.type === "CAPTURE_VIEWPORT") {
|
|
4038
|
+
var captureRequestId = event.data.requestId
|
|
4039
|
+
try {
|
|
4040
|
+
captureViewportDataUrl(event.data.pixelRatio)
|
|
4041
|
+
.then(function (dataUrl) {
|
|
4042
|
+
postMessageToParent({
|
|
4043
|
+
type: "VIEWPORT_CAPTURED",
|
|
4044
|
+
requestId: captureRequestId,
|
|
4045
|
+
dataUrl: dataUrl,
|
|
4046
|
+
width: window.innerWidth,
|
|
4047
|
+
height: window.innerHeight,
|
|
4048
|
+
scrollX: window.scrollX,
|
|
4049
|
+
scrollY: window.scrollY,
|
|
4050
|
+
})
|
|
4051
|
+
})
|
|
4052
|
+
.catch(function (error) {
|
|
4053
|
+
inspectorWarn("[Inspector] CAPTURE_VIEWPORT failed", error)
|
|
4054
|
+
postMessageToParent({
|
|
4055
|
+
type: "VIEWPORT_CAPTURE_FAILED",
|
|
4056
|
+
requestId: captureRequestId,
|
|
4057
|
+
error: String((error && error.message) || error),
|
|
4058
|
+
})
|
|
4059
|
+
})
|
|
4060
|
+
} catch (error) {
|
|
4061
|
+
postMessageToParent({
|
|
4062
|
+
type: "VIEWPORT_CAPTURE_FAILED",
|
|
4063
|
+
requestId: captureRequestId,
|
|
4064
|
+
error: String((error && error.message) || error),
|
|
4065
|
+
})
|
|
4066
|
+
}
|
|
3482
4067
|
} else if (event.data && event.data.type === "APPLY_STYLE_CHANGE") {
|
|
3483
4068
|
const { selector, property, value } = event.data
|
|
3484
4069
|
|
|
@@ -4393,8 +4978,289 @@
|
|
|
4393
4978
|
}
|
|
4394
4979
|
}
|
|
4395
4980
|
|
|
4981
|
+
// ── Right-click context menu ────────────────────────────────────────
|
|
4982
|
+
// Two entries — pin the node as chat context, or open a comment
|
|
4983
|
+
// anchored to it. Active in plain navigate mode as well as inspect, so
|
|
4984
|
+
// the listeners are registered here at load time: setupEventBlockers()
|
|
4985
|
+
// blocks "contextmenu" with stopImmediatePropagation once inspection
|
|
4986
|
+
// starts, and only a listener registered earlier in the capture phase
|
|
4987
|
+
// still runs.
|
|
4988
|
+
|
|
4989
|
+
var inspectorLabels = null
|
|
4990
|
+
var inspectorPermissions = { sendToChat: true }
|
|
4991
|
+
var contextMenuEl = null
|
|
4992
|
+
var contextMenuChatButton = null
|
|
4993
|
+
var contextMenuCommentButton = null
|
|
4994
|
+
var contextMenuTarget = null
|
|
4995
|
+
// Snapshotted when the menu opens: the comment anchor needs the
|
|
4996
|
+
// right-click POINT, which is gone by the time the entry is activated.
|
|
4997
|
+
var contextMenuTargetPayload = null
|
|
4998
|
+
|
|
4999
|
+
function getSendToChatLabel() {
|
|
5000
|
+
var configured =
|
|
5001
|
+
inspectorLabels &&
|
|
5002
|
+
inspectorLabels.badge &&
|
|
5003
|
+
typeof inspectorLabels.badge.sendToChat === "string"
|
|
5004
|
+
? inspectorLabels.badge.sendToChat.trim()
|
|
5005
|
+
: ""
|
|
5006
|
+
return configured || "Send to chat"
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
function getAddCommentLabel() {
|
|
5010
|
+
var configured =
|
|
5011
|
+
inspectorLabels &&
|
|
5012
|
+
inspectorLabels.contextMenu &&
|
|
5013
|
+
typeof inspectorLabels.contextMenu.addComment === "string"
|
|
5014
|
+
? inspectorLabels.contextMenu.addComment.trim()
|
|
5015
|
+
: ""
|
|
5016
|
+
return configured || "Add a comment"
|
|
5017
|
+
}
|
|
5018
|
+
|
|
5019
|
+
// CONFIGURE_INSPECTOR payload — i18n labels plus the host's permission
|
|
5020
|
+
// gate for the chat entry (a viewer who can't send chat messages gets
|
|
5021
|
+
// the comment entry only).
|
|
5022
|
+
function applyInspectorConfiguration(data) {
|
|
5023
|
+
if (data && data.labels && typeof data.labels === "object") {
|
|
5024
|
+
inspectorLabels = data.labels
|
|
5025
|
+
}
|
|
5026
|
+
if (data && data.permissions && typeof data.permissions === "object") {
|
|
5027
|
+
inspectorPermissions = {
|
|
5028
|
+
sendToChat: data.permissions.sendToChat !== false,
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
syncContextMenuLabels()
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
function syncContextMenuLabels() {
|
|
5035
|
+
if (contextMenuChatButton) {
|
|
5036
|
+
contextMenuChatButton.textContent = getSendToChatLabel()
|
|
5037
|
+
contextMenuChatButton.style.display = inspectorPermissions.sendToChat
|
|
5038
|
+
? "block"
|
|
5039
|
+
: "none"
|
|
5040
|
+
}
|
|
5041
|
+
if (contextMenuCommentButton) {
|
|
5042
|
+
contextMenuCommentButton.textContent = getAddCommentLabel()
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
|
|
5046
|
+
function hideSelectionContextMenu() {
|
|
5047
|
+
if (contextMenuEl) contextMenuEl.style.display = "none"
|
|
5048
|
+
}
|
|
5049
|
+
|
|
5050
|
+
function ensureSelectionContextMenu() {
|
|
5051
|
+
if (contextMenuEl && document.contains(contextMenuEl)) {
|
|
5052
|
+
return contextMenuEl
|
|
5053
|
+
}
|
|
5054
|
+
// A previous run's node (or one orphaned by a body wipe) would
|
|
5055
|
+
// otherwise linger with dead click handlers.
|
|
5056
|
+
var orphan = document.getElementById("__archie-inspector-context-menu__")
|
|
5057
|
+
if (orphan && orphan.parentNode) orphan.parentNode.removeChild(orphan)
|
|
5058
|
+
|
|
5059
|
+
var menu = document.createElement("div")
|
|
5060
|
+
menu.id = "__archie-inspector-context-menu__"
|
|
5061
|
+
menu.setAttribute("data-inspector-context-menu", "true")
|
|
5062
|
+
menu.style.cssText =
|
|
5063
|
+
"position:fixed;display:none;z-index:2147483647;box-sizing:border-box;min-width:136px;padding:4px;border:1px solid rgba(15,23,42,0.16);border-radius:6px;background:#fff;box-shadow:0 10px 30px rgba(15,23,42,0.18);font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;"
|
|
5064
|
+
|
|
5065
|
+
function createMenuButton(labelText, onActivate) {
|
|
5066
|
+
var button = document.createElement("button")
|
|
5067
|
+
button.type = "button"
|
|
5068
|
+
button.textContent = labelText
|
|
5069
|
+
button.style.cssText =
|
|
5070
|
+
"display:block;width:100%;border:0;border-radius:4px;background:transparent;padding:6px 8px;text-align:left;color:#0f172a;font-size:12px;line-height:16px;cursor:pointer;"
|
|
5071
|
+
button.addEventListener("mouseenter", function () {
|
|
5072
|
+
button.style.background = inspectorTheme.accentSoft
|
|
5073
|
+
})
|
|
5074
|
+
button.addEventListener("mouseleave", function () {
|
|
5075
|
+
button.style.background = "transparent"
|
|
5076
|
+
})
|
|
5077
|
+
button.addEventListener("click", function (event) {
|
|
5078
|
+
event.preventDefault()
|
|
5079
|
+
event.stopPropagation()
|
|
5080
|
+
event.stopImmediatePropagation()
|
|
5081
|
+
try {
|
|
5082
|
+
onActivate()
|
|
5083
|
+
} catch (err) {
|
|
5084
|
+
inspectorWarn("[archie-inspector] context menu action failed", err)
|
|
5085
|
+
}
|
|
5086
|
+
hideSelectionContextMenu()
|
|
5087
|
+
})
|
|
5088
|
+
return button
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
contextMenuChatButton = createMenuButton(
|
|
5092
|
+
getSendToChatLabel(),
|
|
5093
|
+
function () {
|
|
5094
|
+
if (!contextMenuTarget) return
|
|
5095
|
+
// TWO messages on purpose — the host splits this request in two
|
|
5096
|
+
// and neither half alone is visible:
|
|
5097
|
+
//
|
|
5098
|
+
// bare -> arms the editor surface ("inspect"), which is
|
|
5099
|
+
// what makes the chat's source bar render at all.
|
|
5100
|
+
// + metadata-> pins the node as the chat context and opens the
|
|
5101
|
+
// chat, but leaves the surface untouched.
|
|
5102
|
+
//
|
|
5103
|
+
// Sending only the metadata one pinned an element behind a bar
|
|
5104
|
+
// that was never rendered, which is why the entry looked dead.
|
|
5105
|
+
// The bare half is skipped while inspecting so it can't yank an
|
|
5106
|
+
// open visual-editor session into source-selection mode.
|
|
5107
|
+
if (!isInspecting) {
|
|
5108
|
+
postMessageToParent({ type: "CHAT_CONTEXT_REQUESTED" })
|
|
5109
|
+
}
|
|
5110
|
+
// `extractMetadata` carries the node's whole attribute map (the
|
|
5111
|
+
// `data-archie-id` the plain-render prototype stamps included) plus
|
|
5112
|
+
// `inspectorSource`; the host derives the chip's file from the
|
|
5113
|
+
// latter and its componentId from the former, falling back to
|
|
5114
|
+
// `data-dnd-node-id` on kit pages that stamp no archie id.
|
|
5115
|
+
postMessageToParent({
|
|
5116
|
+
type: "CHAT_CONTEXT_REQUESTED",
|
|
5117
|
+
metadata: extractMetadata(contextMenuTarget),
|
|
5118
|
+
})
|
|
5119
|
+
}
|
|
5120
|
+
)
|
|
5121
|
+
|
|
5122
|
+
contextMenuCommentButton = createMenuButton(
|
|
5123
|
+
getAddCommentLabel(),
|
|
5124
|
+
function () {
|
|
5125
|
+
postMessageToParent({
|
|
5126
|
+
type: "ADD_COMMENT_REQUESTED",
|
|
5127
|
+
target: contextMenuTargetPayload,
|
|
5128
|
+
})
|
|
5129
|
+
}
|
|
5130
|
+
)
|
|
5131
|
+
|
|
5132
|
+
menu.appendChild(contextMenuChatButton)
|
|
5133
|
+
menu.appendChild(contextMenuCommentButton)
|
|
5134
|
+
;(document.body || document.documentElement).appendChild(menu)
|
|
5135
|
+
contextMenuEl = menu
|
|
5136
|
+
syncContextMenuLabels()
|
|
5137
|
+
return menu
|
|
5138
|
+
}
|
|
5139
|
+
|
|
5140
|
+
function buildCommentTarget(element, event) {
|
|
5141
|
+
if (!element) return null
|
|
5142
|
+
try {
|
|
5143
|
+
var rect = element.getBoundingClientRect()
|
|
5144
|
+
var viewportWidth =
|
|
5145
|
+
window.innerWidth || document.documentElement.clientWidth || 0
|
|
5146
|
+
var viewportHeight =
|
|
5147
|
+
window.innerHeight || document.documentElement.clientHeight || 0
|
|
5148
|
+
var preview = element.textContent
|
|
5149
|
+
? element.textContent.trim().slice(0, 120)
|
|
5150
|
+
: ""
|
|
5151
|
+
return {
|
|
5152
|
+
cssSelector: getStableSelector(element),
|
|
5153
|
+
elementTag: String(element.tagName || "").toLowerCase(),
|
|
5154
|
+
elementPreview: preview || null,
|
|
5155
|
+
rect: {
|
|
5156
|
+
x: rect.left,
|
|
5157
|
+
y: rect.top,
|
|
5158
|
+
width: rect.width,
|
|
5159
|
+
height: rect.height,
|
|
5160
|
+
},
|
|
5161
|
+
// Fractions of the iframe viewport — the host re-projects them
|
|
5162
|
+
// against the iframe's own rect (see AddCommentRequestTarget).
|
|
5163
|
+
relativeX: viewportWidth ? event.clientX / viewportWidth : 0,
|
|
5164
|
+
relativeY: viewportHeight ? event.clientY / viewportHeight : 0,
|
|
5165
|
+
viewportWidth: viewportWidth,
|
|
5166
|
+
viewportHeight: viewportHeight,
|
|
5167
|
+
scrollTop:
|
|
5168
|
+
window.scrollY || document.documentElement.scrollTop || 0,
|
|
5169
|
+
}
|
|
5170
|
+
} catch (err) {
|
|
5171
|
+
return null
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
function showSelectionContextMenu(target, event) {
|
|
5176
|
+
var menu = ensureSelectionContextMenu()
|
|
5177
|
+
contextMenuTarget = target
|
|
5178
|
+
contextMenuTargetPayload = buildCommentTarget(target, event)
|
|
5179
|
+
menu.style.left =
|
|
5180
|
+
Math.min(event.clientX, window.innerWidth - 150) + "px"
|
|
5181
|
+
menu.style.top = Math.min(event.clientY, window.innerHeight - 60) + "px"
|
|
5182
|
+
menu.style.display = "block"
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
var onContextMenuOpen = function (e) {
|
|
5186
|
+
var target = e.target
|
|
5187
|
+
if (!target || target === document.documentElement) return
|
|
5188
|
+
if (contextMenuEl && contextMenuEl.contains(target)) return
|
|
5189
|
+
if (isInspectorElement(target)) return
|
|
5190
|
+
|
|
5191
|
+
var resolved = resolveExactInspectTarget(target)
|
|
5192
|
+
if (!isValidContentElement(resolved)) {
|
|
5193
|
+
resolved = getElementFromPoint(e.clientX, e.clientY, target)
|
|
5194
|
+
}
|
|
5195
|
+
if (!isValidContentElement(resolved)) return
|
|
5196
|
+
|
|
5197
|
+
e.preventDefault()
|
|
5198
|
+
e.stopPropagation()
|
|
5199
|
+
// Beat setupEventBlockers' "contextmenu" blocker, which would
|
|
5200
|
+
// otherwise swallow the event before it reaches us again.
|
|
5201
|
+
e.stopImmediatePropagation()
|
|
5202
|
+
showSelectionContextMenu(resolved, e)
|
|
5203
|
+
}
|
|
5204
|
+
|
|
5205
|
+
var onContextMenuDismissPointer = function (e) {
|
|
5206
|
+
if (contextMenuEl && contextMenuEl.contains(e.target)) return
|
|
5207
|
+
hideSelectionContextMenu()
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5210
|
+
var onContextMenuDismissKey = function (e) {
|
|
5211
|
+
if (e.key === "Escape") hideSelectionContextMenu()
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5214
|
+
// Each runInspector() call builds a fresh closure, so drop the previous
|
|
5215
|
+
// run's listeners (and its orphaned menu node) the same way the message
|
|
5216
|
+
// handler above is deduped — otherwise a re-run stacks handlers that
|
|
5217
|
+
// each open their own menu.
|
|
5218
|
+
var previousContextMenuHandlers =
|
|
5219
|
+
window.__ARCHIE_INSPECTOR_CONTEXT_MENU_HANDLERS__
|
|
5220
|
+
if (previousContextMenuHandlers) {
|
|
5221
|
+
try {
|
|
5222
|
+
document.removeEventListener(
|
|
5223
|
+
"contextmenu",
|
|
5224
|
+
previousContextMenuHandlers.contextmenu,
|
|
5225
|
+
true
|
|
5226
|
+
)
|
|
5227
|
+
document.removeEventListener(
|
|
5228
|
+
"mousedown",
|
|
5229
|
+
previousContextMenuHandlers.mousedown,
|
|
5230
|
+
true
|
|
5231
|
+
)
|
|
5232
|
+
window.removeEventListener(
|
|
5233
|
+
"scroll",
|
|
5234
|
+
previousContextMenuHandlers.scroll,
|
|
5235
|
+
true
|
|
5236
|
+
)
|
|
5237
|
+
document.removeEventListener(
|
|
5238
|
+
"keydown",
|
|
5239
|
+
previousContextMenuHandlers.keydown,
|
|
5240
|
+
true
|
|
5241
|
+
)
|
|
5242
|
+
} catch (err) {
|
|
5243
|
+
// Ignore cleanup errors
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
|
|
5247
|
+
document.addEventListener("contextmenu", onContextMenuOpen, true)
|
|
5248
|
+
document.addEventListener("mousedown", onContextMenuDismissPointer, true)
|
|
5249
|
+
window.addEventListener("scroll", hideSelectionContextMenu, true)
|
|
5250
|
+
document.addEventListener("keydown", onContextMenuDismissKey, true)
|
|
5251
|
+
|
|
5252
|
+
window.__ARCHIE_INSPECTOR_CONTEXT_MENU_HANDLERS__ = {
|
|
5253
|
+
contextmenu: onContextMenuOpen,
|
|
5254
|
+
mousedown: onContextMenuDismissPointer,
|
|
5255
|
+
scroll: hideSelectionContextMenu,
|
|
5256
|
+
keydown: onContextMenuDismissKey,
|
|
5257
|
+
}
|
|
5258
|
+
|
|
4396
5259
|
// Register inspector click handler BEFORE event blockers
|
|
4397
|
-
// This ensures it runs first and can mark events as handled
|
|
5260
|
+
// This ensures it runs first and can mark events as handled.
|
|
5261
|
+
// It stays AFTER the context-menu mousedown listener above, which must
|
|
5262
|
+
// still run (to dismiss the menu) even when a selection click calls
|
|
5263
|
+
// stopImmediatePropagation.
|
|
4398
5264
|
document.addEventListener("mousedown", inspectorClickHandler, true)
|
|
4399
5265
|
|
|
4400
5266
|
// Block clicks when element is selected but not inspecting
|