local-operator-ui 0.39.0 → 0.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/out/main/index.js +1127 -125
  2. package/out/preload/index.js +11 -1
  3. package/out/renderer/assets/{_basePickBy-CBn_dsaM.js → _basePickBy-CSbKxvDP.js} +1 -1
  4. package/out/renderer/assets/{_baseUniq-B-kmAdKj.js → _baseUniq-BCDEv_Oc.js} +1 -1
  5. package/out/renderer/assets/addressOverlayList-8wdZRD_t.css +1 -0
  6. package/out/renderer/assets/addressOverlayList-BEh9WU4R.js +1 -0
  7. package/out/renderer/assets/{agent-details-page-CjksC_ZM.js → agent-details-page-ByULov9A.js} +1 -1
  8. package/out/renderer/assets/{agent-hub-page-D3y5dujt.js → agent-hub-page-B69p4cyN.js} +1 -1
  9. package/out/renderer/assets/{agents-page-vmm4VKcI.js → agents-page-Cl3KMRZy.js} +3 -3
  10. package/out/renderer/assets/{arc-BSYPx6kR.js → arc-CyVR9eoB.js} +1 -1
  11. package/out/renderer/assets/{architectureDiagram-IEHRJDOE-C-dAuUYf.js → architectureDiagram-IEHRJDOE-D_nbs604.js} +1 -1
  12. package/out/renderer/assets/{arrow-up-BaHSg-fV.js → arrow-up-JG56D3ZM.js} +1 -1
  13. package/out/renderer/assets/{blockDiagram-JOT3LUYC-Dw9X_Asu.js → blockDiagram-JOT3LUYC-cGHKlnnj.js} +1 -1
  14. package/out/renderer/assets/{book-open-CwoTuMOa.js → book-open-_50fvSpZ.js} +1 -1
  15. package/out/renderer/assets/browser-page-u8Qblazq.js +1 -0
  16. package/out/renderer/assets/{browser-webauthn-prompt-CFEuJXkX.js → browser-webauthn-prompt-BkYnuYH0.js} +1 -1
  17. package/out/renderer/assets/{building-2-D06wTZQj.js → building-2-D-6u9f2b.js} +1 -1
  18. package/out/renderer/assets/{c4Diagram-VJAJSXHY-Bz93LlZc.js → c4Diagram-VJAJSXHY-DPSkxG1K.js} +1 -1
  19. package/out/renderer/assets/channel-BP0N5Jip.js +1 -0
  20. package/out/renderer/assets/{chunk-4BMEZGHF-H3mECOID.js → chunk-4BMEZGHF-k7Xc9N8N.js} +1 -1
  21. package/out/renderer/assets/{chunk-A2AXSNBT-BNJGDJK2.js → chunk-A2AXSNBT-BUQ9LrYj.js} +1 -1
  22. package/out/renderer/assets/{chunk-AEK57VVT-Baj26p0I.js → chunk-AEK57VVT-T8K0wOxu.js} +1 -1
  23. package/out/renderer/assets/{chunk-D6G4REZN-B5inAuRK.js → chunk-D6G4REZN-_drafUVH.js} +1 -1
  24. package/out/renderer/assets/{chunk-RZ5BOZE2-BK_F-zoT.js → chunk-RZ5BOZE2-BlfOLAiK.js} +1 -1
  25. package/out/renderer/assets/{chunk-XZIHB7SX-DipW3oXc.js → chunk-XZIHB7SX-br7k9c-y.js} +1 -1
  26. package/out/renderer/assets/classDiagram-GIVACNV2-DgPhvGMc.js +1 -0
  27. package/out/renderer/assets/classDiagram-v2-COTLJTTW-DgPhvGMc.js +1 -0
  28. package/out/renderer/assets/clone-D_2R859t.js +1 -0
  29. package/out/renderer/assets/{console-mirror-DB5yXAr9.js → console-mirror-BFQaHp1b.js} +1 -1
  30. package/out/renderer/assets/{consoleCapture-Bz69ju0Z.js → consoleCapture-L2qaS8a8.js} +1 -1
  31. package/out/renderer/assets/{conversation-input-sync-BVPZQjhD.js → conversation-input-sync-CH6BMnVZ.js} +267 -267
  32. package/out/renderer/assets/{dagre-OKDRZEBW-3ECMKEeo.js → dagre-OKDRZEBW-BnHmlI8H.js} +1 -1
  33. package/out/renderer/assets/{diagram-SSKATNLV-CmAf5Fvk.js → diagram-SSKATNLV-q_CpwdD7.js} +1 -1
  34. package/out/renderer/assets/{diagram-VNBRO52H-D_21NrT-.js → diagram-VNBRO52H-B2mKekLo.js} +1 -1
  35. package/out/renderer/assets/{erDiagram-Q7BY3M3F-CaUQ9PtP.js → erDiagram-Q7BY3M3F-CyQWMyA_.js} +1 -1
  36. package/out/renderer/assets/{error-boundary-h9802RBt.js → error-boundary-xAR0FhJi.js} +1 -1
  37. package/out/renderer/assets/{flowDiagram-4HSFHLVR-CVLxJB7Y.js → flowDiagram-4HSFHLVR-DaECRw9d.js} +1 -1
  38. package/out/renderer/assets/{ganttDiagram-APWFNJXF-Dt6KPnJQ.js → ganttDiagram-APWFNJXF-DcuyjU9V.js} +1 -1
  39. package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-D1BdvS2j.js → gitGraphDiagram-7IBYFJ6S-BY2WvPMI.js} +1 -1
  40. package/out/renderer/assets/{graph-wsNGWWCD.js → graph-or727h2C.js} +1 -1
  41. package/out/renderer/assets/{hash-C32vAdag.js → hash-Ch3ejz63.js} +1 -1
  42. package/out/renderer/assets/{index-3RPijdn_.css → index-BUzMwM-n.css} +1 -1
  43. package/out/renderer/assets/{index-6JB-jSFn.js → index-Bs2qygzx.js} +1 -1
  44. package/out/renderer/assets/{index-CiOOz1tp.js → index-C1J-T_Mt.js} +184 -184
  45. package/out/renderer/assets/{index-CvofVM43.js → index-DQE5o_2l.js} +1 -1
  46. package/out/renderer/assets/{infoDiagram-PH2N3AL5-CWPf3FZn.js → infoDiagram-PH2N3AL5-BXElUsIa.js} +1 -1
  47. package/out/renderer/assets/{installer-DKAnScPb.js → installer-DBTiypQJ.js} +1 -1
  48. package/out/renderer/assets/{journeyDiagram-U35MCT3I-B5eLtG46.js → journeyDiagram-U35MCT3I-GAMZIDYa.js} +1 -1
  49. package/out/renderer/assets/{kanban-definition-NDS4AKOZ-BtJFd9zq.js → kanban-definition-NDS4AKOZ-ij90M1gN.js} +1 -1
  50. package/out/renderer/assets/{layout-B3W_M4xE.js → layout-DqWzcUI9.js} +1 -1
  51. package/out/renderer/assets/{legacy-agents-page-aqbs_qcV.js → legacy-agents-page-D96D0ThJ.js} +1 -1
  52. package/out/renderer/assets/{library-page-BIa8sD1w.js → library-page-BtkHYaRw.js} +3 -3
  53. package/out/renderer/assets/{mermaid.core-DzmjLSHb.js → mermaid.core-B9fSy9q8.js} +5 -5
  54. package/out/renderer/assets/{mesh-page-De8Abvyb.js → mesh-page-BKS4b5nA.js} +1 -1
  55. package/out/renderer/assets/{mindmap-definition-ALO5MXBD-C1aylJ0P.js → mindmap-definition-ALO5MXBD-CnOp0ZCp.js} +1 -1
  56. package/out/renderer/assets/{mini-mPtvVJCT.js → mini-BL0qywTp.js} +1 -1
  57. package/out/renderer/assets/{notebook-pen-CvyIerwZ.js → notebook-pen-DQs4gt50.js} +1 -1
  58. package/out/renderer/assets/{org-access-Cpt-vs-I.js → org-access-DvoJr_O7.js} +1 -1
  59. package/out/renderer/assets/{org-surface-gate-C8Gs3DNG.js → org-surface-gate-kYD-nQnh.js} +1 -1
  60. package/out/renderer/assets/{page-header-DBIgQcaM.js → page-header-BY6LFXsV.js} +1 -1
  61. package/out/renderer/assets/{pieDiagram-IB7DONF6-CjnobVYZ.js → pieDiagram-IB7DONF6-DkbkzE3D.js} +1 -1
  62. package/out/renderer/assets/{projects-page-041auzXW.js → projects-page-BLQzs3uM.js} +1 -1
  63. package/out/renderer/assets/{publication-validation-JvAtW-AY.js → publication-validation-4m4ljeE_.js} +1 -1
  64. package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-B0TLJJFL.js → quadrantDiagram-7GDLP6J5-Dfe4cbbn.js} +1 -1
  65. package/out/renderer/assets/{radar-MK3ICKWK-D6MZmkB6.js → radar-MK3ICKWK-BdbftlUx.js} +1 -1
  66. package/out/renderer/assets/{radient-auth-buttons-quggTTqK.js → radient-auth-buttons-DngU_dyA.js} +1 -1
  67. package/out/renderer/assets/{requirementDiagram-KVF5MWMF-BSAJQlo8.js → requirementDiagram-KVF5MWMF-PKSI_Jbh.js} +1 -1
  68. package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-CQqGLTr_.js → sankeyDiagram-QLVOVGJD-Bon1uUIU.js} +1 -1
  69. package/out/renderer/assets/{schedules-page-B51F-ADQ.js → schedules-page-DW0Ae9De.js} +1 -1
  70. package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-B0LPyEfC.js → sequenceDiagram-X6HHIX6F-Bnoc1VB7.js} +1 -1
  71. package/out/renderer/assets/{settings-page-BpTAK-RZ.js → settings-page-Djdtg7c4.js} +1 -1
  72. package/out/renderer/assets/{smartart-B1Hg0uN6.js → smartart-CIIex-5K.js} +1 -1
  73. package/out/renderer/assets/{stateDiagram-DGXRK772-hL4-Mzxx.js → stateDiagram-DGXRK772-CGmFZKBF.js} +1 -1
  74. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-C7mh141a.js +1 -0
  75. package/out/renderer/assets/team-bridge-page-B1jfvoLr.js +11 -0
  76. package/out/renderer/assets/{timeline-definition-BDJGKUSR-BOE8E_XE.js → timeline-definition-BDJGKUSR-B3KzRgdo.js} +1 -1
  77. package/out/renderer/assets/{ui-preferences-store-Cws2cozv.js → ui-preferences-store-YyZ50qSr.js} +1 -1
  78. package/out/renderer/assets/{use-download-agent-mutation-HcVu2YgL.js → use-download-agent-mutation-CGnvP-dC.js} +1 -1
  79. package/out/renderer/assets/{use-session-snapshot-CMQBHevf.js → use-session-snapshot-B0KS_Jw_.js} +1 -1
  80. package/out/renderer/assets/{xychartDiagram-VJFVF3MP-D3cI5mnO.js → xychartDiagram-VJFVF3MP-BEcciMcG.js} +1 -1
  81. package/out/renderer/browser-address-overlay-list.html +35 -0
  82. package/out/renderer/console-capture.html +5 -5
  83. package/out/renderer/index.html +7 -7
  84. package/out/renderer/installer.html +4 -4
  85. package/out/renderer/mini.html +7 -7
  86. package/package.json +8 -3
  87. package/out/renderer/assets/browser-page-DmYAxbWu.js +0 -1
  88. package/out/renderer/assets/channel-BJdem-Qj.js +0 -1
  89. package/out/renderer/assets/classDiagram-GIVACNV2-BQLeT9Qk.js +0 -1
  90. package/out/renderer/assets/classDiagram-v2-COTLJTTW-BQLeT9Qk.js +0 -1
  91. package/out/renderer/assets/clone-BaxJyMvd.js +0 -1
  92. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-Bczp9bth.js +0 -1
  93. package/out/renderer/assets/team-bridge-page-xhQelITo.js +0 -11
  94. /package/out/renderer/assets/{index-COgOH-4N.js → index-YWHAePtd.js} +0 -0
package/out/main/index.js CHANGED
@@ -15004,10 +15004,24 @@ function resolveLauncherWatchPlan(input) {
15004
15004
  };
15005
15005
  }
15006
15006
  if (input.mode !== "headless") {
15007
+ if (input.mode !== "inactive" || input.boundedVisibleProbe !== true) {
15008
+ return {
15009
+ watch: false,
15010
+ launcherPid: null,
15011
+ reason: `window mode ${input.mode} is not launcher-bound: a person can close it`
15012
+ };
15013
+ }
15014
+ if (!Number.isInteger(input.launcherPid) || input.launcherPid <= 1) {
15015
+ return {
15016
+ watch: false,
15017
+ launcherPid: null,
15018
+ reason: "visible probe already detached (no launcher to outlive)"
15019
+ };
15020
+ }
15007
15021
  return {
15008
- watch: false,
15009
- launcherPid: null,
15010
- reason: `window mode ${input.mode} is not launcher-bound: a person can close it`
15022
+ watch: true,
15023
+ launcherPid: input.launcherPid,
15024
+ reason: `visible probe launched by pid ${input.launcherPid}; it is capped and quits when that process goes`
15011
15025
  };
15012
15026
  }
15013
15027
  if (!Number.isInteger(input.launcherPid) || input.launcherPid <= 1) {
@@ -26052,11 +26066,11 @@ class ConsoleHost {
26052
26066
  { rendered: null }
26053
26067
  );
26054
26068
  }
26055
- const rect = cropToWindow(
26069
+ const rect2 = cropToWindow(
26056
26070
  report.contentRect,
26057
26071
  window.getContentBounds()
26058
26072
  );
26059
- const png = await this.captureWithRetry(window, rect);
26073
+ const png = await this.captureWithRetry(window, rect2);
26060
26074
  const grid = runtime.emulator.grid;
26061
26075
  return {
26062
26076
  image_base64: png.toString("base64"),
@@ -26092,10 +26106,10 @@ class ConsoleHost {
26092
26106
  * renderer paints antialiased text and a cursor — and the empty-terminal case is the
26093
26107
  * offscreen path's problem too, so this is not a new ceiling.
26094
26108
  */
26095
- async captureWithRetry(window, rect) {
26109
+ async captureWithRetry(window, rect2) {
26096
26110
  let bytes = 0;
26097
26111
  for (let attempt = 1; attempt <= 2; attempt++) {
26098
- const image2 = await window.webContents.capturePage(rect);
26112
+ const image2 = await window.webContents.capturePage(rect2);
26099
26113
  const png = framePng(image2);
26100
26114
  bytes = png.length;
26101
26115
  if (hasTerminalContent(image2.toBitmap())) {
@@ -27060,14 +27074,14 @@ function wrapBracketedPaste(bytes) {
27060
27074
  out.set(end, start.length + bytes.length);
27061
27075
  return out;
27062
27076
  }
27063
- function cropToWindow(rect, bounds) {
27064
- const x = Math.max(Math.floor(rect.x), 0);
27065
- const y = Math.max(Math.floor(rect.y), 0);
27077
+ function cropToWindow(rect2, bounds) {
27078
+ const x = Math.max(Math.floor(rect2.x), 0);
27079
+ const y = Math.max(Math.floor(rect2.y), 0);
27066
27080
  return {
27067
27081
  x,
27068
27082
  y,
27069
- width: Math.max(Math.min(Math.floor(rect.width), bounds.width - x), 1),
27070
- height: Math.max(Math.min(Math.floor(rect.height), bounds.height - y), 1)
27083
+ width: Math.max(Math.min(Math.floor(rect2.width), bounds.width - x), 1),
27084
+ height: Math.max(Math.min(Math.floor(rect2.height), bounds.height - y), 1)
27071
27085
  };
27072
27086
  }
27073
27087
  function framePng(image2) {
@@ -27836,12 +27850,12 @@ function contentReportOrThrow(value) {
27836
27850
  throw new Error("A console content report is required.");
27837
27851
  }
27838
27852
  const report = value;
27839
- const rect = report.contentRect;
27840
- if (!rect || typeof rect !== "object") {
27853
+ const rect2 = report.contentRect;
27854
+ if (!rect2 || typeof rect2 !== "object") {
27841
27855
  throw new Error("A console content report needs a contentRect.");
27842
27856
  }
27843
27857
  const numbers = ["x", "y", "width", "height"].map((key) => {
27844
- const raw = rect[key];
27858
+ const raw = rect2[key];
27845
27859
  if (typeof raw !== "number" || !Number.isFinite(raw)) {
27846
27860
  throw new Error(`contentRect.${key} must be a number.`);
27847
27861
  }
@@ -28341,6 +28355,60 @@ async function startConsoleHost(options) {
28341
28355
  };
28342
28356
  }
28343
28357
  }
28358
+ const DEV_DRIVER_ENV = "LOCAL_OPERATOR_UI_DEV_DRIVER";
28359
+ const DEV_DRIVER_OUT_ENV = "LOCAL_OPERATOR_UI_DEV_DRIVER_OUT";
28360
+ const DEV_DRIVER_ARG = "--lo-dev-driver";
28361
+ const DEV_DRIVER_CAPTURE = "dev-driver-capture";
28362
+ const DEV_DRIVER_FACTS = "dev-driver-facts";
28363
+ const DEV_DRIVER_ADDRESS_OVERLAY = "dev-driver-address-overlay";
28364
+ const DEV_DRIVER_ADDRESS_OVERLAY_PROJECT = "dev-driver-address-overlay-project";
28365
+ const DEV_DRIVER_ADDRESS_OVERLAY_EVENT = "dev-driver-address-overlay-event";
28366
+ const TRUTHY = /* @__PURE__ */ new Set(["1", "true"]);
28367
+ function readOptIn(env) {
28368
+ const raw = env[DEV_DRIVER_ENV];
28369
+ if (raw === void 0 || raw.trim() === "")
28370
+ return { asked: false, value: null };
28371
+ return { asked: true, value: raw.trim() };
28372
+ }
28373
+ function resolveDevDriverArming(input) {
28374
+ const off = (problems = []) => ({
28375
+ armed: false,
28376
+ outDir: null,
28377
+ problems
28378
+ });
28379
+ const optIn = readOptIn(input.env);
28380
+ if (!optIn.asked) return off();
28381
+ if (!TRUTHY.has(optIn.value.toLowerCase())) {
28382
+ return off([
28383
+ `${DEV_DRIVER_ENV}=${optIn.value} is not an opt-in (accepted: 1, true); the renderer dev driver stayed off`
28384
+ ]);
28385
+ }
28386
+ if (input.windowMode !== "headless" && input.windowMode !== "inactive") {
28387
+ return off([
28388
+ `the renderer dev driver refused to arm in a "${input.windowMode}" window; it never raises one, so use --window-mode=headless (or inactive)`
28389
+ ]);
28390
+ }
28391
+ const outDir = input.env[DEV_DRIVER_OUT_ENV]?.trim() ?? "";
28392
+ if (outDir === "" || !outDir.startsWith("/")) {
28393
+ return off([
28394
+ `${DEV_DRIVER_ENV} was set but ${DEV_DRIVER_OUT_ENV} is ${outDir === "" ? "missing" : `"${outDir}"`}; an absolute frames directory is required, so the driver did not arm`
28395
+ ]);
28396
+ }
28397
+ return { armed: true, outDir, problems: [] };
28398
+ }
28399
+ function headlessExerciserAllowed(input) {
28400
+ return input.arming.armed && input.windowMode === "headless";
28401
+ }
28402
+ function devDriverArgument(outDir) {
28403
+ return `${DEV_DRIVER_ARG}=${outDir}`;
28404
+ }
28405
+ function describeDevDriverArming(arming) {
28406
+ if (arming.armed) {
28407
+ return `[dev-driver] ARMED; frames are written to ${arming.outDir}`;
28408
+ }
28409
+ if (arming.problems.length === 0) return null;
28410
+ return `[dev-driver] not armed: ${arming.problems.join("; ")}`;
28411
+ }
28344
28412
  const WEBAUTHN_GROUP_SUFFIX = ".webauthn";
28345
28413
  const WEBAUTHN_PROMPT_REASON = "use a passkey for $1";
28346
28414
  const WEBAUTHN_CHOOSER_TIMEOUT_MS = 6e4;
@@ -28763,7 +28831,368 @@ function labelOf(account) {
28763
28831
  if (!account) return "an unnamed passkey";
28764
28832
  return account.displayName || account.name || "an unnamed passkey";
28765
28833
  }
28834
+ const ADDRESS_LIST_ENV = "LOCAL_OPERATOR_UI_BROWSER_ADDRESS_OVERLAY_S2";
28835
+ const ADDRESS_LIST_DOCUMENT = "browser-address-overlay-list.html";
28836
+ const ADDRESS_LIST_HEADER_HEIGHT = 28;
28837
+ const ADDRESS_LIST_ROW_HEIGHT = 35.5;
28838
+ const ADDRESS_LIST_ROWS_MAX = 5;
28839
+ const ADDRESS_LIST_MAX_HEIGHT_DIP = 212;
28840
+ const ADDRESS_LIST_NOTICE_HEIGHT_DIP = 104;
28841
+ const ADDRESS_LIST_ANCHOR_GAP_MAX = 24;
28842
+ const ADDRESS_LIST_MAX_PAYLOAD_BYTES = 16 * 1024;
28843
+ const ADDRESS_LIST_MAX_TEXT = 512;
28844
+ const ADDRESS_LIST_MAX_TITLE = 256;
28845
+ const ADDRESS_LIST_MAX_LABEL = 256;
28846
+ const ADDRESS_LIST_THEME_FALLBACK = "#1b1f24";
28847
+ const DIP_INT32_MAX$1 = 2 ** 31 - 1;
28848
+ const NOTICE_KINDS = [
28849
+ "error",
28850
+ "hint",
28851
+ "empty",
28852
+ "no_match"
28853
+ ];
28854
+ const HOSTILE_STRING = /[\p{Cc}\u2028\u2029\u202A-\u202E\u2066-\u2069]/gu;
28855
+ const COLOR = /^(#[0-9a-f]{6}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))$/i;
28856
+ const PROJECTION_KEYS = /* @__PURE__ */ new Set([
28857
+ "v",
28858
+ "seq",
28859
+ "tabId",
28860
+ "visible",
28861
+ "pageRect",
28862
+ "field",
28863
+ "listTop",
28864
+ "active",
28865
+ "view",
28866
+ "label",
28867
+ "dir",
28868
+ "theme"
28869
+ ]);
28870
+ const RECT_KEYS = ["x", "y", "width", "height"];
28871
+ const THEME_KEYS = [
28872
+ "elevated",
28873
+ "ink",
28874
+ "inkMuted",
28875
+ "accent",
28876
+ "accentWash",
28877
+ "rowHover",
28878
+ "hairline",
28879
+ "control"
28880
+ ];
28881
+ function isRecord$1(value) {
28882
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28883
+ }
28884
+ function hasOnlyKeys(value, allowed) {
28885
+ const set2 = allowed instanceof Set ? allowed : new Set(allowed);
28886
+ for (const key of Object.keys(value)) if (!set2.has(key)) return false;
28887
+ return true;
28888
+ }
28889
+ function finite(value) {
28890
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
28891
+ }
28892
+ function rect(value) {
28893
+ if (!isRecord$1(value)) return null;
28894
+ if (!hasOnlyKeys(value, RECT_KEYS)) return null;
28895
+ const x = finite(value.x);
28896
+ const y = finite(value.y);
28897
+ const width = finite(value.width);
28898
+ const height = finite(value.height);
28899
+ if (x === null || y === null || width === null || height === null)
28900
+ return null;
28901
+ return { x, y, width, height };
28902
+ }
28903
+ function sanitizeOverlayText(value, cap) {
28904
+ if (typeof value !== "string") return null;
28905
+ const stripped = value.replace(HOSTILE_STRING, "");
28906
+ const points = [...stripped];
28907
+ return points.length > cap ? points.slice(0, cap).join("") : stripped;
28908
+ }
28909
+ function sanitizeRows(value) {
28910
+ if (!Array.isArray(value)) return null;
28911
+ if (value.length === 0 || value.length > ADDRESS_LIST_ROWS_MAX) return null;
28912
+ const rows = [];
28913
+ for (const entry of value) {
28914
+ if (!isRecord$1(entry) || !hasOnlyKeys(entry, ["text", "title"])) return null;
28915
+ const text = sanitizeOverlayText(entry.text, ADDRESS_LIST_MAX_TEXT);
28916
+ const title = sanitizeOverlayText(entry.title, ADDRESS_LIST_MAX_TITLE);
28917
+ if (text === null || title === null) return null;
28918
+ if (text.length === 0) return null;
28919
+ rows.push({ text, title });
28920
+ }
28921
+ return rows;
28922
+ }
28923
+ function sanitizeTheme(value) {
28924
+ if (value === void 0) return { theme: null, fact: "absent" };
28925
+ if (!isRecord$1(value) || !hasOnlyKeys(value, THEME_KEYS)) {
28926
+ return { theme: null, fact: "fallback" };
28927
+ }
28928
+ const theme = {};
28929
+ for (const key of THEME_KEYS) {
28930
+ const raw = value[key];
28931
+ if (typeof raw !== "string" || !COLOR.test(raw)) {
28932
+ return { theme: null, fact: "fallback" };
28933
+ }
28934
+ theme[key] = raw;
28935
+ }
28936
+ return { theme, fact: "resolved" };
28937
+ }
28938
+ function jsonBytes(value) {
28939
+ try {
28940
+ return new TextEncoder().encode(JSON.stringify(value)).length;
28941
+ } catch {
28942
+ return Number.POSITIVE_INFINITY;
28943
+ }
28944
+ }
28945
+ function sanitizeProjection(raw) {
28946
+ if (jsonBytes(raw) > ADDRESS_LIST_MAX_PAYLOAD_BYTES) {
28947
+ return { ok: false, reason: "payload_too_large" };
28948
+ }
28949
+ if (!isRecord$1(raw)) return { ok: false, reason: "not_an_object" };
28950
+ if (!hasOnlyKeys(raw, PROJECTION_KEYS)) {
28951
+ return { ok: false, reason: "unknown_key" };
28952
+ }
28953
+ if (raw.v !== 1) return { ok: false, reason: "bad_version" };
28954
+ if (!Number.isInteger(raw.seq) || raw.seq < 1) {
28955
+ return { ok: false, reason: "bad_seq" };
28956
+ }
28957
+ if (!Number.isInteger(raw.tabId)) return { ok: false, reason: "bad_tab" };
28958
+ if (typeof raw.visible !== "boolean") {
28959
+ return { ok: false, reason: "bad_visible" };
28960
+ }
28961
+ if (!raw.visible) {
28962
+ return {
28963
+ ok: true,
28964
+ projection: {
28965
+ v: 1,
28966
+ seq: raw.seq,
28967
+ tabId: raw.tabId,
28968
+ visible: false,
28969
+ pageRect: { x: 0, y: 0, width: 0, height: 0 },
28970
+ field: { x: 0, y: 0, width: 0, height: 0 },
28971
+ listTop: 0,
28972
+ active: -1,
28973
+ view: { kind: "notice", notice: "empty", text: "" },
28974
+ label: "",
28975
+ dir: "ltr",
28976
+ theme: null,
28977
+ themeFact: "absent"
28978
+ }
28979
+ };
28980
+ }
28981
+ const pageRect = rect(raw.pageRect);
28982
+ const field = rect(raw.field);
28983
+ if (pageRect === null || field === null) {
28984
+ return { ok: false, reason: "bad_rect" };
28985
+ }
28986
+ const listTop = finite(raw.listTop);
28987
+ if (listTop === null) return { ok: false, reason: "bad_list_top" };
28988
+ if (field.width <= 0 || field.height <= 0) {
28989
+ return { ok: false, reason: "bad_field" };
28990
+ }
28991
+ if (!Number.isInteger(raw.active)) return { ok: false, reason: "bad_active" };
28992
+ const active = raw.active;
28993
+ const viewRaw = raw.view;
28994
+ if (!isRecord$1(viewRaw)) return { ok: false, reason: "bad_view" };
28995
+ let view;
28996
+ if (viewRaw.kind === "rows") {
28997
+ if (!hasOnlyKeys(viewRaw, ["kind", "rows"])) {
28998
+ return { ok: false, reason: "bad_view" };
28999
+ }
29000
+ const rows = sanitizeRows(viewRaw.rows);
29001
+ if (rows === null) return { ok: false, reason: "bad_rows" };
29002
+ view = { kind: "rows", rows };
29003
+ } else if (viewRaw.kind === "notice") {
29004
+ if (!hasOnlyKeys(viewRaw, ["kind", "notice", "text"])) {
29005
+ return { ok: false, reason: "bad_view" };
29006
+ }
29007
+ if (!NOTICE_KINDS.includes(viewRaw.notice)) {
29008
+ return { ok: false, reason: "bad_notice" };
29009
+ }
29010
+ const text = sanitizeOverlayText(viewRaw.text, ADDRESS_LIST_MAX_LABEL);
29011
+ if (text === null) return { ok: false, reason: "bad_notice_text" };
29012
+ view = {
29013
+ kind: "notice",
29014
+ notice: viewRaw.notice,
29015
+ text
29016
+ };
29017
+ } else {
29018
+ return { ok: false, reason: "bad_view" };
29019
+ }
29020
+ if (view.kind === "rows" ? active < -1 || active >= view.rows.length : active !== -1) {
29021
+ return { ok: false, reason: "bad_active" };
29022
+ }
29023
+ const label = sanitizeOverlayText(raw.label, ADDRESS_LIST_MAX_LABEL);
29024
+ if (label === null) return { ok: false, reason: "bad_label" };
29025
+ if (raw.dir !== "ltr" && raw.dir !== "rtl") {
29026
+ return { ok: false, reason: "bad_dir" };
29027
+ }
29028
+ const { theme, fact } = sanitizeTheme(raw.theme);
29029
+ return {
29030
+ ok: true,
29031
+ projection: {
29032
+ v: 1,
29033
+ seq: raw.seq,
29034
+ tabId: raw.tabId,
29035
+ visible: true,
29036
+ pageRect,
29037
+ field,
29038
+ listTop,
29039
+ active,
29040
+ view,
29041
+ label,
29042
+ dir: raw.dir,
29043
+ theme,
29044
+ themeFact: fact
29045
+ }
29046
+ };
29047
+ }
29048
+ function int32$1(value) {
29049
+ if (!Number.isFinite(value)) return null;
29050
+ const rounded = Math.round(value);
29051
+ return Math.abs(rounded) > DIP_INT32_MAX$1 ? null : rounded;
29052
+ }
29053
+ function addressListHeightFor(view) {
29054
+ if (view.kind === "notice") return ADDRESS_LIST_NOTICE_HEIGHT_DIP;
29055
+ return Math.ceil(
29056
+ ADDRESS_LIST_HEADER_HEIGHT + 2 + view.rows.length * ADDRESS_LIST_ROW_HEIGHT + 2 + 1
29057
+ );
29058
+ }
29059
+ function addressListBoundsFor(field, listTop, view, owner) {
29060
+ const fx = int32$1(field.x);
29061
+ const fy = int32$1(field.y);
29062
+ const fw = int32$1(field.width);
29063
+ const fh = int32$1(field.height);
29064
+ const top = int32$1(listTop);
29065
+ const ownerWidth = int32$1(owner.width);
29066
+ const ownerHeight = int32$1(owner.height);
29067
+ if (fx === null || fy === null || fw === null || fh === null || top === null || ownerWidth === null || ownerHeight === null) {
29068
+ return null;
29069
+ }
29070
+ if (fw <= 0 || fh <= 0 || ownerWidth <= 0 || ownerHeight <= 0) return null;
29071
+ const gap = top - (fy + fh);
29072
+ if (gap < 0 || gap > ADDRESS_LIST_ANCHOR_GAP_MAX) return null;
29073
+ const height = addressListHeightFor(view);
29074
+ if (height > ADDRESS_LIST_MAX_HEIGHT_DIP) return null;
29075
+ const left = Math.max(0, fx);
29076
+ const right = Math.min(ownerWidth, fx + fw);
29077
+ const bottom = Math.min(ownerHeight, top + height);
29078
+ if (right <= left) return null;
29079
+ if (bottom - top !== height) return null;
29080
+ return { x: left, y: top, width: right - left, height };
29081
+ }
29082
+ function addressListRowIndexAt(view, offsetY) {
29083
+ if (view.kind !== "rows") return null;
29084
+ if (!Number.isFinite(offsetY)) return null;
29085
+ const withinRows = offsetY - ADDRESS_LIST_HEADER_HEIGHT - 2;
29086
+ if (withinRows < 0) return null;
29087
+ const index = Math.floor(withinRows / ADDRESS_LIST_ROW_HEIGHT);
29088
+ return index >= 0 && index < view.rows.length ? index : null;
29089
+ }
29090
+ function measurementValue(value) {
29091
+ if (!isRecord$1(value)) return null;
29092
+ if (!Number.isInteger(value.appliedSeq)) return null;
29093
+ if (!Number.isInteger(value.rowCount)) return null;
29094
+ const headerHeight = finite(value.headerHeight);
29095
+ const viewportHeight = finite(value.viewportHeight);
29096
+ const scrollHeight = finite(value.scrollHeight);
29097
+ if (headerHeight === null || viewportHeight === null || scrollHeight === null)
29098
+ return null;
29099
+ if (!Array.isArray(value.rowRects)) return null;
29100
+ const rowRects = [];
29101
+ for (const entry of value.rowRects) {
29102
+ if (!isRecord$1(entry)) return null;
29103
+ const top = finite(entry.top);
29104
+ const height = finite(entry.height);
29105
+ if (top === null || height === null) return null;
29106
+ rowRects.push({ top, height });
29107
+ }
29108
+ const innerHeight = finite(value.innerHeight);
29109
+ const visualHeight = finite(value.visualHeight);
29110
+ let activeRowRect = null;
29111
+ if (value.activeRowRect !== null && value.activeRowRect !== void 0) {
29112
+ if (!isRecord$1(value.activeRowRect)) return null;
29113
+ const top = finite(value.activeRowRect.top);
29114
+ const height = finite(value.activeRowRect.height);
29115
+ if (top === null || height === null) return null;
29116
+ activeRowRect = { top, height };
29117
+ }
29118
+ const appliedSeq = value.appliedSeq;
29119
+ const rowCount = value.rowCount;
29120
+ if (typeof appliedSeq !== "number" || typeof rowCount !== "number")
29121
+ return null;
29122
+ return {
29123
+ appliedSeq,
29124
+ rowCount,
29125
+ headerHeight,
29126
+ rowRects,
29127
+ viewportHeight,
29128
+ scrollHeight,
29129
+ ...innerHeight === null ? {} : { innerHeight },
29130
+ ...visualHeight === null ? {} : { visualHeight },
29131
+ activeRowRect
29132
+ };
29133
+ }
29134
+ function addressListMeasurementProblem(raw, projection, requestedHeight) {
29135
+ const measurement = measurementValue(raw);
29136
+ if (measurement === null) return "measure_invalid";
29137
+ if (measurement.appliedSeq !== projection.seq) return "measure_stale";
29138
+ const reported = measurement.visualHeight ?? measurement.innerHeight;
29139
+ if (reported !== void 0 && Math.abs(reported - requestedHeight) > 1) {
29140
+ return "measure_viewport";
29141
+ }
29142
+ if (projection.view.kind !== "rows") {
29143
+ return measurement.rowCount === 0 ? null : "measure_extra_rows";
29144
+ }
29145
+ const rows = projection.view.rows;
29146
+ if (measurement.rowCount !== rows.length) return "measure_row_count";
29147
+ if (measurement.rowRects.length !== rows.length) return "measure_row_rects";
29148
+ if (Math.abs(measurement.headerHeight - ADDRESS_LIST_HEADER_HEIGHT) > 0.5) {
29149
+ return "measure_header";
29150
+ }
29151
+ for (const row of measurement.rowRects) {
29152
+ if (Math.abs(row.height - ADDRESS_LIST_ROW_HEIGHT) > 0.5) {
29153
+ return "measure_row_height";
29154
+ }
29155
+ }
29156
+ if (measurement.scrollHeight > measurement.viewportHeight + 1) {
29157
+ return "measure_scroll";
29158
+ }
29159
+ if (projection.active >= 0) {
29160
+ if (measurement.activeRowRect === null) return "measure_active_missing";
29161
+ const paintBox = measurement.visualHeight ?? measurement.innerHeight ?? requestedHeight;
29162
+ const { top, height } = measurement.activeRowRect;
29163
+ const documentBottom = measurement.headerHeight + top + height;
29164
+ if (top < -0.5 || documentBottom > paintBox + 0.5) {
29165
+ return "active_not_visible";
29166
+ }
29167
+ }
29168
+ return null;
29169
+ }
29170
+ function addressListViewModel(projection) {
29171
+ const fallback = {
29172
+ elevated: ADDRESS_LIST_THEME_FALLBACK,
29173
+ ink: "#e6e8ea",
29174
+ inkMuted: "#9aa0a6",
29175
+ accent: "#5b9dd9",
29176
+ accentWash: "#23303d",
29177
+ rowHover: "#24282e",
29178
+ hairline: "#2f343b",
29179
+ control: "#3a4048"
29180
+ };
29181
+ const theme = projection.theme ?? fallback;
29182
+ return {
29183
+ v: 1,
29184
+ seq: projection.seq,
29185
+ dir: projection.dir,
29186
+ label: projection.label,
29187
+ active: projection.active,
29188
+ view: projection.view,
29189
+ height: addressListHeightFor(projection.view),
29190
+ theme
29191
+ };
29192
+ }
28766
29193
  const ADDRESS_OVERLAY_S1_ENV = "LOCAL_OPERATOR_UI_BROWSER_ADDRESS_OVERLAY_S1";
29194
+ const ADDRESS_OVERLAY_VISIBLE_ENV = "LOCAL_OPERATOR_UI_BROWSER_ADDRESS_OVERLAY_VISIBLE";
29195
+ const ADDRESS_OVERLAY_VISIBLE_MAX_MS = 12e4;
28767
29196
  const ADDRESS_OVERLAY_DOCUMENT = "browser-address-overlay.html";
28768
29197
  const ADDRESS_OVERLAY_MAX_HEIGHT_DIP = 160;
28769
29198
  const ADDRESS_OVERLAY_READY_TIMEOUT_MS = 5e3;
@@ -28771,25 +29200,132 @@ const DIP_INT32_MAX = 2 ** 31 - 1;
28771
29200
  const INDEX_DOCUMENT_SUFFIX$2 = /\/?index\.html(\?.*)?$/;
28772
29201
  const TRAILING_SLASH$1 = /\/$/;
28773
29202
  function addressOverlayDocumentUrlFor(rendererUrl) {
28774
- return INDEX_DOCUMENT_SUFFIX$2.test(rendererUrl) ? rendererUrl.replace(INDEX_DOCUMENT_SUFFIX$2, `/${ADDRESS_OVERLAY_DOCUMENT}`) : `${rendererUrl.replace(TRAILING_SLASH$1, "")}/${ADDRESS_OVERLAY_DOCUMENT}`;
29203
+ return documentUrlFor(rendererUrl, ADDRESS_OVERLAY_DOCUMENT);
29204
+ }
29205
+ function addressListDocumentUrlFor(rendererUrl) {
29206
+ return documentUrlFor(rendererUrl, ADDRESS_LIST_DOCUMENT);
29207
+ }
29208
+ function documentUrlFor(rendererUrl, document) {
29209
+ return INDEX_DOCUMENT_SUFFIX$2.test(rendererUrl) ? rendererUrl.replace(INDEX_DOCUMENT_SUFFIX$2, `/${document}`) : `${rendererUrl.replace(TRAILING_SLASH$1, "")}/${document}`;
28775
29210
  }
28776
29211
  function resolveAddressOverlayGate(input) {
28777
29212
  const raw = input.env[ADDRESS_OVERLAY_S1_ENV];
29213
+ const listRaw = input.env[ADDRESS_LIST_ENV];
29214
+ const visibleRaw = input.env[ADDRESS_OVERLAY_VISIBLE_ENV];
29215
+ const listAsked = listRaw !== void 0 && listRaw.trim() !== "";
29216
+ const visibleAsked = visibleRaw !== void 0 && visibleRaw.trim() !== "";
28778
29217
  if (raw === void 0 || raw.trim() === "") {
28779
- return { asked: false, enabled: false, reason: null };
29218
+ if (visibleAsked) {
29219
+ return {
29220
+ asked: true,
29221
+ enabled: false,
29222
+ list: false,
29223
+ visible: false,
29224
+ reason: `${ADDRESS_OVERLAY_VISIBLE_ENV} needs ${ADDRESS_OVERLAY_S1_ENV}=1 and ${ADDRESS_LIST_ENV}=1: the visible probe is an increment on S1's child and S2's mirror, not a third arm`
29225
+ };
29226
+ }
29227
+ return listAsked ? {
29228
+ asked: true,
29229
+ enabled: false,
29230
+ list: false,
29231
+ visible: false,
29232
+ reason: `${ADDRESS_LIST_ENV} needs ${ADDRESS_OVERLAY_S1_ENV}=1: S2's list is a mirror inside S1's child, not a second child`
29233
+ } : {
29234
+ asked: false,
29235
+ enabled: false,
29236
+ list: false,
29237
+ visible: false,
29238
+ reason: null
29239
+ };
28780
29240
  }
28781
29241
  const asked = true;
28782
29242
  if (raw.trim() !== "1") {
28783
29243
  return {
28784
29244
  asked,
28785
29245
  enabled: false,
29246
+ list: false,
29247
+ visible: false,
28786
29248
  reason: `${ADDRESS_OVERLAY_S1_ENV}=${raw.trim()} is not an opt-in (accepted: 1); the address overlay stayed off`
28787
29249
  };
28788
29250
  }
29251
+ if (listAsked && listRaw.trim() !== "1") {
29252
+ return {
29253
+ asked,
29254
+ enabled: false,
29255
+ list: false,
29256
+ visible: false,
29257
+ reason: `${ADDRESS_LIST_ENV}=${listRaw.trim()} is not an opt-in (accepted: 1); the address overlay stayed off`
29258
+ };
29259
+ }
29260
+ if (visibleAsked) {
29261
+ if (visibleRaw.trim() !== "1") {
29262
+ return {
29263
+ asked,
29264
+ enabled: false,
29265
+ list: false,
29266
+ visible: false,
29267
+ reason: `${ADDRESS_OVERLAY_VISIBLE_ENV}=${visibleRaw.trim()} is not an opt-in (accepted: 1); the address overlay stayed off`
29268
+ };
29269
+ }
29270
+ if (!listAsked) {
29271
+ return {
29272
+ asked,
29273
+ enabled: false,
29274
+ list: false,
29275
+ visible: false,
29276
+ reason: `${ADDRESS_OVERLAY_VISIBLE_ENV} needs ${ADDRESS_LIST_ENV}=1: S3's visible probe shows S2's mirror, never a bare S1 child`
29277
+ };
29278
+ }
29279
+ if (input.windowMode === "headless") {
29280
+ return {
29281
+ asked,
29282
+ enabled: false,
29283
+ list: false,
29284
+ visible: false,
29285
+ reason: `${ADDRESS_OVERLAY_VISIBLE_ENV} refused in a "headless" window: a headless run labelled visible would misattribute its evidence; use --window-mode=inactive`
29286
+ };
29287
+ }
29288
+ if (input.windowMode !== "inactive") {
29289
+ return {
29290
+ asked,
29291
+ enabled: false,
29292
+ list: false,
29293
+ visible: false,
29294
+ reason: `${ADDRESS_OVERLAY_VISIBLE_ENV} refused in a "${input.windowMode}" window: a visible probe never runs in the product's own mode; use --window-mode=inactive`
29295
+ };
29296
+ }
29297
+ if (!input.devDriverArmed) {
29298
+ return {
29299
+ asked,
29300
+ enabled: false,
29301
+ list: false,
29302
+ visible: false,
29303
+ reason: "the visible address overlay needs the armed renderer dev driver; it has no product path"
29304
+ };
29305
+ }
29306
+ if (input.packaged === true) {
29307
+ return {
29308
+ asked,
29309
+ enabled: false,
29310
+ list: false,
29311
+ visible: false,
29312
+ reason: `${ADDRESS_OVERLAY_VISIBLE_ENV} refused in a packaged build: S3 is an unpackaged probe, never product surface`
29313
+ };
29314
+ }
29315
+ return {
29316
+ asked,
29317
+ enabled: true,
29318
+ list: true,
29319
+ visible: true,
29320
+ reason: null
29321
+ };
29322
+ }
28789
29323
  if (!input.devDriverArmed) {
28790
29324
  return {
28791
29325
  asked,
28792
29326
  enabled: false,
29327
+ list: false,
29328
+ visible: false,
28793
29329
  reason: "the address overlay needs the armed renderer dev driver; it has no user-facing trigger in S1"
28794
29330
  };
28795
29331
  }
@@ -28797,10 +29333,18 @@ function resolveAddressOverlayGate(input) {
28797
29333
  return {
28798
29334
  asked,
28799
29335
  enabled: false,
29336
+ list: false,
29337
+ visible: false,
28800
29338
  reason: `the address overlay refused to arm in a "${input.windowMode}" window; S1 has no user-facing path, so use --window-mode=headless`
28801
29339
  };
28802
29340
  }
28803
- return { asked, enabled: true, reason: null };
29341
+ return {
29342
+ asked,
29343
+ enabled: true,
29344
+ list: listAsked,
29345
+ visible: false,
29346
+ reason: null
29347
+ };
28804
29348
  }
28805
29349
  function int32(value) {
28806
29350
  if (typeof value !== "number" || !Number.isFinite(value)) return null;
@@ -28844,11 +29388,15 @@ function copyLayout(snapshot2) {
28844
29388
  };
28845
29389
  }
28846
29390
  function layoutKeyOf(snapshot2) {
28847
- const rect = snapshot2.contentRect;
28848
- return `${snapshot2.activeTabId}:${snapshot2.presented ? 1 : 0}:${rect ? `${rect.x},${rect.y},${rect.width},${rect.height}` : "null"}`;
29391
+ const rect2 = snapshot2.contentRect;
29392
+ return `${snapshot2.activeTabId}:${snapshot2.presented ? 1 : 0}:${rect2 ? `${rect2.x},${rect2.y},${rect2.width},${rect2.height}` : "null"}`;
29393
+ }
29394
+ function sameRect(a, b) {
29395
+ if (b === null) return false;
29396
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
28849
29397
  }
28850
29398
  const OVERLAY_BACKGROUND = "#1b1f24";
28851
- function createHardenedView() {
29399
+ function createHardenedView(options) {
28852
29400
  const partition = `address-overlay-${node_crypto.randomUUID()}`;
28853
29401
  const childSession = electron.session.fromPartition(partition);
28854
29402
  childSession.setPermissionRequestHandler(
@@ -28868,10 +29416,15 @@ function createHardenedView() {
28868
29416
  nodeIntegration: false,
28869
29417
  webSecurity: true,
28870
29418
  allowRunningInsecureContent: false,
28871
- // No `preload`, and no script in the document: nothing can ask for a
28872
- // bridge, and the renderer never runs JavaScript even if the document
28873
- // changes by accident.
28874
- javascript: false
29419
+ // No `preload`, and no app imports in either document. The flag is the
29420
+ // S1/S2 split: false for the inert document, true for the mirror list.
29421
+ javascript: options.javascript,
29422
+ // The mirror is a tiny document that must stay frame-accurate while the
29423
+ // owner window is hidden, unfocused or occluded (headless probe, a
29424
+ // background app window). Chromium otherwise throttles its frame loop,
29425
+ // and a native resize can sit unapplied for ~1 s — the exact interval
29426
+ // `measure_viewport` is there to catch. S1 keeps the default.
29427
+ ...options.javascript ? { backgroundThrottling: false } : {}
28875
29428
  }
28876
29429
  });
28877
29430
  view.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
@@ -28882,6 +29435,8 @@ class AddressOverlayController {
28882
29435
  window;
28883
29436
  documentUrl;
28884
29437
  createView;
29438
+ javascript;
29439
+ onEvent;
28885
29440
  readinessTimeoutMs;
28886
29441
  log;
28887
29442
  now;
@@ -28909,10 +29464,42 @@ class AddressOverlayController {
28909
29464
  disposed;
28910
29465
  ownerListeners;
28911
29466
  childListeners;
29467
+ // ---- S2 mirror state ----------------------------------------------------
29468
+ lastSeq;
29469
+ shownSeq;
29470
+ lastProjection;
29471
+ /** The rectangle last applied for a projection. `onLayout`'s unchanged-key
29472
+ * re-assert uses THIS, never the S1 top-strip rule: an S2 child sits at the
29473
+ * dock, and re-raising it with the S1 rectangle would move the list. */
29474
+ lastAppliedBounds;
29475
+ lastMeasurement;
29476
+ themeFact;
29477
+ applyCount;
29478
+ commitsEmitted;
29479
+ commitsDropped;
29480
+ hideCounts;
29481
+ keysBlocked;
29482
+ mouseDowns;
29483
+ mouseUps;
29484
+ mouseMoves;
29485
+ mouseDownRow;
29486
+ contextMenuBlocked;
29487
+ // ---- S3 focus facts -----------------------------------------------------
29488
+ /** Counted only for the scripted (S2/S3) child, so S1's listener set stays
29489
+ * exactly what it was; see `wireChild`'s S2 policy comment. */
29490
+ childFocusEvents;
29491
+ childBlurEvents;
29492
+ /** The launch gate's `visible` fact, copied in so `facts()` can report it
29493
+ * without re-deriving the gate (which it must never do: one pure decision). */
29494
+ visibleMode;
29495
+ lastRejected;
29496
+ projectQueue;
28912
29497
  constructor(deps) {
28913
29498
  this.window = deps.window;
28914
29499
  this.documentUrl = deps.documentUrl;
28915
- this.createView = deps.createView ?? createHardenedView;
29500
+ this.createView = deps.createView ?? (() => createHardenedView({ javascript: deps.javascript === true }));
29501
+ this.javascript = deps.javascript === true;
29502
+ this.onEvent = deps.onEvent ?? null;
28916
29503
  this.readinessTimeoutMs = deps.readinessTimeoutMs ?? ADDRESS_OVERLAY_READY_TIMEOUT_MS;
28917
29504
  this.log = deps.log ?? (() => {
28918
29505
  });
@@ -28946,6 +29533,27 @@ class AddressOverlayController {
28946
29533
  this.disposed = false;
28947
29534
  this.ownerListeners = [];
28948
29535
  this.childListeners = [];
29536
+ this.lastSeq = 0;
29537
+ this.shownSeq = -1;
29538
+ this.lastProjection = null;
29539
+ this.lastAppliedBounds = null;
29540
+ this.lastMeasurement = null;
29541
+ this.themeFact = "absent";
29542
+ this.applyCount = 0;
29543
+ this.commitsEmitted = 0;
29544
+ this.commitsDropped = /* @__PURE__ */ new Map();
29545
+ this.hideCounts = /* @__PURE__ */ new Map();
29546
+ this.keysBlocked = 0;
29547
+ this.mouseDowns = 0;
29548
+ this.mouseUps = 0;
29549
+ this.mouseMoves = 0;
29550
+ this.mouseDownRow = null;
29551
+ this.contextMenuBlocked = 0;
29552
+ this.childFocusEvents = 0;
29553
+ this.childBlurEvents = 0;
29554
+ this.visibleMode = deps.visible === true;
29555
+ this.lastRejected = null;
29556
+ this.projectQueue = Promise.resolve();
28949
29557
  this.attachOwnerListeners();
28950
29558
  }
28951
29559
  // ---- observer hooks ------------------------------------------------------
@@ -28975,7 +29583,7 @@ class AddressOverlayController {
28975
29583
  if (this.state !== "shown") return;
28976
29584
  const view = this.view;
28977
29585
  if (!view || this.viewUsable(view) === false) return;
28978
- const bounds = overlayBoundsFor(next.contentRect, this.ownerSize());
29586
+ const bounds = this.lastProjection !== null ? this.lastAppliedBounds : overlayBoundsFor(next.contentRect, this.ownerSize());
28979
29587
  if (bounds === null || !next.presented) {
28980
29588
  this.hide("layout_invalid");
28981
29589
  return;
@@ -29022,6 +29630,10 @@ class AddressOverlayController {
29022
29630
  this.generation += 1;
29023
29631
  this.settleReady(false);
29024
29632
  this.hiddenReason = reason;
29633
+ this.hideCounts.set(reason, (this.hideCounts.get(reason) ?? 0) + 1);
29634
+ this.shownSeq = -1;
29635
+ this.mouseDownRow = null;
29636
+ this.lastMeasurement = null;
29025
29637
  const view = this.view;
29026
29638
  if (view && this.viewUsable(view)) {
29027
29639
  try {
@@ -29034,6 +29646,14 @@ class AddressOverlayController {
29034
29646
  }
29035
29647
  this.log(`[browser] address overlay hidden: ${reason}`);
29036
29648
  }
29649
+ /** `WebContents.isFocused()` behind a guard: a disposed view throws. */
29650
+ childIsFocused(view) {
29651
+ try {
29652
+ return view.webContents.isFocused();
29653
+ } catch {
29654
+ return false;
29655
+ }
29656
+ }
29037
29657
  /** Facts only: identifiers, geometry and counters. No page content. */
29038
29658
  facts() {
29039
29659
  const view = this.view;
@@ -29056,7 +29676,10 @@ class AddressOverlayController {
29056
29676
  visible,
29057
29677
  bounds,
29058
29678
  order,
29059
- childCount
29679
+ childCount,
29680
+ focusEvents: this.childFocusEvents,
29681
+ blurEvents: this.childBlurEvents,
29682
+ isFocused: this.childIsFocused(view)
29060
29683
  };
29061
29684
  }
29062
29685
  let ownerDestroyed = true;
@@ -29067,6 +29690,18 @@ class AddressOverlayController {
29067
29690
  } catch {
29068
29691
  ownerDestroyed = true;
29069
29692
  }
29693
+ let ownerWindowFocused = false;
29694
+ try {
29695
+ ownerWindowFocused = !ownerDestroyed && this.window.isFocused();
29696
+ } catch {
29697
+ ownerWindowFocused = false;
29698
+ }
29699
+ let ownerFocusedContentsId = null;
29700
+ try {
29701
+ ownerFocusedContentsId = electron.webContents.getFocusedWebContents()?.id ?? null;
29702
+ } catch {
29703
+ ownerFocusedContentsId = null;
29704
+ }
29070
29705
  return {
29071
29706
  documentUrl: this.documentUrl,
29072
29707
  state: this.state,
@@ -29094,10 +29729,36 @@ class AddressOverlayController {
29094
29729
  },
29095
29730
  pendingShow: this.showInFlight !== null,
29096
29731
  readinessTimer: this.readinessTimer !== null,
29732
+ visibleMode: this.visibleMode,
29733
+ ownerFocusedContentsId,
29734
+ ownerWindowFocused,
29735
+ s2: {
29736
+ list: this.javascript,
29737
+ javascript: this.javascript,
29738
+ lastSeq: this.lastSeq,
29739
+ shownSeq: this.shownSeq,
29740
+ lastRejected: this.lastRejected,
29741
+ theme: this.themeFact,
29742
+ applyCount: this.applyCount,
29743
+ view: this.lastProjection === null ? { kind: "none", rowCount: 0, active: -1 } : {
29744
+ kind: this.lastProjection.view.kind,
29745
+ rowCount: this.lastProjection.view.kind === "rows" ? this.lastProjection.view.rows.length : 0,
29746
+ active: this.lastProjection.active
29747
+ },
29748
+ measured: this.lastMeasurement,
29749
+ commitsEmitted: this.commitsEmitted,
29750
+ commitsDropped: Object.fromEntries(this.commitsDropped),
29751
+ hideCounts: Object.fromEntries(this.hideCounts),
29752
+ keysBlocked: this.keysBlocked,
29753
+ mouseDowns: this.mouseDowns,
29754
+ mouseUps: this.mouseUps,
29755
+ mouseMoves: this.mouseMoves,
29756
+ contextMenuBlocked: this.contextMenuBlocked
29757
+ },
29097
29758
  metrics: this.appMetrics ? this.appMetrics() : null
29098
29759
  };
29099
29760
  }
29100
- async probe(action) {
29761
+ async probe(action, argument) {
29101
29762
  if (action === "show") {
29102
29763
  await this.showOverlay();
29103
29764
  return this.facts();
@@ -29106,8 +29767,278 @@ class AddressOverlayController {
29106
29767
  this.hide("probe");
29107
29768
  return this.facts();
29108
29769
  }
29770
+ if (action === "click") {
29771
+ this.probeClick(argument);
29772
+ await new Promise((done) => setTimeout(done, 40));
29773
+ return this.facts();
29774
+ }
29775
+ if (action === "measure") {
29776
+ await this.probeMeasure();
29777
+ return this.facts();
29778
+ }
29779
+ if (action === "key") {
29780
+ this.probeKey(argument);
29781
+ await new Promise((done) => setTimeout(done, 20));
29782
+ return this.facts();
29783
+ }
29109
29784
  return this.facts();
29110
29785
  }
29786
+ /**
29787
+ * Apply one renderer projection (S2). Everything arriving here was or will be
29788
+ * revalidated: JSON size, shape, every string, tab/layout freshness, zoom,
29789
+ * anchor geometry and — after the child paints — the child's own measurements.
29790
+ *
29791
+ * Serialized: a per-frame renderer can outpace `executeJavaScript`, and two
29792
+ * overlapping applies would let an older model win. The queue is per
29793
+ * controller, so a burst between frames costs one apply each in arrival order.
29794
+ */
29795
+ project(raw) {
29796
+ const run = () => this.projectOnce(raw);
29797
+ const next = this.projectQueue.then(run, run);
29798
+ this.projectQueue = next.then(
29799
+ () => void 0,
29800
+ () => void 0
29801
+ );
29802
+ return next;
29803
+ }
29804
+ async projectOnce(raw) {
29805
+ const parsed = sanitizeProjection(raw);
29806
+ if (!parsed.ok) {
29807
+ this.drop(parsed.reason);
29808
+ return {
29809
+ accepted: false,
29810
+ shown: false,
29811
+ reason: parsed.reason,
29812
+ bounds: null
29813
+ };
29814
+ }
29815
+ const projection = parsed.projection;
29816
+ this.themeFact = projection.themeFact;
29817
+ if (!projection.visible) {
29818
+ if (projection.seq > this.lastSeq) this.lastSeq = projection.seq;
29819
+ this.hide("projection_hidden");
29820
+ return { accepted: true, shown: false, reason: null, bounds: null };
29821
+ }
29822
+ if (this.disposed) {
29823
+ return this.refuseProjection("disposed");
29824
+ }
29825
+ if (projection.seq < this.lastSeq) {
29826
+ this.drop("stale_seq");
29827
+ return {
29828
+ accepted: false,
29829
+ shown: this.state === "shown",
29830
+ reason: "stale_seq",
29831
+ bounds: null
29832
+ };
29833
+ }
29834
+ if (projection.seq === this.lastSeq && projection.seq === this.shownSeq) {
29835
+ return {
29836
+ accepted: true,
29837
+ shown: this.state === "shown",
29838
+ reason: null,
29839
+ bounds: this.view && this.viewUsable(this.view) ? this.view.getBounds() : null
29840
+ };
29841
+ }
29842
+ if (this.failedReason) return this.refuseProjection("overlay_failed");
29843
+ if (this.ownerUnavailable()) {
29844
+ this.releaseChild("owner_unavailable");
29845
+ return this.refuseProjection("owner_unavailable");
29846
+ }
29847
+ const zoom = this.ownerZoom();
29848
+ if (zoom === null || Math.abs(zoom - 1) > 1e-3) {
29849
+ return this.refuseProjection("zoomed");
29850
+ }
29851
+ const layout = this.layout;
29852
+ if (projection.tabId !== layout.activeTabId) {
29853
+ return this.refuseProjection("stale_tab");
29854
+ }
29855
+ if (!layout.presented || !sameRect(projection.pageRect, layout.contentRect)) {
29856
+ return this.refuseProjection("stale_layout");
29857
+ }
29858
+ const bounds = addressListBoundsFor(
29859
+ projection.field,
29860
+ projection.listTop,
29861
+ projection.view,
29862
+ this.ownerSize()
29863
+ );
29864
+ if (bounds === null) return this.refuseProjection("anchor_inconsistent");
29865
+ this.generation += 1;
29866
+ const generation = this.generation;
29867
+ this.boundTabId = projection.tabId;
29868
+ if (!this.view) {
29869
+ const created = this.createChild(bounds);
29870
+ if (!created) {
29871
+ return this.refuseProjection(
29872
+ this.failedReason ?? "child_create_failed"
29873
+ );
29874
+ }
29875
+ }
29876
+ if (!(this.domReady && this.finished)) {
29877
+ this.state = "loading";
29878
+ const ready = await this.waitForReady();
29879
+ if (!ready)
29880
+ return this.refuseProjection(this.failedReason ?? "invalidated");
29881
+ }
29882
+ if (this.disposed || generation !== this.generation) {
29883
+ return this.refuseProjection("invalidated");
29884
+ }
29885
+ const view = this.view;
29886
+ if (!view || !this.viewUsable(view)) {
29887
+ return this.refuseProjection("child_unavailable");
29888
+ }
29889
+ view.setBounds(bounds);
29890
+ this.raiseChild(view);
29891
+ view.setVisible(true);
29892
+ const model = addressListViewModel(projection);
29893
+ let measured = null;
29894
+ let problem = null;
29895
+ try {
29896
+ for (let attempt = 0; attempt < 8; attempt += 1) {
29897
+ if (attempt % 2 === 1) view.setBounds(bounds);
29898
+ measured = await view.webContents.executeJavaScript(
29899
+ `window.__loAddressOverlay && window.__loAddressOverlay.apply(${JSON.stringify(model)})`,
29900
+ false
29901
+ );
29902
+ problem = addressListMeasurementProblem(
29903
+ measured,
29904
+ projection,
29905
+ bounds.height
29906
+ );
29907
+ if (problem === null) break;
29908
+ if (problem !== "measure_scroll" && problem !== "measure_viewport")
29909
+ break;
29910
+ await new Promise((done) => setTimeout(done, 150));
29911
+ }
29912
+ } catch {
29913
+ return this.refuseProjection("apply_error");
29914
+ }
29915
+ if (problem !== null) return this.refuseProjection(problem, bounds);
29916
+ this.applyCount += 1;
29917
+ this.lastSeq = projection.seq;
29918
+ this.shownSeq = projection.seq;
29919
+ this.lastProjection = projection;
29920
+ this.lastAppliedBounds = bounds;
29921
+ this.lastMeasurement = measured;
29922
+ this.lastRejected = null;
29923
+ this.state = "shown";
29924
+ this.hiddenReason = null;
29925
+ return { accepted: true, shown: true, reason: null, bounds };
29926
+ }
29927
+ refuseProjection(reason, bounds = null) {
29928
+ this.lastRejected = reason;
29929
+ this.drop(reason);
29930
+ this.hide(reason);
29931
+ return { accepted: false, shown: false, reason, bounds };
29932
+ }
29933
+ drop(reason) {
29934
+ this.commitsDropped.set(reason, (this.commitsDropped.get(reason) ?? 0) + 1);
29935
+ }
29936
+ ownerZoom() {
29937
+ try {
29938
+ const zoom = this.window.webContents.getZoomFactor();
29939
+ return typeof zoom === "number" && Number.isFinite(zoom) ? zoom : null;
29940
+ } catch {
29941
+ return null;
29942
+ }
29943
+ }
29944
+ rowAt(y) {
29945
+ if (this.state !== "shown") return null;
29946
+ if (this.lastProjection === null) return null;
29947
+ if (this.shownSeq !== this.lastSeq || this.shownSeq < 1) return null;
29948
+ const projection = this.lastProjection;
29949
+ if (projection.view.kind !== "rows") return null;
29950
+ const index = addressListRowIndexAt(projection.view, y);
29951
+ if (index === null || index >= projection.view.rows.length) return null;
29952
+ return index;
29953
+ }
29954
+ emitCommit(index) {
29955
+ const projection = this.lastProjection;
29956
+ const sink = this.onEvent;
29957
+ if (projection === null || sink === null) {
29958
+ this.drop("no_sink");
29959
+ return;
29960
+ }
29961
+ if (projection.view.kind !== "rows" || index >= projection.view.rows.length) {
29962
+ this.drop("commit_index");
29963
+ return;
29964
+ }
29965
+ this.commitsEmitted += 1;
29966
+ sink({ type: "commit", seq: projection.seq, index });
29967
+ this.log(
29968
+ `[browser] address overlay commit emitted: seq=${projection.seq} index=${index}`
29969
+ );
29970
+ }
29971
+ /**
29972
+ * Probe-only mouse path: `sendInputEvent` into the child's own input pipeline.
29973
+ * This exercises the child and the before-mouse-event observer; it does NOT
29974
+ * exercise macOS NSView hit-testing between sibling views (matrix V3).
29975
+ */
29976
+ probeClick(argument) {
29977
+ const view = this.view;
29978
+ const measurement = this.lastMeasurement;
29979
+ if (!view || !this.viewUsable(view) || !measurement) {
29980
+ throw new Error("address_overlay_click: no applied list to click");
29981
+ }
29982
+ const arg = argument ?? {};
29983
+ const row = arg.row;
29984
+ const upRow = arg.upRow === void 0 ? row : arg.upRow;
29985
+ const rows = measurement.rowRects;
29986
+ if (typeof row !== "number" || !Number.isInteger(row) || !rows[row]) {
29987
+ throw new Error(
29988
+ "address_overlay_click: row must be a rendered row index"
29989
+ );
29990
+ }
29991
+ if (typeof upRow !== "number" || !Number.isInteger(upRow)) {
29992
+ throw new Error("address_overlay_click: upRow must be an integer");
29993
+ }
29994
+ const point = (index) => {
29995
+ const rect2 = rows[index];
29996
+ const fallbackY = ADDRESS_LIST_HEADER_HEIGHT + 2 + (index + 0.5) * ADDRESS_LIST_ROW_HEIGHT;
29997
+ return rect2 ? {
29998
+ x: 40,
29999
+ y: ADDRESS_LIST_HEADER_HEIGHT + 2 + rect2.top + rect2.height / 2
30000
+ } : { x: 40, y: fallbackY };
30001
+ };
30002
+ const receive = (type2, index) => {
30003
+ const { x, y } = point(index);
30004
+ view.webContents.sendInputEvent({
30005
+ type: type2,
30006
+ x: Math.round(x),
30007
+ y: Math.round(y),
30008
+ button: "left",
30009
+ clickCount: 1
30010
+ });
30011
+ };
30012
+ receive("mouseMove", row);
30013
+ receive("mouseDown", row);
30014
+ receive("mouseUp", upRow);
30015
+ }
30016
+ /**
30017
+ * Probe-only key into the child's own input pipeline. It is how the gated
30018
+ * harness proves `before-input-event` swallows keys; CDP-injected keys go
30019
+ * through a different path and were measured NOT to fire it.
30020
+ */
30021
+ probeKey(argument) {
30022
+ const view = this.view;
30023
+ if (!view || !this.viewUsable(view)) {
30024
+ throw new Error("address_overlay_key: no child");
30025
+ }
30026
+ const arg = argument ?? {};
30027
+ const keyCode = typeof arg.keyCode === "string" && arg.keyCode.length > 0 ? arg.keyCode.slice(0, 32) : "A";
30028
+ view.webContents.sendInputEvent({ type: "keyDown", keyCode });
30029
+ view.webContents.sendInputEvent({ type: "keyUp", keyCode });
30030
+ }
30031
+ async probeMeasure() {
30032
+ const view = this.view;
30033
+ if (!view || !this.viewUsable(view)) {
30034
+ throw new Error("address_overlay_measure: no child");
30035
+ }
30036
+ const measured = await view.webContents.executeJavaScript(
30037
+ "window.__loAddressOverlay && window.__loAddressOverlay.measure()",
30038
+ false
30039
+ );
30040
+ this.lastMeasurement = measured;
30041
+ }
29111
30042
  /**
29112
30043
  * Idempotent teardown for the host's stop/rollback/window-close paths.
29113
30044
  * Closing the webContents before detaching is required: removing a
@@ -29319,6 +30250,54 @@ class AddressOverlayController {
29319
30250
  event?.preventDefault?.();
29320
30251
  this.log(`[browser] address overlay refused redirect to ${url}`);
29321
30252
  });
30253
+ if (!this.javascript) return;
30254
+ on(contents, "before-input-event", (...args) => {
30255
+ const event = args[0];
30256
+ event?.preventDefault?.();
30257
+ this.keysBlocked += 1;
30258
+ });
30259
+ on(contents, "context-menu", (...args) => {
30260
+ const event = args[0];
30261
+ event?.preventDefault?.();
30262
+ this.contextMenuBlocked += 1;
30263
+ });
30264
+ on(contents, "focus", () => {
30265
+ this.childFocusEvents += 1;
30266
+ });
30267
+ on(contents, "blur", () => {
30268
+ this.childBlurEvents += 1;
30269
+ });
30270
+ on(contents, "before-mouse-event", (...args) => {
30271
+ const event = args[0];
30272
+ const mouse = args[1];
30273
+ if (!mouse || typeof mouse.x !== "number" || typeof mouse.y !== "number") {
30274
+ return;
30275
+ }
30276
+ if (mouse.type === "mouseMove") {
30277
+ this.mouseMoves += 1;
30278
+ return;
30279
+ }
30280
+ event?.preventDefault?.();
30281
+ if (mouse.type === "mouseDown") {
30282
+ this.mouseDowns += 1;
30283
+ this.mouseDownRow = this.rowAt(mouse.y);
30284
+ return;
30285
+ }
30286
+ if (mouse.type !== "mouseUp") return;
30287
+ this.mouseUps += 1;
30288
+ const row = this.rowAt(mouse.y);
30289
+ const downRow = this.mouseDownRow;
30290
+ this.mouseDownRow = null;
30291
+ if (downRow === null || row === null) {
30292
+ this.drop("mouse_no_row");
30293
+ return;
30294
+ }
30295
+ if (downRow !== row) {
30296
+ this.drop("mouse_mismatch");
30297
+ return;
30298
+ }
30299
+ this.emitCommit(row);
30300
+ });
29322
30301
  }
29323
30302
  /** Terminal for this host lifetime: a broken document must not be retried. */
29324
30303
  failChild(reason) {
@@ -64082,8 +65061,8 @@ class TabRegistry {
64082
65061
  * hijacked window. Visiting the route again reports a fresh rect and restores
64083
65062
  * it. A null rect is also how the caller hides the view at teardown, so it must
64084
65063
  * never be swallowed on the way here (see `use-browser-chrome`). */
64085
- setContentRect(rect) {
64086
- this.contentRect = rect;
65064
+ setContentRect(rect2) {
65065
+ this.contentRect = rect2;
64087
65066
  this.applyLayout();
64088
65067
  }
64089
65068
  /** Full-window overlays hide the view (design 11.3): a native view paints
@@ -64109,16 +65088,16 @@ class TabRegistry {
64109
65088
  record.view.setVisible(record.presented);
64110
65089
  }
64111
65090
  this.layoutRevision += 1;
64112
- const rect = this.contentRect;
65091
+ const rect2 = this.contentRect;
64113
65092
  try {
64114
65093
  this.onLayout({
64115
65094
  activeTabId: this.activeTabId,
64116
65095
  presented: active?.presented === true,
64117
- contentRect: rect ? {
64118
- x: rect.x,
64119
- y: rect.y,
64120
- width: rect.width,
64121
- height: rect.height
65096
+ contentRect: rect2 ? {
65097
+ x: rect2.x,
65098
+ y: rect2.y,
65099
+ width: rect2.width,
65100
+ height: rect2.height
64122
65101
  } : null,
64123
65102
  revision: this.layoutRevision
64124
65103
  });
@@ -66287,9 +67266,9 @@ class BrowserHost {
66287
67266
  }
66288
67267
  /** Layout authority is the renderer's (design 11.2): it measures the content
66289
67268
  * area and reports it, and main applies it to the active tab only. */
66290
- setContentRect(rect) {
66291
- this.registry.setContentRect(rect);
66292
- if (rect === null) this.clearPageFocus();
67269
+ setContentRect(rect2) {
67270
+ this.registry.setContentRect(rect2);
67271
+ if (rect2 === null) this.clearPageFocus();
66293
67272
  return this.chromeState();
66294
67273
  }
66295
67274
  /** Full-window overlays hide the view: a native view paints above all DOM, so
@@ -67219,58 +68198,6 @@ function restrictMode(path2, log2) {
67219
68198
  );
67220
68199
  }
67221
68200
  }
67222
- const DEV_DRIVER_ENV = "LOCAL_OPERATOR_UI_DEV_DRIVER";
67223
- const DEV_DRIVER_OUT_ENV = "LOCAL_OPERATOR_UI_DEV_DRIVER_OUT";
67224
- const DEV_DRIVER_ARG = "--lo-dev-driver";
67225
- const DEV_DRIVER_CAPTURE = "dev-driver-capture";
67226
- const DEV_DRIVER_FACTS = "dev-driver-facts";
67227
- const DEV_DRIVER_ADDRESS_OVERLAY = "dev-driver-address-overlay";
67228
- const TRUTHY = /* @__PURE__ */ new Set(["1", "true"]);
67229
- function readOptIn(env) {
67230
- const raw = env[DEV_DRIVER_ENV];
67231
- if (raw === void 0 || raw.trim() === "")
67232
- return { asked: false, value: null };
67233
- return { asked: true, value: raw.trim() };
67234
- }
67235
- function resolveDevDriverArming(input) {
67236
- const off = (problems = []) => ({
67237
- armed: false,
67238
- outDir: null,
67239
- problems
67240
- });
67241
- const optIn = readOptIn(input.env);
67242
- if (!optIn.asked) return off();
67243
- if (!TRUTHY.has(optIn.value.toLowerCase())) {
67244
- return off([
67245
- `${DEV_DRIVER_ENV}=${optIn.value} is not an opt-in (accepted: 1, true); the renderer dev driver stayed off`
67246
- ]);
67247
- }
67248
- if (input.windowMode !== "headless" && input.windowMode !== "inactive") {
67249
- return off([
67250
- `the renderer dev driver refused to arm in a "${input.windowMode}" window; it never raises one, so use --window-mode=headless (or inactive)`
67251
- ]);
67252
- }
67253
- const outDir = input.env[DEV_DRIVER_OUT_ENV]?.trim() ?? "";
67254
- if (outDir === "" || !outDir.startsWith("/")) {
67255
- return off([
67256
- `${DEV_DRIVER_ENV} was set but ${DEV_DRIVER_OUT_ENV} is ${outDir === "" ? "missing" : `"${outDir}"`}; an absolute frames directory is required, so the driver did not arm`
67257
- ]);
67258
- }
67259
- return { armed: true, outDir, problems: [] };
67260
- }
67261
- function headlessExerciserAllowed(input) {
67262
- return input.arming.armed && input.windowMode === "headless";
67263
- }
67264
- function devDriverArgument(outDir) {
67265
- return `${DEV_DRIVER_ARG}=${outDir}`;
67266
- }
67267
- function describeDevDriverArming(arming) {
67268
- if (arming.armed) {
67269
- return `[dev-driver] ARMED; frames are written to ${arming.outDir}`;
67270
- }
67271
- if (arming.problems.length === 0) return null;
67272
- return `[dev-driver] not armed: ${arming.problems.join("; ")}`;
67273
- }
67274
68201
  class StartupRollback {
67275
68202
  owners = [];
67276
68203
  add(cleanup) {
@@ -67373,7 +68300,8 @@ const BROWSER_IPC_CHANNELS = [
67373
68300
  * `unregisterBrowserIpc` remove it on a stop/retry like every other channel
67374
68301
  * (removing an unregistered handler is a no-op).
67375
68302
  */
67376
- DEV_DRIVER_ADDRESS_OVERLAY
68303
+ DEV_DRIVER_ADDRESS_OVERLAY,
68304
+ DEV_DRIVER_ADDRESS_OVERLAY_PROJECT
67377
68305
  ];
67378
68306
  function registerBrowserIpc(options) {
67379
68307
  function authorizeSender(event) {
@@ -67518,9 +68446,9 @@ function registerBrowserIpc(options) {
67518
68446
  }
67519
68447
  return host.historyActive(direction);
67520
68448
  });
67521
- electron.ipcMain.handle("browser-set-content-rect", (event, rect) => {
68449
+ electron.ipcMain.handle("browser-set-content-rect", (event, rect2) => {
67522
68450
  const host = authorize(event);
67523
- return host.setContentRect(contentRectOrNull(rect));
68451
+ return host.setContentRect(contentRectOrNull(rect2));
67524
68452
  });
67525
68453
  electron.ipcMain.handle("browser-set-visible", (event, visible) => {
67526
68454
  const host = authorize(event);
@@ -67620,21 +68548,40 @@ function registerBrowserIpc(options) {
67620
68548
  return { retried: true };
67621
68549
  });
67622
68550
  if (options.addressOverlayProbe) {
67623
- electron.ipcMain.handle(DEV_DRIVER_ADDRESS_OVERLAY, (event, action) => {
67624
- authorizeSender(event);
67625
- if (action !== "show" && action !== "hide" && action !== "facts") {
67626
- throw new Error(
67627
- "address_overlay_invalid_action: expected 'show', 'hide' or 'facts'."
67628
- );
68551
+ electron.ipcMain.handle(
68552
+ DEV_DRIVER_ADDRESS_OVERLAY,
68553
+ (event, action, argument) => {
68554
+ authorizeSender(event);
68555
+ if (action !== "show" && action !== "hide" && action !== "facts" && action !== "click" && action !== "measure" && action !== "key") {
68556
+ throw new Error(
68557
+ "address_overlay_invalid_action: expected 'show', 'hide', 'facts', 'click', 'measure' or 'key'."
68558
+ );
68559
+ }
68560
+ const probe = options.addressOverlayProbe;
68561
+ if (!probe) {
68562
+ throw new Error(
68563
+ "address_overlay_unavailable: the overlay probe is not running."
68564
+ );
68565
+ }
68566
+ const readsArgument = action === "click" || action === "measure" || action === "key";
68567
+ return probe(action, readsArgument ? argument : void 0);
67629
68568
  }
67630
- const probe = options.addressOverlayProbe;
67631
- if (!probe) {
67632
- throw new Error(
67633
- "address_overlay_unavailable: the overlay probe is not running."
67634
- );
68569
+ );
68570
+ }
68571
+ if (options.addressOverlayProject) {
68572
+ electron.ipcMain.handle(
68573
+ DEV_DRIVER_ADDRESS_OVERLAY_PROJECT,
68574
+ (event, payload) => {
68575
+ authorizeSender(event);
68576
+ const project = options.addressOverlayProject;
68577
+ if (!project) {
68578
+ throw new Error(
68579
+ "address_overlay_unavailable: the overlay projection is not running."
68580
+ );
68581
+ }
68582
+ return project(payload);
67635
68583
  }
67636
- return probe(action);
67637
- });
68584
+ );
67638
68585
  }
67639
68586
  }
67640
68587
  function unregisterBrowserIpc() {
@@ -67699,17 +68646,17 @@ function contentRectOrNull(value) {
67699
68646
  if (value === null || value === void 0) return null;
67700
68647
  if (typeof value !== "object")
67701
68648
  throw new Error("Content rect must be an object.");
67702
- const rect = value;
68649
+ const rect2 = value;
67703
68650
  for (const key of ["x", "y", "width", "height"]) {
67704
- if (typeof rect[key] !== "number" || !Number.isFinite(rect[key])) {
68651
+ if (typeof rect2[key] !== "number" || !Number.isFinite(rect2[key])) {
67705
68652
  throw new Error("Content rect needs finite x, y, width and height.");
67706
68653
  }
67707
68654
  }
67708
68655
  return {
67709
- x: rect.x,
67710
- y: rect.y,
67711
- width: rect.width,
67712
- height: rect.height
68656
+ x: rect2.x,
68657
+ y: rect2.y,
68658
+ width: rect2.width,
68659
+ height: rect2.height
67713
68660
  };
67714
68661
  }
67715
68662
  function paneNavigationDirection(input) {
@@ -69619,15 +70566,30 @@ function startBrowserHost(options) {
69619
70566
  * replaces it) rather than capturing a controller here.
69620
70567
  */
69621
70568
  ...options.addressOverlay?.enabled ? {
69622
- addressOverlayProbe: (action) => {
70569
+ addressOverlayProbe: (action, argument) => {
69623
70570
  const probe = bindings.addressOverlayProbe;
69624
70571
  if (!probe) {
69625
70572
  throw new Error(
69626
70573
  "address_overlay_unavailable: the overlay controller is not running."
69627
70574
  );
69628
70575
  }
69629
- return probe(action);
69630
- }
70576
+ return probe(action, argument);
70577
+ },
70578
+ /* S2's projection. Registered only with the S1 probe, because the
70579
+ * projection can only drive a child the probe's gate also armed. The
70580
+ * current attempt's handler is read through `bindings` per call, like
70581
+ * the probe above, so a retry cannot leave a stale controller. */
70582
+ ...options.addressOverlay.list === true ? {
70583
+ addressOverlayProject: (payload) => {
70584
+ const project = bindings.addressOverlayProject;
70585
+ if (!project) {
70586
+ throw new Error(
70587
+ "address_overlay_unavailable: the overlay controller is not running."
70588
+ );
70589
+ }
70590
+ return project(payload);
70591
+ }
70592
+ } : {}
69631
70593
  } : {},
69632
70594
  log: options.log
69633
70595
  });
@@ -70017,6 +70979,18 @@ async function initializeBrowserHost(options, bindings) {
70017
70979
  const addressOverlay = options.addressOverlay?.enabled === true ? new AddressOverlayController({
70018
70980
  window: options.window,
70019
70981
  documentUrl: options.addressOverlay.documentUrl,
70982
+ javascript: options.addressOverlay.list === true,
70983
+ visible: options.addressOverlay.visible === true,
70984
+ // The only consumer of a validated row click: the owner window's own
70985
+ // renderer. Guarded because a stop can race a queued mouse event.
70986
+ onEvent: (event) => {
70987
+ if (!options.window.isDestroyed()) {
70988
+ options.window.webContents.send(
70989
+ DEV_DRIVER_ADDRESS_OVERLAY_EVENT,
70990
+ event
70991
+ );
70992
+ }
70993
+ },
70020
70994
  log: log2,
70021
70995
  appMetrics: () => electron.app.getAppMetrics().map((metric) => ({
70022
70996
  pid: metric.pid,
@@ -70325,13 +71299,13 @@ async function initializeBrowserHost(options, bindings) {
70325
71299
  );
70326
71300
  rollback.add(() => stateWriter?.clear());
70327
71301
  stateWriter.start(server.port, sessionKey);
70328
- const probeAddressOverlay = async (action) => {
71302
+ const probeAddressOverlay = async (action, argument) => {
70329
71303
  if (!addressOverlay) {
70330
71304
  throw new Error(
70331
71305
  "address_overlay_unavailable: this host owns no controller."
70332
71306
  );
70333
71307
  }
70334
- const facts2 = await addressOverlay.probe(action);
71308
+ const facts2 = await addressOverlay.probe(action, argument);
70335
71309
  const record = registry.activeTab;
70336
71310
  const page = record ? {
70337
71311
  tabId: record.tabId,
@@ -70341,8 +71315,19 @@ async function initializeBrowserHost(options, bindings) {
70341
71315
  } : null;
70342
71316
  return { ...facts2, page };
70343
71317
  };
71318
+ const projectAddressOverlay = (payload) => {
71319
+ if (!addressOverlay) {
71320
+ throw new Error(
71321
+ "address_overlay_unavailable: this host owns no controller."
71322
+ );
71323
+ }
71324
+ return addressOverlay.project(payload);
71325
+ };
70344
71326
  if (addressOverlay) {
70345
71327
  bindings.addressOverlayProbe = probeAddressOverlay;
71328
+ if (options.addressOverlay?.list === true) {
71329
+ bindings.addressOverlayProject = projectAddressOverlay;
71330
+ }
70346
71331
  }
70347
71332
  Object.assign(bindings, {
70348
71333
  window: () => options.window,
@@ -81704,11 +82689,12 @@ if (devDriverLine) console.log(devDriverLine);
81704
82689
  const addressOverlayGate = resolveAddressOverlayGate({
81705
82690
  env: launchEnv,
81706
82691
  devDriverArmed: devDriverArming.armed,
81707
- windowMode: windowLaunch.mode
82692
+ windowMode: windowLaunch.mode,
82693
+ packaged: electron.app.isPackaged
81708
82694
  });
81709
82695
  if (addressOverlayGate.asked) {
81710
82696
  console.log(
81711
- addressOverlayGate.enabled ? "[address-overlay] ARMED (S1 probe: native child, no product path)" : `[address-overlay] not armed: ${addressOverlayGate.reason}`
82697
+ addressOverlayGate.enabled ? addressOverlayGate.visible ? "[address-overlay] ARMED (S3 VISIBLE probe, inactive window, 120 s cap, no product path)" : `[address-overlay] ARMED (S${addressOverlayGate.list ? "2 probe: mirror list" : "1 probe: native child"}, no product path)` : `[address-overlay] not armed: ${addressOverlayGate.reason}`
81712
82698
  );
81713
82699
  }
81714
82700
  const devDriverWebPreferences = devDriverArming.armed && devDriverArming.outDir ? { additionalArguments: [devDriverArgument(devDriverArming.outDir)] } : {};
@@ -81982,7 +82968,10 @@ electron.app.whenReady().then(async () => {
81982
82968
  const launcher = resolveLauncherWatchPlan({
81983
82969
  mode: windowLaunch.mode,
81984
82970
  launcherPid: process.ppid,
81985
- env: launchEnv
82971
+ env: launchEnv,
82972
+ // S3 only: the one visible run nobody is watching still must not
82973
+ // outlive its launcher. False for every product launch.
82974
+ boundedVisibleProbe: addressOverlayGate.visible
81986
82975
  });
81987
82976
  logger.info(`[window-mode] ${launcher.reason}`, LogFileType.BACKEND);
81988
82977
  if (windowLaunch.mode !== "normal") {
@@ -81997,12 +82986,23 @@ electron.app.whenReady().then(async () => {
81997
82986
  onLauncherGone: ({ message, detail }) => endHeadlessRun(message, detail)
81998
82987
  });
81999
82988
  }
82000
- if (windowLaunch.mode === "headless") {
82989
+ if (addressOverlayGate.visible) {
82990
+ const cap = setTimeout(() => {
82991
+ endHeadlessRun(
82992
+ `the visible address-overlay probe reached its ${ADDRESS_OVERLAY_VISIBLE_MAX_MS / 1e3} s cap`
82993
+ );
82994
+ }, ADDRESS_OVERLAY_VISIBLE_MAX_MS);
82995
+ cap.unref();
82996
+ console.log(
82997
+ `[address-overlay] visible probe cap: ${ADDRESS_OVERLAY_VISIBLE_MAX_MS / 1e3} s from launch`
82998
+ );
82999
+ }
83000
+ if (windowLaunch.mode === "headless" || addressOverlayGate.visible) {
82001
83001
  for (const signal of ["SIGTERM", "SIGINT"]) {
82002
83002
  process.on(signal, () => endHeadlessRun(`received ${signal}`));
82003
83003
  }
82004
83004
  }
82005
- if (windowLaunch.mode !== "headless") {
83005
+ if (windowLaunch.mode !== "headless" && !addressOverlayGate.visible) {
82006
83006
  const onSigtermDuringQuit = () => {
82007
83007
  if (!quitState.isQuitting()) {
82008
83008
  process.removeListener("SIGTERM", onSigtermDuringQuit);
@@ -82114,7 +83114,7 @@ electron.app.whenReady().then(async () => {
82114
83114
  });
82115
83115
  const rendererUrl = rendererDocumentUrl();
82116
83116
  const consoleCaptureUrl = consoleCaptureUrlFor(rendererUrl);
82117
- const addressOverlayDocumentUrl = addressOverlayDocumentUrlFor(rendererUrl);
83117
+ const addressOverlayDocumentUrl = addressOverlayGate.list ? addressListDocumentUrlFor(rendererUrl) : addressOverlayDocumentUrlFor(rendererUrl);
82118
83118
  backendService.observeDesktopFeed(
82119
83119
  (frame) => {
82120
83120
  if (frame.type === "notification") {
@@ -82928,6 +83928,8 @@ electron.app.whenReady().then(async () => {
82928
83928
  ...addressOverlayGate.enabled ? {
82929
83929
  addressOverlay: {
82930
83930
  enabled: true,
83931
+ list: addressOverlayGate.list,
83932
+ visible: addressOverlayGate.visible,
82931
83933
  documentUrl: addressOverlayDocumentUrl
82932
83934
  }
82933
83935
  } : {},