local-operator-ui 0.39.4 → 0.39.6

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 (95) hide show
  1. package/out/main/index.js +844 -105
  2. package/out/preload/index.js +12 -0
  3. package/out/renderer/assets/{_basePickBy-DpOw3Tny.js → _basePickBy-CEM-62na.js} +1 -1
  4. package/out/renderer/assets/{_baseUniq-CloF1Z39.js → _baseUniq-DKUG2lEn.js} +1 -1
  5. package/out/renderer/assets/{addressOverlayList-8wdZRD_t.css → addressOverlayList-B7X6o95l.css} +1 -1
  6. package/out/renderer/assets/{addressOverlayList-BEh9WU4R.js → addressOverlayList-SGt8bawf.js} +1 -1
  7. package/out/renderer/assets/{agent-details-page-B-wOn-JJ.js → agent-details-page-uwmizvcA.js} +1 -1
  8. package/out/renderer/assets/{agent-hub-page-kOIRpx4R.js → agent-hub-page-DY0hrFjh.js} +1 -1
  9. package/out/renderer/assets/{agents-page-A6mqclDx.js → agents-page-B82GhyEm.js} +3 -3
  10. package/out/renderer/assets/{arc-C-USYSJX.js → arc-CwS59wEx.js} +1 -1
  11. package/out/renderer/assets/{architectureDiagram-IEHRJDOE-BStVetpz.js → architectureDiagram-IEHRJDOE-CrSOPHPj.js} +1 -1
  12. package/out/renderer/assets/{blockDiagram-JOT3LUYC-DFraQTdY.js → blockDiagram-JOT3LUYC-DOKW2aS1.js} +1 -1
  13. package/out/renderer/assets/{book-open-CbkGl_K7.js → book-open-BrVlmR50.js} +1 -1
  14. package/out/renderer/assets/browser-page-kxT2NYqz.js +1 -0
  15. package/out/renderer/assets/{browser-webauthn-prompt-CzS7qaIE.js → browser-webauthn-prompt-D4Pmblc_.js} +1 -1
  16. package/out/renderer/assets/{building-2-cVqkqSs_.js → building-2-BbJxBV3t.js} +1 -1
  17. package/out/renderer/assets/{c4Diagram-VJAJSXHY-Bpo5tV9E.js → c4Diagram-VJAJSXHY-BNZOSwVL.js} +1 -1
  18. package/out/renderer/assets/channel-D2-Cirxc.js +1 -0
  19. package/out/renderer/assets/{chunk-4BMEZGHF-BvVdPPZL.js → chunk-4BMEZGHF-DatYJ8Pc.js} +1 -1
  20. package/out/renderer/assets/{chunk-A2AXSNBT-DtDHNfFN.js → chunk-A2AXSNBT-kofNI-nh.js} +1 -1
  21. package/out/renderer/assets/{chunk-AEK57VVT-Cy4jpw-R.js → chunk-AEK57VVT-RG-vyDkt.js} +1 -1
  22. package/out/renderer/assets/{chunk-D6G4REZN-DvaHPHsB.js → chunk-D6G4REZN-DbgxdXuD.js} +1 -1
  23. package/out/renderer/assets/{chunk-RZ5BOZE2-CEKwLFh6.js → chunk-RZ5BOZE2-Pwbky5G7.js} +1 -1
  24. package/out/renderer/assets/{chunk-XZIHB7SX-CSpNkV4-.js → chunk-XZIHB7SX-nMQwH5Ev.js} +1 -1
  25. package/out/renderer/assets/classDiagram-GIVACNV2-D1u4IPED.js +1 -0
  26. package/out/renderer/assets/classDiagram-v2-COTLJTTW-D1u4IPED.js +1 -0
  27. package/out/renderer/assets/clone-D45gbF22.js +1 -0
  28. package/out/renderer/assets/{console-mirror-3ddzbqxj.js → console-mirror-B7HDmLVb.js} +1 -1
  29. package/out/renderer/assets/{consoleCapture-CUH-HGgu.js → consoleCapture-wuvuMPlf.js} +1 -1
  30. package/out/renderer/assets/{conversation-input-sync-C16E6bmc.js → conversation-input-sync-DeQTtOSG.js} +172 -172
  31. package/out/renderer/assets/{dagre-OKDRZEBW-CZM6WQD8.js → dagre-OKDRZEBW-BXF3VqDs.js} +1 -1
  32. package/out/renderer/assets/{diagram-SSKATNLV-DhfdsL7X.js → diagram-SSKATNLV-C6ZzdCUp.js} +1 -1
  33. package/out/renderer/assets/{diagram-VNBRO52H-6SKM5YIe.js → diagram-VNBRO52H-ND-gVRs3.js} +1 -1
  34. package/out/renderer/assets/{erDiagram-Q7BY3M3F-Dr-mAd-w.js → erDiagram-Q7BY3M3F-DJ8vCpMT.js} +1 -1
  35. package/out/renderer/assets/{error-boundary-BQkbSeb-.js → error-boundary-D81PAlVn.js} +3 -3
  36. package/out/renderer/assets/{flowDiagram-4HSFHLVR-XzjnYaiy.js → flowDiagram-4HSFHLVR-C0lG2NYg.js} +1 -1
  37. package/out/renderer/assets/{ganttDiagram-APWFNJXF-BiDYLq2r.js → ganttDiagram-APWFNJXF-BBxt5Pgj.js} +1 -1
  38. package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-CHG8kjsH.js → gitGraphDiagram-7IBYFJ6S-B5eei6E_.js} +1 -1
  39. package/out/renderer/assets/{graph-CCrDrJrv.js → graph-lWFNtwh7.js} +1 -1
  40. package/out/renderer/assets/{hash-m3f0Pwzh.js → hash-3k6_lTgk.js} +1 -1
  41. package/out/renderer/assets/index-BtI7uak5.js +835 -0
  42. package/out/renderer/assets/index-DsY6xpO9.js +57 -0
  43. package/out/renderer/assets/{index-D1rKdut2.js → index-FqHXqlQT.js} +1 -1
  44. package/out/renderer/assets/index-ZC-NNW64.css +1 -0
  45. package/out/renderer/assets/{index-DQdKGxDM.js → index-tyh1i-0R.js} +1 -1
  46. package/out/renderer/assets/{infoDiagram-PH2N3AL5-Boc-74q5.js → infoDiagram-PH2N3AL5-DfdIGj2q.js} +1 -1
  47. package/out/renderer/assets/{installer-DIGUKrBB.js → installer-OLhNHLbA.js} +1 -1
  48. package/out/renderer/assets/{journeyDiagram-U35MCT3I-D7_ElfaN.js → journeyDiagram-U35MCT3I-DY36aDPm.js} +1 -1
  49. package/out/renderer/assets/{kanban-definition-NDS4AKOZ-C6QsJ5yF.js → kanban-definition-NDS4AKOZ-DkmkP4pt.js} +1 -1
  50. package/out/renderer/assets/{layout-aAj5fMFf.js → layout-Wh_eVbud.js} +1 -1
  51. package/out/renderer/assets/{legacy-agents-page-USk5n9oW.js → legacy-agents-page-m5-4VKbu.js} +1 -1
  52. package/out/renderer/assets/{library-page-D8DpGQTs.js → library-page-vUP7gWsI.js} +3 -3
  53. package/out/renderer/assets/{mermaid.core-3ySvSsav.js → mermaid.core-CIUeSke9.js} +5 -5
  54. package/out/renderer/assets/{mesh-page-DzMTk3NO.js → mesh-page-CoUCZ19i.js} +1 -1
  55. package/out/renderer/assets/{mindmap-definition-ALO5MXBD-B0nPGBao.js → mindmap-definition-ALO5MXBD-E7Cl2drf.js} +1 -1
  56. package/out/renderer/assets/{mini-DFKXChgT.js → mini-D6HdkQvd.js} +1 -1
  57. package/out/renderer/assets/{notebook-pen-D0aUl8AG.js → notebook-pen-CiUgTpp-.js} +1 -1
  58. package/out/renderer/assets/{org-access-HADVafzg.js → org-access-DMUk8SEr.js} +1 -1
  59. package/out/renderer/assets/{org-surface-gate-DgSDgcYC.js → org-surface-gate-DdKUA8S8.js} +1 -1
  60. package/out/renderer/assets/{page-header-gQzwRfwk.js → page-header-DyRHEB-L.js} +1 -1
  61. package/out/renderer/assets/{pieDiagram-IB7DONF6-BiqgPuyx.js → pieDiagram-IB7DONF6-C3KFzuOU.js} +1 -1
  62. package/out/renderer/assets/{projects-page-ZOe4bm_U.js → projects-page-BdU99VZf.js} +1 -1
  63. package/out/renderer/assets/{publication-validation-hQvjDgK-.js → publication-validation-Cv6lnH83.js} +1 -1
  64. package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-DIWtZVho.js → quadrantDiagram-7GDLP6J5-DommBk0z.js} +1 -1
  65. package/out/renderer/assets/{radar-MK3ICKWK-2Q646pxQ.js → radar-MK3ICKWK-D5VUcmjY.js} +1 -1
  66. package/out/renderer/assets/{radient-auth-buttons-CfbkjaWR.js → radient-auth-buttons-BXc5xVU7.js} +1 -1
  67. package/out/renderer/assets/{requirementDiagram-KVF5MWMF-BgM3WO01.js → requirementDiagram-KVF5MWMF-RSR1-rSs.js} +1 -1
  68. package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-B0mmrM-5.js → sankeyDiagram-QLVOVGJD-Caojpkpm.js} +1 -1
  69. package/out/renderer/assets/{schedules-page-CSLJTbio.js → schedules-page-BWM0NUo9.js} +1 -1
  70. package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-D-_IaAU6.js → sequenceDiagram-X6HHIX6F-CrZCUpMQ.js} +1 -1
  71. package/out/renderer/assets/{settings-page-DMJjQldW.js → settings-page-sXBnyGof.js} +1 -1
  72. package/out/renderer/assets/{smartart-Bko--HTI.js → smartart-BJStpUh7.js} +1 -1
  73. package/out/renderer/assets/{stateDiagram-DGXRK772-CuY0nRt2.js → stateDiagram-DGXRK772-PFp2Dzsd.js} +1 -1
  74. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-DhzpDrem.js +1 -0
  75. package/out/renderer/assets/{team-bridge-page-BSiDGZ0J.js → team-bridge-page-pZkYpCnA.js} +1 -1
  76. package/out/renderer/assets/{timeline-definition-BDJGKUSR-CRyMxdHE.js → timeline-definition-BDJGKUSR-BI2T4N_q.js} +1 -1
  77. package/out/renderer/assets/{ui-preferences-store-Bop4_ZlF.js → ui-preferences-store-BZs48Tq1.js} +1 -1
  78. package/out/renderer/assets/{use-download-agent-mutation-C8Fg7G5t.js → use-download-agent-mutation-B3ghS441.js} +1 -1
  79. package/out/renderer/assets/{use-session-snapshot-Bg0gSZ12.js → use-session-snapshot-BbulmIXL.js} +1 -1
  80. package/out/renderer/assets/{xychartDiagram-VJFVF3MP-CEgHjMik.js → xychartDiagram-VJFVF3MP-CuNt7ik-.js} +1 -1
  81. package/out/renderer/browser-address-overlay-list.html +2 -2
  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 +2 -2
  87. package/out/renderer/assets/browser-page-BZouue9V.js +0 -1
  88. package/out/renderer/assets/channel-Dw6-NFJz.js +0 -1
  89. package/out/renderer/assets/classDiagram-GIVACNV2-CadtvAj7.js +0 -1
  90. package/out/renderer/assets/classDiagram-v2-COTLJTTW-CadtvAj7.js +0 -1
  91. package/out/renderer/assets/clone-Bfys-Y9K.js +0 -1
  92. package/out/renderer/assets/index-DGTlmx84.js +0 -57
  93. package/out/renderer/assets/index-DULvJq2X.js +0 -830
  94. package/out/renderer/assets/index-Dyn0TMCv.css +0 -1
  95. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-rk6LIoPS.js +0 -1
package/out/main/index.js CHANGED
@@ -28839,6 +28839,7 @@ const ADDRESS_LIST_ROWS_MAX = 5;
28839
28839
  const ADDRESS_LIST_MAX_HEIGHT_DIP = 212;
28840
28840
  const ADDRESS_LIST_NOTICE_HEIGHT_DIP = 104;
28841
28841
  const ADDRESS_LIST_ANCHOR_GAP_MAX = 24;
28842
+ const ADDRESS_LIST_MIN_WIDTH_DIP = 240;
28842
28843
  const ADDRESS_LIST_MAX_PAYLOAD_BYTES = 16 * 1024;
28843
28844
  const ADDRESS_LIST_MAX_TEXT = 512;
28844
28845
  const ADDRESS_LIST_MAX_TITLE = 256;
@@ -28847,13 +28848,12 @@ const ADDRESS_LIST_THEME_FALLBACK = "#1b1f24";
28847
28848
  const DIP_INT32_MAX$1 = 2 ** 31 - 1;
28848
28849
  const NOTICE_KINDS = [
28849
28850
  "error",
28850
- "hint",
28851
28851
  "empty",
28852
28852
  "no_match"
28853
28853
  ];
28854
28854
  const HOSTILE_STRING = /[\p{Cc}\u2028\u2029\u202A-\u202E\u2066-\u2069]/gu;
28855
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([
28856
+ const PROJECTION_KEYS_V1 = /* @__PURE__ */ new Set([
28857
28857
  "v",
28858
28858
  "seq",
28859
28859
  "tabId",
@@ -28867,6 +28867,20 @@ const PROJECTION_KEYS = /* @__PURE__ */ new Set([
28867
28867
  "dir",
28868
28868
  "theme"
28869
28869
  ]);
28870
+ const PROJECTION_KEYS_V2 = /* @__PURE__ */ new Set([
28871
+ "v",
28872
+ "seq",
28873
+ "tabId",
28874
+ "visible",
28875
+ "pageRect",
28876
+ "anchor",
28877
+ "active",
28878
+ "view",
28879
+ "label",
28880
+ "dir",
28881
+ "theme"
28882
+ ]);
28883
+ const ANCHOR_KEYS = ["x", "width"];
28870
28884
  const RECT_KEYS = ["x", "y", "width", "height"];
28871
28885
  const THEME_KEYS = [
28872
28886
  "elevated",
@@ -28947,10 +28961,10 @@ function sanitizeProjection(raw) {
28947
28961
  return { ok: false, reason: "payload_too_large" };
28948
28962
  }
28949
28963
  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" };
28964
+ if (raw.v !== 1 && raw.v !== 2) return { ok: false, reason: "bad_version" };
28965
+ const version = raw.v;
28966
+ const keys = version === 2 ? PROJECTION_KEYS_V2 : PROJECTION_KEYS_V1;
28967
+ if (!hasOnlyKeys(raw, keys)) return { ok: false, reason: "unknown_key" };
28954
28968
  if (!Number.isInteger(raw.seq) || raw.seq < 1) {
28955
28969
  return { ok: false, reason: "bad_seq" };
28956
28970
  }
@@ -28959,15 +28973,29 @@ function sanitizeProjection(raw) {
28959
28973
  return { ok: false, reason: "bad_visible" };
28960
28974
  }
28961
28975
  if (!raw.visible) {
28976
+ const empty = { x: 0, y: 0, width: 0, height: 0 };
28962
28977
  return {
28963
28978
  ok: true,
28964
- projection: {
28979
+ projection: version === 2 ? {
28980
+ v: 2,
28981
+ seq: raw.seq,
28982
+ tabId: raw.tabId,
28983
+ visible: false,
28984
+ pageRect: empty,
28985
+ anchor: { x: 0, width: 0 },
28986
+ active: -1,
28987
+ view: { kind: "notice", notice: "empty", text: "" },
28988
+ label: "",
28989
+ dir: "ltr",
28990
+ theme: null,
28991
+ themeFact: "absent"
28992
+ } : {
28965
28993
  v: 1,
28966
28994
  seq: raw.seq,
28967
28995
  tabId: raw.tabId,
28968
28996
  visible: false,
28969
- pageRect: { x: 0, y: 0, width: 0, height: 0 },
28970
- field: { x: 0, y: 0, width: 0, height: 0 },
28997
+ pageRect: empty,
28998
+ field: empty,
28971
28999
  listTop: 0,
28972
29000
  active: -1,
28973
29001
  view: { kind: "notice", notice: "empty", text: "" },
@@ -28979,14 +29007,30 @@ function sanitizeProjection(raw) {
28979
29007
  };
28980
29008
  }
28981
29009
  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" };
29010
+ if (pageRect === null) return { ok: false, reason: "bad_rect" };
29011
+ let field = null;
29012
+ let listTop = 0;
29013
+ let anchor = null;
29014
+ if (version === 2) {
29015
+ if (!isRecord$1(raw.anchor) || !hasOnlyKeys(raw.anchor, ANCHOR_KEYS)) {
29016
+ return { ok: false, reason: "bad_anchor" };
29017
+ }
29018
+ const x = finite(raw.anchor.x);
29019
+ const width = finite(raw.anchor.width);
29020
+ if (x === null || width === null) {
29021
+ return { ok: false, reason: "bad_anchor" };
29022
+ }
29023
+ if (width <= 0) return { ok: false, reason: "bad_anchor" };
29024
+ anchor = { x, width };
29025
+ } else {
29026
+ field = rect(raw.field);
29027
+ if (field === null) return { ok: false, reason: "bad_rect" };
29028
+ const top = finite(raw.listTop);
29029
+ if (top === null) return { ok: false, reason: "bad_list_top" };
29030
+ listTop = top;
29031
+ if (field.width <= 0 || field.height <= 0) {
29032
+ return { ok: false, reason: "bad_field" };
29033
+ }
28990
29034
  }
28991
29035
  if (!Number.isInteger(raw.active)) return { ok: false, reason: "bad_active" };
28992
29036
  const active = raw.active;
@@ -29026,6 +29070,27 @@ function sanitizeProjection(raw) {
29026
29070
  return { ok: false, reason: "bad_dir" };
29027
29071
  }
29028
29072
  const { theme, fact } = sanitizeTheme(raw.theme);
29073
+ if (version === 2) {
29074
+ if (anchor === null) return { ok: false, reason: "bad_anchor" };
29075
+ return {
29076
+ ok: true,
29077
+ projection: {
29078
+ v: 2,
29079
+ seq: raw.seq,
29080
+ tabId: raw.tabId,
29081
+ visible: true,
29082
+ pageRect,
29083
+ anchor,
29084
+ active,
29085
+ view,
29086
+ label,
29087
+ dir: raw.dir,
29088
+ theme,
29089
+ themeFact: fact
29090
+ }
29091
+ };
29092
+ }
29093
+ if (field === null) return { ok: false, reason: "bad_rect" };
29029
29094
  return {
29030
29095
  ok: true,
29031
29096
  projection: {
@@ -29079,6 +29144,26 @@ function addressListBoundsFor(field, listTop, view, owner) {
29079
29144
  if (bottom - top !== height) return null;
29080
29145
  return { x: left, y: top, width: right - left, height };
29081
29146
  }
29147
+ function addressListBoundsV2For(anchor, pageRect, view, owner) {
29148
+ const ax = int32$1(anchor.x);
29149
+ const aw = int32$1(anchor.width);
29150
+ const px = int32$1(pageRect.x);
29151
+ const pw = int32$1(pageRect.width);
29152
+ const py = int32$1(pageRect.y);
29153
+ const ownerWidth = int32$1(owner.width);
29154
+ const ownerHeight = int32$1(owner.height);
29155
+ if (ax === null || aw === null || px === null || pw === null || py === null || ownerWidth === null || ownerHeight === null) {
29156
+ return null;
29157
+ }
29158
+ if (aw <= 0 || pw <= 0 || ownerWidth <= 0 || ownerHeight <= 0) return null;
29159
+ const left = Math.max(0, Math.max(ax, px));
29160
+ const right = Math.min(ownerWidth, Math.min(ax + aw, px + pw));
29161
+ if (right - left < ADDRESS_LIST_MIN_WIDTH_DIP) return null;
29162
+ const height = addressListHeightFor(view);
29163
+ if (height > ADDRESS_LIST_MAX_HEIGHT_DIP) return null;
29164
+ if (py < 0 || py + height > ownerHeight) return null;
29165
+ return { x: left, y: py, width: right - left, height };
29166
+ }
29082
29167
  function addressListRowIndexAt(view, offsetY) {
29083
29168
  if (view.kind !== "rows") return null;
29084
29169
  if (!Number.isFinite(offsetY)) return null;
@@ -29391,6 +29476,10 @@ function layoutKeyOf(snapshot2) {
29391
29476
  const rect2 = snapshot2.contentRect;
29392
29477
  return `${snapshot2.activeTabId}:${snapshot2.presented ? 1 : 0}:${rect2 ? `${rect2.x},${rect2.y},${rect2.width},${rect2.height}` : "null"}`;
29393
29478
  }
29479
+ function layoutContentKeyOf(snapshot2) {
29480
+ const rect2 = snapshot2.contentRect;
29481
+ return `${snapshot2.activeTabId}:${rect2 ? `${rect2.x},${rect2.y},${rect2.width},${rect2.height}` : "null"}`;
29482
+ }
29394
29483
  function sameRect(a, b) {
29395
29484
  if (b === null) return false;
29396
29485
  return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
@@ -29437,6 +29526,7 @@ class AddressOverlayController {
29437
29526
  createView;
29438
29527
  javascript;
29439
29528
  onEvent;
29529
+ onHealthChange;
29440
29530
  readinessTimeoutMs;
29441
29531
  log;
29442
29532
  now;
@@ -29452,6 +29542,9 @@ class AddressOverlayController {
29452
29542
  lastShow;
29453
29543
  layout;
29454
29544
  layoutKey;
29545
+ /** The layout's content identity (tab + rect), the fact a product dismissal
29546
+ * is allowed to rest on; see `layoutContentKeyOf`. */
29547
+ layoutContentKey;
29455
29548
  boundTabId;
29456
29549
  domReady;
29457
29550
  finished;
@@ -29492,14 +29585,25 @@ class AddressOverlayController {
29492
29585
  /** The launch gate's `visible` fact, copied in so `facts()` can report it
29493
29586
  * without re-deriving the gate (which it must never do: one pure decision). */
29494
29587
  visibleMode;
29588
+ product;
29589
+ warmScheduled;
29495
29590
  lastRejected;
29496
29591
  projectQueue;
29592
+ /** The native page is hidden for a reason that says nothing about the popout
29593
+ * (the page menu suppresses it while its panel is open). The row stays
29594
+ * raised, so the child's paint stops and its "shown" state does not. */
29595
+ presentationSuspended;
29596
+ /** A projection that arrived while the page was suppressed. Applied on
29597
+ * resume rather than refused: a refusal would make the mirror re-project
29598
+ * every frame until the menu closed. */
29599
+ deferredProjection;
29497
29600
  constructor(deps) {
29498
29601
  this.window = deps.window;
29499
29602
  this.documentUrl = deps.documentUrl;
29500
29603
  this.createView = deps.createView ?? (() => createHardenedView({ javascript: deps.javascript === true }));
29501
29604
  this.javascript = deps.javascript === true;
29502
29605
  this.onEvent = deps.onEvent ?? null;
29606
+ this.onHealthChange = deps.onHealthChange ?? null;
29503
29607
  this.readinessTimeoutMs = deps.readinessTimeoutMs ?? ADDRESS_OVERLAY_READY_TIMEOUT_MS;
29504
29608
  this.log = deps.log ?? (() => {
29505
29609
  });
@@ -29521,6 +29625,7 @@ class AddressOverlayController {
29521
29625
  revision: 0
29522
29626
  };
29523
29627
  this.layoutKey = "none";
29628
+ this.layoutContentKey = "none";
29524
29629
  this.boundTabId = null;
29525
29630
  this.domReady = false;
29526
29631
  this.finished = false;
@@ -29552,8 +29657,12 @@ class AddressOverlayController {
29552
29657
  this.childFocusEvents = 0;
29553
29658
  this.childBlurEvents = 0;
29554
29659
  this.visibleMode = deps.visible === true;
29660
+ this.product = deps.product === true;
29661
+ this.warmScheduled = false;
29555
29662
  this.lastRejected = null;
29556
29663
  this.projectQueue = Promise.resolve();
29664
+ this.presentationSuspended = false;
29665
+ this.deferredProjection = null;
29557
29666
  this.attachOwnerListeners();
29558
29667
  }
29559
29668
  // ---- observer hooks ------------------------------------------------------
@@ -29561,25 +29670,41 @@ class AddressOverlayController {
29561
29670
  * The registry's post-layout hook. Called after every page layout pass,
29562
29671
  * including null and suppressed ones.
29563
29672
  *
29564
- * A changed key (tab, presentation or rectangle) hides and invalidates first:
29565
- * a moved page must never keep a stale rectangle floating over it, and a
29566
- * resized window invalidates old geometry until a fresh renderer rect
29567
- * arrives. An unchanged key is the background-page-creation case: the layout
29568
- * pass may have added a page above the child, so the child is re-added last
29569
- * and given the same rectangle. It never re-opens by itself; showing again is
29570
- * always an explicit probe call.
29673
+ * A changed key (tab, presentation or rectangle) is handled by ONE of two
29674
+ * rules, and the split is load-bearing (agent review round 1, MAJOR-1):
29675
+ * a CONTENT change (tab or rectangle) hides and invalidates, while a
29676
+ * PRESENTATION-only change is a suppression that belongs to someone else —
29677
+ * the page menu, which paints into the content rect — so it only stops the
29678
+ * child's paint and keeps the shown state. Collapsing the two made opening
29679
+ * the menu dismiss the popout it was opened from. An unchanged key is the
29680
+ * background-page-creation case: the layout pass may have added a page above
29681
+ * the child, so the child is re-added last and given the same rectangle. It
29682
+ * never re-opens by itself; showing again is always an explicit probe call.
29571
29683
  */
29572
29684
  onLayout(snapshot2) {
29573
29685
  if (this.disposed) return;
29574
29686
  const next = copyLayout(snapshot2);
29575
29687
  const nextKey = layoutKeyOf(next);
29576
29688
  const previousKey = this.layoutKey;
29689
+ const nextContentKey = layoutContentKeyOf(next);
29690
+ const previousContentKey = this.layoutContentKey;
29577
29691
  this.layout = next;
29578
29692
  this.layoutKey = nextKey;
29693
+ this.layoutContentKey = nextContentKey;
29579
29694
  if (previousKey !== nextKey) {
29695
+ const contentChanged = previousContentKey !== nextContentKey;
29696
+ if (!contentChanged) {
29697
+ if (next.presented) this.resumePresentation();
29698
+ else this.suspendPresentation();
29699
+ return;
29700
+ }
29580
29701
  this.hide("layout");
29702
+ if (this.product && previousKey !== "none") this.emitDismiss("layout");
29581
29703
  return;
29582
29704
  }
29705
+ if (this.product && next.presented && next.contentRect !== null) {
29706
+ this.ensureChild();
29707
+ }
29583
29708
  if (this.state !== "shown") return;
29584
29709
  const view = this.view;
29585
29710
  if (!view || this.viewUsable(view) === false) return;
@@ -29596,6 +29721,40 @@ class AddressOverlayController {
29596
29721
  this.log(`[browser] address overlay layout failed: ${String(error)}`);
29597
29722
  }
29598
29723
  }
29724
+ /** Hide the child's paint without dropping the popout's state; see `onLayout`. */
29725
+ suspendPresentation() {
29726
+ this.presentationSuspended = true;
29727
+ const view = this.view;
29728
+ if (!view || this.viewUsable(view) === false) return;
29729
+ try {
29730
+ view.setVisible(false);
29731
+ } catch {
29732
+ }
29733
+ }
29734
+ /** The page is back. Re-present the child from the bounds last applied, or
29735
+ * apply the projection that arrived while the page was hidden. */
29736
+ resumePresentation() {
29737
+ if (!this.presentationSuspended) return;
29738
+ this.presentationSuspended = false;
29739
+ const deferred = this.deferredProjection;
29740
+ this.deferredProjection = null;
29741
+ if (deferred !== null) {
29742
+ void this.project(deferred);
29743
+ return;
29744
+ }
29745
+ if (this.state !== "shown") return;
29746
+ const bounds = this.lastAppliedBounds ?? overlayBoundsFor(this.layout.contentRect, this.ownerSize());
29747
+ if (bounds === null) return;
29748
+ const view = this.view;
29749
+ if (!view || this.viewUsable(view) === false) return;
29750
+ try {
29751
+ view.setBounds(bounds);
29752
+ this.raiseChild(view);
29753
+ view.setVisible(true);
29754
+ } catch {
29755
+ this.releaseChild("layout_error");
29756
+ }
29757
+ }
29599
29758
  /** The bound page is going away. Dispose the child before the page release. */
29600
29759
  onTabReleased(tabId) {
29601
29760
  if (this.disposed) return;
@@ -29610,6 +29769,63 @@ class AddressOverlayController {
29610
29769
  if (this.boundTabId !== tabId) return;
29611
29770
  this.releaseChild("page-crash");
29612
29771
  }
29772
+ /**
29773
+ * The popout's current health, in the one shape the renderer reads.
29774
+ *
29775
+ * `suspend` is sticky for the app run: the child is either latched failed or
29776
+ * gone, and the restart that would clear it is a new process. A conditional
29777
+ * refusal (`zoomed`, `anchor_too_narrow`, `stale_*`) is NOT reported here —
29778
+ * those clear themselves, and the projection answer names them while they
29779
+ * last.
29780
+ */
29781
+ mode() {
29782
+ if (this.disposed) return { mode: "suspend", reason: "disposed" };
29783
+ if (this.failedReason)
29784
+ return { mode: "suspend", reason: this.failedReason };
29785
+ return { mode: "popout", reason: null };
29786
+ }
29787
+ /**
29788
+ * A main-decided dismissal (the page took focus, the bound tab's main frame
29789
+ * navigated, the tab went away). Idempotent: a second reason for a popout that
29790
+ * is already hidden sends nothing.
29791
+ */
29792
+ dismiss(reason) {
29793
+ if (this.disposed) return;
29794
+ this.hide(reason);
29795
+ if (this.product) this.emitDismiss(reason);
29796
+ }
29797
+ /** The page `tabId` took focus. Only the bound tab can dismiss its own list;
29798
+ * a background tab's focus event is not this popout's business. */
29799
+ dismissForTab(tabId, reason) {
29800
+ if (this.disposed) return;
29801
+ if (this.boundTabId !== null && this.boundTabId !== tabId) return;
29802
+ if (this.layout.activeTabId !== null && this.layout.activeTabId !== tabId)
29803
+ return;
29804
+ this.dismiss(reason);
29805
+ }
29806
+ /**
29807
+ * Warm the list child once the page has a presented layout, so the first
29808
+ * raise finds a loaded document instead of paying the load after Cmd+L. The
29809
+ * child is created hidden and stays hidden until a projection shows it.
29810
+ */
29811
+ ensureChild() {
29812
+ if (!this.product || this.view || this.failedReason || this.warmScheduled)
29813
+ return;
29814
+ const bounds = overlayBoundsFor(this.layout.contentRect, this.ownerSize());
29815
+ if (bounds === null || !this.layout.presented) return;
29816
+ this.warmScheduled = true;
29817
+ setTimeout(() => {
29818
+ this.warmScheduled = false;
29819
+ if (this.disposed || this.view || this.failedReason) return;
29820
+ if (!this.layout.presented) return;
29821
+ const rect2 = overlayBoundsFor(this.layout.contentRect, this.ownerSize());
29822
+ if (rect2 === null) return;
29823
+ if (this.createChild(rect2)) this.state = "loading";
29824
+ }, 0);
29825
+ }
29826
+ emitDismiss(reason) {
29827
+ this.onEvent?.({ type: "dismiss", reason });
29828
+ }
29613
29829
  // ---- probe surface ------------------------------------------------------
29614
29830
  async showOverlay() {
29615
29831
  if (this.showInFlight) return this.showInFlight;
@@ -29627,6 +29843,8 @@ class AddressOverlayController {
29627
29843
  }
29628
29844
  hide(reason = "hide") {
29629
29845
  if (this.disposed) return;
29846
+ this.presentationSuspended = false;
29847
+ this.deferredProjection = null;
29630
29848
  this.generation += 1;
29631
29849
  this.settleReady(false);
29632
29850
  this.hiddenReason = reason;
@@ -29793,6 +30011,15 @@ class AddressOverlayController {
29793
30011
  * controller, so a burst between frames costs one apply each in arrival order.
29794
30012
  */
29795
30013
  project(raw) {
30014
+ if (this.presentationSuspended) {
30015
+ this.deferredProjection = raw;
30016
+ return Promise.resolve({
30017
+ accepted: true,
30018
+ shown: false,
30019
+ reason: null,
30020
+ bounds: null
30021
+ });
30022
+ }
29796
30023
  const run = () => this.projectOnce(raw);
29797
30024
  const next = this.projectQueue.then(run, run);
29798
30025
  this.projectQueue = next.then(
@@ -29855,13 +30082,23 @@ class AddressOverlayController {
29855
30082
  if (!layout.presented || !sameRect(projection.pageRect, layout.contentRect)) {
29856
30083
  return this.refuseProjection("stale_layout");
29857
30084
  }
29858
- const bounds = addressListBoundsFor(
30085
+ const bounds = projection.v === 2 ? projection.anchor.width < ADDRESS_LIST_MIN_WIDTH_DIP ? null : addressListBoundsV2For(
30086
+ projection.anchor,
30087
+ projection.pageRect,
30088
+ projection.view,
30089
+ this.ownerSize()
30090
+ ) : addressListBoundsFor(
29859
30091
  projection.field,
29860
30092
  projection.listTop,
29861
30093
  projection.view,
29862
30094
  this.ownerSize()
29863
30095
  );
29864
- if (bounds === null) return this.refuseProjection("anchor_inconsistent");
30096
+ if (bounds === null) {
30097
+ const tooNarrow = projection.v === 2 && projection.anchor.width < ADDRESS_LIST_MIN_WIDTH_DIP;
30098
+ return this.refuseProjection(
30099
+ tooNarrow ? "anchor_too_narrow" : "anchor_inconsistent"
30100
+ );
30101
+ }
29865
30102
  this.generation += 1;
29866
30103
  const generation = this.generation;
29867
30104
  this.boundTabId = projection.tabId;
@@ -30307,6 +30544,7 @@ class AddressOverlayController {
30307
30544
  this.settleReady(false);
30308
30545
  this.destroyChild();
30309
30546
  this.log(`[browser] address overlay failed: ${reason}`);
30547
+ this.onHealthChange?.();
30310
30548
  }
30311
30549
  /** Recoverable: the child goes, a later explicit show may lazily recreate one. */
30312
30550
  releaseChild(reason) {
@@ -30319,6 +30557,7 @@ class AddressOverlayController {
30319
30557
  this.finished = false;
30320
30558
  this.loadStartedAt = null;
30321
30559
  this.log(`[browser] address overlay child released: ${reason}`);
30560
+ this.onHealthChange?.();
30322
30561
  }
30323
30562
  destroyChild() {
30324
30563
  const view = this.view;
@@ -35118,11 +35357,15 @@ const enUiBrowser = {
35118
35357
  "ui.browser.browser_tab_strip.unmute_tab": "Unmute tab",
35119
35358
  "ui.browser.browser_tab_strip.watch": 'Watch "{title}"',
35120
35359
  "ui.browser.browser_url_bar.address": "Address",
35121
- "ui.browser.browser_url_bar.address_required": "Enter a web address or choose a suggestion.",
35360
+ "ui.browser.browser_url_bar.address_search_row": 'Search DuckDuckGo for "{query}"',
35361
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "Already open — {title}",
35362
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "Already open",
35122
35363
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, one {{count, number, ::group-off} suggestion available} other {{count, number, ::group-off} suggestions available}}",
35123
- "ui.browser.browser_url_bar.address_suggestions_empty": "No links from these tabs yet.",
35124
- "ui.browser.browser_url_bar.address_suggestions_label": "From this browser session",
35125
- "ui.browser.browser_url_bar.address_suggestions_no_match": "No matching links from this browser session.",
35364
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "Only web addresses can open here.",
35365
+ "ui.browser.browser_url_bar.address_suggestions_empty": "No browsing history yet.",
35366
+ "ui.browser.browser_url_bar.address_suggestions_label": "From your history",
35367
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "Suggestions",
35368
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "No matching pages in your history.",
35126
35369
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "Address suggestions are unavailable.",
35127
35370
  "ui.browser.browser_url_bar.approvals_waiting": "Approvals, {count, plural, other {{count, number, ::group-off} waiting}}",
35128
35371
  "ui.browser.browser_url_bar.back": "Back",
@@ -35135,6 +35378,7 @@ const enUiBrowser = {
35135
35378
  "ui.browser.browser_url_bar.home_destination": "Home — go to the start page (blank)",
35136
35379
  "ui.browser.browser_url_bar.latest_download": "Open downloads folder — {name} was saved there",
35137
35380
  "ui.browser.browser_url_bar.newest_download": "Open downloads folder — {name} was the newest saved there ({count, plural, other {{count, number, ::group-off} this session}})",
35381
+ "ui.browser.browser_url_bar.page_menu": "Page options",
35138
35382
  "ui.browser.browser_url_bar.page_zoom_reset": "Page zoom {zoom, number, ::group-off}%, reset to 100%",
35139
35383
  "ui.browser.browser_url_bar.print_page": "Print page",
35140
35384
  "ui.browser.browser_url_bar.reload": "Reload",
@@ -38842,15 +39086,20 @@ const esUiBrowser = {
38842
39086
  "ui.browser.browser_url_bar.print_page": "Imprimir página",
38843
39087
  "ui.browser.browser_url_bar.forward": "Adelante",
38844
39088
  "ui.browser.browser_url_bar.reset_page_zoom_shortcut": "Restablecer zoom de la página ({shortcut})",
39089
+ "ui.browser.browser_url_bar.page_menu": "Opciones de página",
38845
39090
  "ui.browser.browser_url_bar.page_zoom_reset": "Zoom de la página al {zoom, number, ::group-off}%, restablecer al 100%",
38846
39091
  "ui.browser.browser_url_bar.stop_loading": "Detener carga",
38847
39092
  "ui.browser.browser_url_bar.reload": "Recargar",
38848
39093
  "ui.browser.browser_url_bar.address": "Dirección",
38849
- "ui.browser.browser_url_bar.address_required": "Escribe una dirección web o elige una sugerencia.",
39094
+ "ui.browser.browser_url_bar.address_search_row": "Buscar «{query}» en DuckDuckGo",
39095
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "Ya abierto — {title}",
39096
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "Ya abierto",
38850
39097
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, one {{count, number, ::group-off} sugerencia disponible} other {{count, number, ::group-off} sugerencias disponibles}}",
38851
- "ui.browser.browser_url_bar.address_suggestions_empty": "Aún no hay enlaces de estas pestañas.",
38852
- "ui.browser.browser_url_bar.address_suggestions_label": "De esta sesión del navegador",
38853
- "ui.browser.browser_url_bar.address_suggestions_no_match": "No hay enlaces que coincidan en esta sesión del navegador.",
39098
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "Aquí solo se pueden abrir direcciones web.",
39099
+ "ui.browser.browser_url_bar.address_suggestions_empty": "Aún no hay historial de navegación.",
39100
+ "ui.browser.browser_url_bar.address_suggestions_label": "De tu historial",
39101
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "Sugerencias",
39102
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "No hay páginas coincidentes en tu historial.",
38854
39103
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "Las sugerencias de direcciones no están disponibles.",
38855
39104
  "ui.browser.browser_url_bar.approvals_waiting": "Aprobaciones, {count, plural, other {{count, number, ::group-off} en espera}}",
38856
39105
  "ui.browser.browser_url_bar.home_destination": "Inicio — ir a la página inicial (en blanco)",
@@ -42418,15 +42667,20 @@ const frUiBrowser = {
42418
42667
  "ui.browser.browser_url_bar.print_page": "Imprimer la page",
42419
42668
  "ui.browser.browser_url_bar.forward": "Avancer",
42420
42669
  "ui.browser.browser_url_bar.reset_page_zoom_shortcut": "Réinitialiser le zoom de la page ({shortcut})",
42670
+ "ui.browser.browser_url_bar.page_menu": "Options de la page",
42421
42671
  "ui.browser.browser_url_bar.page_zoom_reset": "Zoom de la page {zoom, number, ::group-off} %, réinitialiser à 100 %",
42422
42672
  "ui.browser.browser_url_bar.stop_loading": "Arrêter le chargement",
42423
42673
  "ui.browser.browser_url_bar.reload": "Recharger",
42424
42674
  "ui.browser.browser_url_bar.address": "Adresse",
42425
- "ui.browser.browser_url_bar.address_required": "Saisissez une adresse web ou choisissez une suggestion.",
42675
+ "ui.browser.browser_url_bar.address_search_row": "Rechercher « {query} » sur DuckDuckGo",
42676
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "Déjà ouvert — {title}",
42677
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "Déjà ouvert",
42426
42678
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, one {{count, number, ::group-off} suggestion disponible} many {{count, number, ::group-off} de suggestions disponibles} other {{count, number, ::group-off} suggestions disponibles}}",
42427
- "ui.browser.browser_url_bar.address_suggestions_empty": "Aucun lien de ces onglets pour l’instant.",
42428
- "ui.browser.browser_url_bar.address_suggestions_label": "De cette session de navigation",
42429
- "ui.browser.browser_url_bar.address_suggestions_no_match": "Aucun lien correspondant dans cette session de navigation.",
42679
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "Seules les adresses web peuvent s’ouvrir ici.",
42680
+ "ui.browser.browser_url_bar.address_suggestions_empty": "Pas encore d'historique de navigation.",
42681
+ "ui.browser.browser_url_bar.address_suggestions_label": "De votre historique",
42682
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "Suggestions",
42683
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "Aucune page correspondante dans votre historique.",
42430
42684
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "Les suggestions d’adresses ne sont pas disponibles.",
42431
42685
  "ui.browser.browser_url_bar.approvals_waiting": "Approbations, {count, plural, one {{count, number, ::group-off} en attente} many {{count, number, ::group-off} en attente} other {{count, number, ::group-off} en attente}}",
42432
42686
  "ui.browser.browser_url_bar.home_destination": "Accueil — aller à la page de départ (vide)",
@@ -45998,11 +46252,15 @@ const hiUiBrowser = {
45998
46252
  "ui.browser.browser_tab_strip.unmute_tab": "टैब की आवाज़ चालू करें",
45999
46253
  "ui.browser.browser_tab_strip.watch": '"{title}" देखें',
46000
46254
  "ui.browser.browser_url_bar.address": "पता",
46001
- "ui.browser.browser_url_bar.address_required": "वेब पता दर्ज करें या कोई सुझाव चुनें।",
46255
+ "ui.browser.browser_url_bar.address_search_row": 'DuckDuckGo पर "{query}" खोजें',
46256
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "पहले से खुला — {title}",
46257
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "पहले से खुला",
46002
46258
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, one {{count, number, ::group-off} सुझाव उपलब्ध है} other {{count, number, ::group-off} सुझाव उपलब्ध हैं}}",
46003
- "ui.browser.browser_url_bar.address_suggestions_empty": "इन टैब से अभी कोई लिंक नहीं है।",
46004
- "ui.browser.browser_url_bar.address_suggestions_label": "इस ब्राउज़र सत्र से",
46005
- "ui.browser.browser_url_bar.address_suggestions_no_match": "इस ब्राउज़र सत्र में कोई मेल खाता लिंक नहीं है।",
46259
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "यहाँ केवल वेब पते खुल सकते हैं।",
46260
+ "ui.browser.browser_url_bar.address_suggestions_empty": "अभी तक कोई ब्राउज़िंग इतिहास नहीं।",
46261
+ "ui.browser.browser_url_bar.address_suggestions_label": "आपके इतिहास से",
46262
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "सुझाव",
46263
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "आपके इतिहास में कोई मेल खाता पृष्ठ नहीं।",
46006
46264
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "पते के सुझाव उपलब्ध नहीं हैं।",
46007
46265
  "ui.browser.browser_url_bar.approvals_waiting": "स्वीकृतियाँ, {count, plural, other {{count, number, ::group-off} इंतज़ार में}}",
46008
46266
  "ui.browser.browser_url_bar.back": "पीछे",
@@ -46015,6 +46273,7 @@ const hiUiBrowser = {
46015
46273
  "ui.browser.browser_url_bar.home_destination": "होम — शुरुआती पेज पर जाएँ (खाली)",
46016
46274
  "ui.browser.browser_url_bar.latest_download": "डाउनलोड फ़ोल्डर खोलें — {name} वहाँ सहेजी गई थी",
46017
46275
  "ui.browser.browser_url_bar.newest_download": "डाउनलोड फ़ोल्डर खोलें — {name} वहाँ सबसे हाल में सहेजी गई थी ({count, plural, other {इस सत्र में {count, number, ::group-off}}})",
46276
+ "ui.browser.browser_url_bar.page_menu": "पेज विकल्प",
46018
46277
  "ui.browser.browser_url_bar.page_zoom_reset": "पेज ज़ूम {zoom, number, ::group-off}%, 100% पर रीसेट करें",
46019
46278
  "ui.browser.browser_url_bar.print_page": "पेज प्रिंट करें",
46020
46279
  "ui.browser.browser_url_bar.reload": "फिर लोड करें",
@@ -49351,11 +49610,15 @@ const ruUiBrowser = {
49351
49610
  "ui.browser.browser_tab_strip.unmute_tab": "Включить звук вкладки",
49352
49611
  "ui.browser.browser_tab_strip.watch": "Смотреть «{title}»",
49353
49612
  "ui.browser.browser_url_bar.address": "Адрес",
49354
- "ui.browser.browser_url_bar.address_required": "Введите веб-адрес или выберите подсказку.",
49613
+ "ui.browser.browser_url_bar.address_search_row": "Искать «{query}» в DuckDuckGo",
49614
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "Уже открыто — {title}",
49615
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "Уже открыто",
49355
49616
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, one {{count, number, ::group-off} подсказка доступна} few {{count, number, ::group-off} подсказки доступны} many {{count, number, ::group-off} подсказок доступно} other {{count, number, ::group-off} подсказки доступны}}",
49356
- "ui.browser.browser_url_bar.address_suggestions_empty": "В этих вкладках пока нет ссылок.",
49357
- "ui.browser.browser_url_bar.address_suggestions_label": "Из этой сессии браузера",
49358
- "ui.browser.browser_url_bar.address_suggestions_no_match": "В этой сессии браузера нет подходящих ссылок.",
49617
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "Здесь можно открывать только веб-адреса.",
49618
+ "ui.browser.browser_url_bar.address_suggestions_empty": "Истории просмотров пока нет.",
49619
+ "ui.browser.browser_url_bar.address_suggestions_label": "Из вашей истории",
49620
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "Подсказки",
49621
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "В вашей истории нет подходящих страниц.",
49359
49622
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "Подсказки адресов недоступны.",
49360
49623
  "ui.browser.browser_url_bar.approvals_waiting": "Одобрения, {count, plural, one {ожидает {count, number, ::group-off}} few {ожидают {count, number, ::group-off}} many {ожидают {count, number, ::group-off}} other {ожидают {count, number, ::group-off}}}",
49361
49624
  "ui.browser.browser_url_bar.back": "Назад",
@@ -49368,6 +49631,7 @@ const ruUiBrowser = {
49368
49631
  "ui.browser.browser_url_bar.home_destination": "Домой — перейти на стартовую страницу (пустую)",
49369
49632
  "ui.browser.browser_url_bar.latest_download": "Открыть папку загрузок — {name} сохранён туда",
49370
49633
  "ui.browser.browser_url_bar.newest_download": "Открыть папку загрузок — {name} сохранён туда последним ({count, plural, one {{count, number, ::group-off} за эту сессию} few {{count, number, ::group-off} за эту сессию} many {{count, number, ::group-off} за эту сессию} other {{count, number, ::group-off} за эту сессию}})",
49634
+ "ui.browser.browser_url_bar.page_menu": "Параметры страницы",
49371
49635
  "ui.browser.browser_url_bar.page_zoom_reset": "Масштаб страницы {zoom, number, ::group-off}%, сбросить до 100%",
49372
49636
  "ui.browser.browser_url_bar.print_page": "Печать страницы",
49373
49637
  "ui.browser.browser_url_bar.reload": "Перезагрузить",
@@ -52810,11 +53074,15 @@ const urUiBrowser = {
52810
53074
  "ui.browser.browser_tab_strip.unmute_tab": "ٹیب کی آواز بحال کریں",
52811
53075
  "ui.browser.browser_tab_strip.watch": "“{title}” پر نظر رکھیں",
52812
53076
  "ui.browser.browser_url_bar.address": "پتہ",
52813
- "ui.browser.browser_url_bar.address_required": "ویب پتہ درج کریں یا کوئی تجویز منتخب کریں۔",
53077
+ "ui.browser.browser_url_bar.address_search_row": 'DuckDuckGo پر "{query}" تلاش کریں',
53078
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "پہلے سے کھلا — {title}",
53079
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "پہلے سے کھلا",
52814
53080
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, one {{count, number, ::group-off} تجویز دستیاب ہے} other {{count, number, ::group-off} تجاویز دستیاب ہیں}}",
52815
- "ui.browser.browser_url_bar.address_suggestions_empty": "ان ٹیب سے ابھی کوئی لنک نہیں ہے۔",
52816
- "ui.browser.browser_url_bar.address_suggestions_label": "اس براؤزر سیشن سے",
52817
- "ui.browser.browser_url_bar.address_suggestions_no_match": "اس براؤزر سیشن میں کوئی مماثل لنک نہیں ہے۔",
53081
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "یہاں صرف ویب پتے کھل سکتے ہیں۔",
53082
+ "ui.browser.browser_url_bar.address_suggestions_empty": "ابھی تک کوئی براؤزنگ سرگزشت نہیں۔",
53083
+ "ui.browser.browser_url_bar.address_suggestions_label": "آپ کی سرگزشت سے",
53084
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "تجاویز",
53085
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "آپ کی سرگزشت میں کوئی مماثل صفحہ نہیں۔",
52818
53086
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "پتے کی تجاویز دستیاب نہیں ہیں۔",
52819
53087
  "ui.browser.browser_url_bar.approvals_waiting": "منظوریاں، {count, plural, other {{count, number, ::group-off} منتظر}}",
52820
53088
  "ui.browser.browser_url_bar.back": "پیچھے",
@@ -52827,6 +53095,7 @@ const urUiBrowser = {
52827
53095
  "ui.browser.browser_url_bar.home_destination": "ابتدائی صفحہ — آغاز کے خالی صفحے پر جائیں",
52828
53096
  "ui.browser.browser_url_bar.latest_download": "ڈاؤن لوڈ فولڈر کھولیں — {name} وہاں محفوظ ہوئی",
52829
53097
  "ui.browser.browser_url_bar.newest_download": "ڈاؤن لوڈ فولڈر کھولیں — وہاں سب سے حال میں {name} محفوظ ہوئی ({count, plural, other {اس سیشن میں {count, number, ::group-off}}})",
53098
+ "ui.browser.browser_url_bar.page_menu": "صفحے کے اختیارات",
52830
53099
  "ui.browser.browser_url_bar.page_zoom_reset": "صفحے کا زوم {zoom, number, ::group-off}%، 100% پر ری سیٹ کریں",
52831
53100
  "ui.browser.browser_url_bar.print_page": "صفحہ پرنٹ کریں",
52832
53101
  "ui.browser.browser_url_bar.reload": "دوبارہ لوڈ کریں",
@@ -56183,11 +56452,15 @@ const viUiBrowser = {
56183
56452
  "ui.browser.browser_tab_strip.unmute_tab": "Bật tiếng tab",
56184
56453
  "ui.browser.browser_tab_strip.watch": 'Theo dõi "{title}"',
56185
56454
  "ui.browser.browser_url_bar.address": "Địa chỉ",
56186
- "ui.browser.browser_url_bar.address_required": "Nhập địa chỉ web hoặc chọn một gợi ý.",
56455
+ "ui.browser.browser_url_bar.address_search_row": 'Tìm "{query}" trên DuckDuckGo',
56456
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "Đã mở — {title}",
56457
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "Đã mở",
56187
56458
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, other {{count, number, ::group-off} gợi ý có sẵn}}",
56188
- "ui.browser.browser_url_bar.address_suggestions_empty": "Chưa có liên kết nào từ các tab này.",
56189
- "ui.browser.browser_url_bar.address_suggestions_label": "Từ phiên trình duyệt này",
56190
- "ui.browser.browser_url_bar.address_suggestions_no_match": "Không có liên kết phù hợp từ phiên trình duyệt này.",
56459
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "Chỉ có thể mở địa chỉ web ở đây.",
56460
+ "ui.browser.browser_url_bar.address_suggestions_empty": "Chưa có lịch sử duyệt web.",
56461
+ "ui.browser.browser_url_bar.address_suggestions_label": "Từ lịch sử của bạn",
56462
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "Gợi ý",
56463
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "Không có trang nào phù hợp trong lịch sử của bạn.",
56191
56464
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "Gợi ý địa chỉ không khả dụng.",
56192
56465
  "ui.browser.browser_url_bar.approvals_waiting": "Phê duyệt, {count, plural, other {{count, number, ::group-off} đang chờ}}",
56193
56466
  "ui.browser.browser_url_bar.back": "Quay lại",
@@ -56200,6 +56473,7 @@ const viUiBrowser = {
56200
56473
  "ui.browser.browser_url_bar.home_destination": "Trang chủ — đến trang bắt đầu (trống)",
56201
56474
  "ui.browser.browser_url_bar.latest_download": "Mở thư mục tải xuống — {name} đã được lưu ở đó",
56202
56475
  "ui.browser.browser_url_bar.newest_download": "Mở thư mục tải xuống — {name} là tệp mới nhất được lưu ở đó ({count, plural, other {{count, number, ::group-off} trong phiên này}})",
56476
+ "ui.browser.browser_url_bar.page_menu": "Tùy chọn trang",
56203
56477
  "ui.browser.browser_url_bar.page_zoom_reset": "Thu phóng trang {zoom, number, ::group-off}%, đặt lại về 100%",
56204
56478
  "ui.browser.browser_url_bar.print_page": "In trang",
56205
56479
  "ui.browser.browser_url_bar.reload": "Tải lại",
@@ -59451,11 +59725,15 @@ const zhCNUiBrowser = {
59451
59725
  "ui.browser.browser_tab_strip.unmute_tab": "取消标签页静音",
59452
59726
  "ui.browser.browser_tab_strip.watch": "查看“{title}”",
59453
59727
  "ui.browser.browser_url_bar.address": "地址",
59454
- "ui.browser.browser_url_bar.address_required": "请输入地址或选择一条建议。",
59728
+ "ui.browser.browser_url_bar.address_search_row": "在 DuckDuckGo 中搜索“{query}”",
59729
+ "ui.browser.browser_url_bar.address_suggestion_open_tab": "已打开 — {title}",
59730
+ "ui.browser.browser_url_bar.address_suggestion_open_tab_untitled": "已打开",
59455
59731
  "ui.browser.browser_url_bar.address_suggestions_available": "{count, plural, other {{count, number, ::group-off}条建议可用}}",
59456
- "ui.browser.browser_url_bar.address_suggestions_empty": "这些标签页中还没有链接。",
59457
- "ui.browser.browser_url_bar.address_suggestions_label": "来自此浏览器会话",
59458
- "ui.browser.browser_url_bar.address_suggestions_no_match": "此浏览器会话中没有匹配的链接。",
59732
+ "ui.browser.browser_url_bar.address_suggestions_blocked": "此处只能打开网页地址。",
59733
+ "ui.browser.browser_url_bar.address_suggestions_empty": "还没有浏览历史记录。",
59734
+ "ui.browser.browser_url_bar.address_suggestions_label": "来自你的历史记录",
59735
+ "ui.browser.browser_url_bar.address_suggestions_label_mixed": "建议",
59736
+ "ui.browser.browser_url_bar.address_suggestions_no_match": "你的历史记录中没有匹配的页面。",
59459
59737
  "ui.browser.browser_url_bar.address_suggestions_unavailable": "地址建议不可用。",
59460
59738
  "ui.browser.browser_url_bar.approvals_waiting": "审批,{count, plural, other {{count, number, ::group-off}项待处理}}",
59461
59739
  "ui.browser.browser_url_bar.back": "后退",
@@ -59468,6 +59746,7 @@ const zhCNUiBrowser = {
59468
59746
  "ui.browser.browser_url_bar.home_destination": "主页——前往起始页(空白页)",
59469
59747
  "ui.browser.browser_url_bar.latest_download": "打开下载文件夹——{name}已保存于此",
59470
59748
  "ui.browser.browser_url_bar.newest_download": "打开下载文件夹——最新保存的文件为{name}(本次会话共{count, plural, other {{count, number, ::group-off}个}})",
59749
+ "ui.browser.browser_url_bar.page_menu": "页面选项",
59471
59750
  "ui.browser.browser_url_bar.page_zoom_reset": "页面缩放{zoom, number, ::group-off}%,重置为100%",
59472
59751
  "ui.browser.browser_url_bar.print_page": "打印页面",
59473
59752
  "ui.browser.browser_url_bar.reload": "重新加载",
@@ -65747,7 +66026,8 @@ const ADDRESS_HISTORY_MAX_ENTRIES_PER_TAB = 50;
65747
66026
  const ADDRESS_HISTORY_MAX_ENTRIES = 256;
65748
66027
  const ADDRESS_HISTORY_MAX_URL_LENGTH = 2048;
65749
66028
  const ADDRESS_HISTORY_MAX_TITLE_LENGTH = 256;
65750
- const ADDRESS_HISTORY_MAX_SESSION_ID_LENGTH = 256;
66029
+ const ADDRESS_HISTORY_MAX_QUERY_LENGTH = ADDRESS_HISTORY_MAX_URL_LENGTH;
66030
+ const ADDRESS_HISTORY_MAX_LIMIT = 20;
65751
66031
  const CONTROL_CHARS$1 = /[\p{Cc}]/u;
65752
66032
  function windowAround(entries, activeIndexRaw, max) {
65753
66033
  const activeIndex = Math.min(
@@ -65823,12 +66103,9 @@ function historyTitle(raw) {
65823
66103
  const cleaned = raw.replace(CONTROL_CHARS$1, " ").replace(/\s+/g, " ").trim();
65824
66104
  return cleaned.length > ADDRESS_HISTORY_MAX_TITLE_LENGTH ? cleaned.slice(0, ADDRESS_HISTORY_MAX_TITLE_LENGTH) : cleaned;
65825
66105
  }
65826
- function collectAddressHistory(tabs2, scope, activeTabId) {
65827
- const inScope = scope === "all" ? [...tabs2] : tabs2.filter(
65828
- (tab) => tab.sessionId !== null && tab.sessionId === scope.sessionId
65829
- );
65830
- const active = inScope.find((tab) => tab.tabId === activeTabId);
65831
- const ordered = active ? [active, ...inScope.filter((tab) => tab.tabId !== activeTabId)] : inScope;
66106
+ function collectAddressHistory(tabs2, activeTabId) {
66107
+ const active = tabs2.find((tab) => tab.tabId === activeTabId);
66108
+ const ordered = active ? [active, ...tabs2.filter((tab) => tab.tabId !== activeTabId)] : [...tabs2];
65832
66109
  const seen = /* @__PURE__ */ new Set();
65833
66110
  const entries = [];
65834
66111
  for (const tab of ordered.slice(0, ADDRESS_HISTORY_MAX_TABS)) {
@@ -65844,6 +66121,12 @@ function collectAddressHistory(tabs2, scope, activeTabId) {
65844
66121
  entries.push({
65845
66122
  url,
65846
66123
  title: historyTitle(entry.title),
66124
+ // A stack has no visit count and no visit timestamp. Zero is the
66125
+ // honest absence; the renderer treats a tab row's order as stack
66126
+ // distance, never as frequency.
66127
+ visitCount: 0,
66128
+ lastVisitedAt: 0,
66129
+ source: "tab",
65847
66130
  sourceTabId: tab.tabId,
65848
66131
  sourceIndex,
65849
66132
  activeTab: tab.tabId === activeTabId,
@@ -65857,6 +66140,38 @@ function collectAddressHistory(tabs2, scope, activeTabId) {
65857
66140
  }
65858
66141
  return { activeTabId, entries };
65859
66142
  }
66143
+ function foldAscii(value) {
66144
+ return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
66145
+ }
66146
+ function mergeAddressHistory(tabEntries, historyEntries, query, limit) {
66147
+ const cap = Math.max(0, Math.floor(limit));
66148
+ if (cap === 0) return [];
66149
+ const seen = /* @__PURE__ */ new Set();
66150
+ const merged = [];
66151
+ for (const entry of historyEntries) {
66152
+ if (merged.length >= cap) break;
66153
+ const url = canonicalHistoryUrl(entry.url);
66154
+ if (!url || seen.has(url)) continue;
66155
+ seen.add(url);
66156
+ merged.push({
66157
+ url,
66158
+ title: historyTitle(entry.title),
66159
+ visitCount: entry.visitCount,
66160
+ lastVisitedAt: entry.lastVisitedAt,
66161
+ source: "history"
66162
+ });
66163
+ }
66164
+ const needle = foldAscii(query.trim());
66165
+ for (const entry of tabEntries) {
66166
+ if (merged.length >= cap) break;
66167
+ if (seen.has(entry.url)) continue;
66168
+ if (needle.length > 0 && !foldAscii(entry.url).includes(needle) && !foldAscii(entry.title).includes(needle))
66169
+ continue;
66170
+ seen.add(entry.url);
66171
+ merged.push(entry);
66172
+ }
66173
+ return merged;
66174
+ }
65860
66175
  class ChromeTabOrder {
65861
66176
  ids = [];
65862
66177
  customized = false;
@@ -65886,6 +66201,305 @@ class ChromeTabOrder {
65886
66201
  return true;
65887
66202
  }
65888
66203
  }
66204
+ const HISTORY_STORE_VERSION = 1;
66205
+ const HISTORY_STORE_MAX_ENTRIES = 5e3;
66206
+ const HISTORY_STORE_MAX_BYTES = 2 * 1024 * 1024;
66207
+ const HISTORY_STORE_WRITE_DEBOUNCE_MS = 500;
66208
+ const HISTORY_STORE_MAX_CANDIDATES = 200;
66209
+ const PREFIX_WIDTH = 3;
66210
+ const RECENCY_HALF_LIFE_DAYS = 7;
66211
+ const DAY_MS = 24 * 60 * 60 * 1e3;
66212
+ const SCHEME_PREFIX = /^([a-z][a-z0-9+.-]*):\/\//i;
66213
+ const PATH_OR_QUERY = /[/?#]/;
66214
+ function schemeLess(value) {
66215
+ const match = SCHEME_PREFIX.exec(value);
66216
+ return match ? value.slice(match[0].length) : value;
66217
+ }
66218
+ function stripWww(value) {
66219
+ return value.startsWith("www.") && value.length > "www.".length ? value.slice("www.".length) : value;
66220
+ }
66221
+ function matchKey(value) {
66222
+ return stripWww(schemeLess(value.trim()).toLowerCase());
66223
+ }
66224
+ function literalKey(value) {
66225
+ return schemeLess(value.trim()).toLowerCase();
66226
+ }
66227
+ function indexKeys(url) {
66228
+ const keys = /* @__PURE__ */ new Set([literalKey(url), matchKey(url)]);
66229
+ return [...keys].filter((key) => key.length > 0);
66230
+ }
66231
+ function queryNeedles(raw) {
66232
+ const literal = literalKey(raw);
66233
+ const keys = /* @__PURE__ */ new Set([literal, stripWww(literal)]);
66234
+ return [...keys].filter((key) => key.length > 0);
66235
+ }
66236
+ function matchesNeedle(value, needle) {
66237
+ if (!value.startsWith(needle)) return false;
66238
+ const hostOnly = !PATH_OR_QUERY.test(needle);
66239
+ if (!hostOnly) return true;
66240
+ if (!needle.includes(".") || needle.endsWith(".")) return true;
66241
+ const next = value.charAt(needle.length);
66242
+ return next === "" || next === "/" || next === "?" || next === "#" || next === ":";
66243
+ }
66244
+ function prefixKey(matchKeyValue) {
66245
+ return matchKeyValue.slice(0, PREFIX_WIDTH);
66246
+ }
66247
+ function historyScore(entry, now) {
66248
+ const ageDays = Math.max(0, now - entry.lastVisitedAt) / DAY_MS;
66249
+ return Math.log2(1 + entry.visitCount) + 0.5 ** (ageDays / RECENCY_HALF_LIFE_DAYS);
66250
+ }
66251
+ function compareEntries(now) {
66252
+ return (a, b) => {
66253
+ const scoreDiff = historyScore(b, now) - historyScore(a, now);
66254
+ if (scoreDiff !== 0) return scoreDiff;
66255
+ if (a.lastVisitedAt !== b.lastVisitedAt)
66256
+ return b.lastVisitedAt - a.lastVisitedAt;
66257
+ return a.url < b.url ? -1 : a.url > b.url ? 1 : 0;
66258
+ };
66259
+ }
66260
+ function storageUrl(raw) {
66261
+ const canonical = canonicalHistoryUrl(raw);
66262
+ if (!canonical) return null;
66263
+ const hash = canonical.indexOf("#");
66264
+ return hash < 0 ? canonical : canonical.slice(0, hash);
66265
+ }
66266
+ function isEntry(value) {
66267
+ if (typeof value !== "object" || value === null) return false;
66268
+ const record = value;
66269
+ return typeof record.url === "string" && typeof record.title === "string" && typeof record.host === "string" && Number.isSafeInteger(record.visitCount) && record.visitCount > 0 && typeof record.lastVisitedAt === "number" && Number.isFinite(record.lastVisitedAt);
66270
+ }
66271
+ function createBrowserHistoryStore(filePath, options = {}) {
66272
+ const now = options.now ?? Date.now;
66273
+ const byUrl = /* @__PURE__ */ new Map();
66274
+ const prefix = /* @__PURE__ */ new Map();
66275
+ let writeTimer = null;
66276
+ let approxBytes = 0;
66277
+ function estimate(entry) {
66278
+ return entry.url.length + entry.title.length + entry.host.length + 192;
66279
+ }
66280
+ function indexAdd(entry) {
66281
+ approxBytes += estimate(entry);
66282
+ for (const key of indexKeys(entry.url)) {
66283
+ const bucketKey = prefixKey(key);
66284
+ let bucket = prefix.get(bucketKey);
66285
+ if (!bucket) {
66286
+ bucket = /* @__PURE__ */ new Set();
66287
+ prefix.set(bucketKey, bucket);
66288
+ }
66289
+ bucket.add(entry.url);
66290
+ }
66291
+ }
66292
+ function indexRemove(entry) {
66293
+ approxBytes -= estimate(entry);
66294
+ for (const key of indexKeys(entry.url)) {
66295
+ const bucketKey = prefixKey(key);
66296
+ const bucket = prefix.get(bucketKey);
66297
+ bucket?.delete(entry.url);
66298
+ if (bucket?.size === 0) prefix.delete(bucketKey);
66299
+ }
66300
+ }
66301
+ function setTitle(entry, next) {
66302
+ const before = estimate(entry);
66303
+ entry.title = next;
66304
+ approxBytes += estimate(entry) - before;
66305
+ }
66306
+ function enforceBounds(at) {
66307
+ let changed = false;
66308
+ if (byUrl.size <= HISTORY_STORE_MAX_ENTRIES && approxBytes <= HISTORY_STORE_MAX_BYTES)
66309
+ return false;
66310
+ if (byUrl.size > HISTORY_STORE_MAX_ENTRIES) {
66311
+ const overflow = byUrl.size - HISTORY_STORE_MAX_ENTRIES;
66312
+ const victims = [...byUrl.values()].sort(compareEntries(at)).reverse().slice(0, overflow);
66313
+ for (const victim of victims) {
66314
+ byUrl.delete(victim.url);
66315
+ indexRemove(victim);
66316
+ changed = true;
66317
+ }
66318
+ }
66319
+ let guard = 0;
66320
+ while (guard < 200 && byUrl.size > 0) {
66321
+ guard += 1;
66322
+ const bytes = Buffer.byteLength(
66323
+ JSON.stringify(document(), null, 2),
66324
+ "utf8"
66325
+ );
66326
+ if (bytes <= HISTORY_STORE_MAX_BYTES) {
66327
+ approxBytes = bytes;
66328
+ break;
66329
+ }
66330
+ const batch = Math.max(1, Math.ceil(byUrl.size / 100));
66331
+ const victims = [...byUrl.values()].sort(compareEntries(at)).reverse().slice(0, batch);
66332
+ for (const victim of victims) {
66333
+ byUrl.delete(victim.url);
66334
+ indexRemove(victim);
66335
+ changed = true;
66336
+ }
66337
+ }
66338
+ return changed;
66339
+ }
66340
+ function document() {
66341
+ return {
66342
+ version: HISTORY_STORE_VERSION,
66343
+ entries: [...byUrl.values()]
66344
+ };
66345
+ }
66346
+ function writeNow() {
66347
+ if (writeTimer !== null) {
66348
+ clearTimeout(writeTimer);
66349
+ writeTimer = null;
66350
+ }
66351
+ writeJsonAtomic(filePath, document());
66352
+ }
66353
+ function scheduleWrite2() {
66354
+ if (writeTimer !== null) return;
66355
+ writeTimer = setTimeout(() => {
66356
+ writeTimer = null;
66357
+ writeJsonAtomic(filePath, document());
66358
+ }, HISTORY_STORE_WRITE_DEBOUNCE_MS);
66359
+ writeTimer.unref?.();
66360
+ }
66361
+ const loaded = readJson(filePath);
66362
+ if (loaded && loaded.version === HISTORY_STORE_VERSION && Array.isArray(loaded.entries)) {
66363
+ for (const raw of loaded.entries) {
66364
+ if (!isEntry(raw)) continue;
66365
+ const url = storageUrl(raw.url);
66366
+ if (!url) continue;
66367
+ const host = new URL(url).host;
66368
+ const existing = byUrl.get(url);
66369
+ if (existing) {
66370
+ existing.visitCount = Math.max(existing.visitCount, raw.visitCount);
66371
+ existing.lastVisitedAt = Math.max(
66372
+ existing.lastVisitedAt,
66373
+ raw.lastVisitedAt
66374
+ );
66375
+ if (existing.title.length === 0) {
66376
+ const title = historyTitle(raw.title);
66377
+ if (title.length > 0) setTitle(existing, title);
66378
+ }
66379
+ continue;
66380
+ }
66381
+ const entry = {
66382
+ url,
66383
+ title: historyTitle(raw.title),
66384
+ host,
66385
+ visitCount: raw.visitCount,
66386
+ lastVisitedAt: raw.lastVisitedAt
66387
+ };
66388
+ byUrl.set(url, entry);
66389
+ indexAdd(entry);
66390
+ }
66391
+ if (enforceBounds(now())) {
66392
+ writeNow();
66393
+ }
66394
+ }
66395
+ return {
66396
+ record(rawUrl, rawTitle, at = now()) {
66397
+ const url = storageUrl(rawUrl);
66398
+ if (!url) return;
66399
+ const title = historyTitle(rawTitle);
66400
+ const existing = byUrl.get(url);
66401
+ if (existing) {
66402
+ existing.visitCount += 1;
66403
+ existing.lastVisitedAt = at;
66404
+ if (title.length > 0 && title !== existing.title)
66405
+ setTitle(existing, title);
66406
+ } else {
66407
+ const entry = {
66408
+ url,
66409
+ title,
66410
+ host: new URL(url).host,
66411
+ visitCount: 1,
66412
+ lastVisitedAt: at
66413
+ };
66414
+ byUrl.set(url, entry);
66415
+ indexAdd(entry);
66416
+ }
66417
+ enforceBounds(at);
66418
+ scheduleWrite2();
66419
+ },
66420
+ updateTitle(rawUrl, rawTitle) {
66421
+ const url = storageUrl(rawUrl);
66422
+ if (!url) return;
66423
+ const entry = byUrl.get(url);
66424
+ if (!entry) return;
66425
+ const title = historyTitle(rawTitle);
66426
+ if (title.length === 0 || title === entry.title) return;
66427
+ setTitle(entry, title);
66428
+ enforceBounds(now());
66429
+ scheduleWrite2();
66430
+ },
66431
+ suggest(rawQuery, limit) {
66432
+ const wanted = Math.max(0, Math.floor(limit));
66433
+ if (wanted === 0 || byUrl.size === 0) return [];
66434
+ const at = now();
66435
+ if (rawQuery.length === 0) {
66436
+ return [...byUrl.values()].sort(compareEntries(at)).slice(0, wanted);
66437
+ }
66438
+ const needles = queryNeedles(rawQuery);
66439
+ if (needles.length === 0) return [];
66440
+ const bucketSet = /* @__PURE__ */ new Set();
66441
+ for (const needle of needles) {
66442
+ if (needle.length >= PREFIX_WIDTH) {
66443
+ const bucket = prefix.get(prefixKey(needle));
66444
+ if (bucket) bucketSet.add(bucket);
66445
+ } else {
66446
+ for (const [key, bucket] of prefix) {
66447
+ if (key.startsWith(needle)) bucketSet.add(bucket);
66448
+ }
66449
+ }
66450
+ }
66451
+ const buckets = [...bucketSet];
66452
+ if (buckets.length === 0) return [];
66453
+ const candidates = [];
66454
+ const seen = /* @__PURE__ */ new Set();
66455
+ for (const bucket of buckets) {
66456
+ for (const url of bucket) {
66457
+ if (candidates.length >= HISTORY_STORE_MAX_CANDIDATES) break;
66458
+ if (seen.has(url)) continue;
66459
+ const keys = indexKeys(url);
66460
+ const hit = needles.some(
66461
+ (needle) => keys.some((key) => matchesNeedle(key, needle))
66462
+ );
66463
+ if (!hit) continue;
66464
+ const entry = byUrl.get(url);
66465
+ if (!entry) continue;
66466
+ seen.add(url);
66467
+ candidates.push(entry);
66468
+ }
66469
+ if (candidates.length >= HISTORY_STORE_MAX_CANDIDATES) break;
66470
+ }
66471
+ candidates.sort(compareEntries(at));
66472
+ return candidates.slice(0, wanted);
66473
+ },
66474
+ size() {
66475
+ return byUrl.size;
66476
+ },
66477
+ clear() {
66478
+ byUrl.clear();
66479
+ prefix.clear();
66480
+ writeNow();
66481
+ },
66482
+ clearOrigin(rawOrigin) {
66483
+ let parsed;
66484
+ try {
66485
+ parsed = new URL(rawOrigin);
66486
+ } catch {
66487
+ return;
66488
+ }
66489
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
66490
+ const origin = parsed.origin;
66491
+ for (const [url, entry] of [...byUrl.entries()]) {
66492
+ if (new URL(url).origin !== origin) continue;
66493
+ byUrl.delete(url);
66494
+ indexRemove(entry);
66495
+ }
66496
+ writeNow();
66497
+ },
66498
+ flush() {
66499
+ if (writeTimer !== null) writeNow();
66500
+ }
66501
+ };
66502
+ }
65889
66503
  const PAGE_ZOOM_STEPS = [
65890
66504
  0.25,
65891
66505
  0.33,
@@ -66259,6 +66873,14 @@ class BrowserHost {
66259
66873
  facts;
66260
66874
  /** The session's per-origin storage clear, injected (see `BrowserHostOptions`). */
66261
66875
  forgetSiteData;
66876
+ /** S4b's popout health, read from the controller (or the launch's platform
66877
+ * answer when no controller exists). The strip reads this from the same
66878
+ * projection as the tabs, so the renderer has one source for "child or
66879
+ * fallback" and never guesses from a failed IPC call. */
66880
+ addressOverlayMode;
66881
+ /** One instance per host, constructed once in the constructor. `null` when
66882
+ * no path was supplied (see `BrowserHostOptions.historyFilePath`). */
66883
+ historyStore;
66262
66884
  /**
66263
66885
  * The last main-frame load failure per tab, cleared by the next load.
66264
66886
  *
@@ -66284,6 +66906,8 @@ class BrowserHost {
66284
66906
  this.tabAudio = new TabAudioTracker(() => this.onChanged());
66285
66907
  this.facts = options.facts;
66286
66908
  this.forgetSiteData = options.forgetSiteData;
66909
+ this.addressOverlayMode = options.addressOverlayMode ?? (() => ({ mode: "suspend", reason: "unavailable" }));
66910
+ this.historyStore = options.historyFilePath ? createBrowserHistoryStore(options.historyFilePath) : null;
66287
66911
  }
66288
66912
  /** Answer one wire request. The only place a method name becomes an action. */
66289
66913
  async dispatch(method, params, requestId2) {
@@ -66564,6 +67188,7 @@ class BrowserHost {
66564
67188
  new Set(this.registry.snapshot().map((entry) => entry.tabId))
66565
67189
  );
66566
67190
  return {
67191
+ addressOverlay: this.addressOverlayMode(),
66567
67192
  pageControls: this.pageControls.state(active),
66568
67193
  tabs: this.chromeTabOrder.project(this.registry.snapshot()).map((entry, displayOrder) => ({
66569
67194
  ...this.chromeTabOrder.hasCustomOrder ? { displayOrder } : {},
@@ -67150,8 +67775,13 @@ class BrowserHost {
67150
67775
  * no longer matches, which covers the race between this check and the reply
67151
67776
  * landing.
67152
67777
  *
67153
- * Collection is bounded and derived (`address-history.ts`); this method adds
67154
- * only the authority the collector must not pretend to own.
67778
+ * RANKING MOVED HERE with the persistent store: the renderer used to rank a
67779
+ * snapshot of every stack, which cannot scale to a 5000-entry history either
67780
+ * in memory or across IPC. Main now asks the store for its top matches, puts
67781
+ * them ahead of the live-tab rows (`mergeAddressHistory`), and bounds the
67782
+ * answer by `request.limit`. The renderer still derives display text, the
67783
+ * target URL and the inline suffix, because those depend on exactly what the
67784
+ * user typed.
67155
67785
  */
67156
67786
  addressHistory(request) {
67157
67787
  const activeTabId = this.registry.activeTab?.tabId ?? null;
@@ -67161,20 +67791,59 @@ class BrowserHost {
67161
67791
  "the active tab changed before suggestions were read"
67162
67792
  );
67163
67793
  }
67794
+ const live = collectAddressHistory(
67795
+ this.registry.list().map((record) => ({
67796
+ tabId: record.tabId,
67797
+ sessionId: record.sessionId,
67798
+ webContents: record.view.webContents,
67799
+ restoreRow: record.restoreRow ?? null
67800
+ })),
67801
+ activeTabId
67802
+ );
67803
+ const historyMatches = this.historyStore?.suggest(request.query, request.limit) ?? [];
67164
67804
  return {
67165
67805
  requestId: request.requestId,
67166
- ...collectAddressHistory(
67167
- this.registry.list().map((record) => ({
67168
- tabId: record.tabId,
67169
- sessionId: record.sessionId,
67170
- webContents: record.view.webContents,
67171
- restoreRow: record.restoreRow ?? null
67172
- })),
67173
- request.scope,
67174
- activeTabId
67806
+ activeTabId: live.activeTabId,
67807
+ entries: mergeAddressHistory(
67808
+ live.entries,
67809
+ historyMatches,
67810
+ request.query,
67811
+ request.limit
67175
67812
  )
67176
67813
  };
67177
67814
  }
67815
+ /**
67816
+ * Record one committed top-level navigation.
67817
+ *
67818
+ * Called from the per-view wiring in `index.ts`, which is the only place that
67819
+ * sees the event for every tab. A non-HTTP(S) URL is dropped by the store, so
67820
+ * `about:blank`, `chrome://` and a refused scheme never enter the document.
67821
+ * The title is filled in by `recordHistoryTitle` when Chromium announces it;
67822
+ * recording the visit first is deliberate, because the commit is the fact and
67823
+ * the title is a later, optional update.
67824
+ */
67825
+ recordHistoryVisit(url) {
67826
+ this.historyStore?.record(url, "");
67827
+ }
67828
+ /** Update the stored title for a URL that is already in the history. Never
67829
+ * increments a visit: `page-title-updated` fires for a page that has already
67830
+ * been counted, sometimes several times. */
67831
+ recordHistoryTitle(url, title) {
67832
+ this.historyStore?.updateTitle(url, title);
67833
+ }
67834
+ /** Write any debounced history change now (quit, and the host's own stop). */
67835
+ flushHistory() {
67836
+ this.historyStore?.flush();
67837
+ }
67838
+ /** "Clear everything" also forgets the suggestion history (`profile.ts` is
67839
+ * the path that calls this, so cookies/cache alone cannot reach it). */
67840
+ clearHistory() {
67841
+ this.historyStore?.clear();
67842
+ }
67843
+ /** "Forget this site" drops that origin's rows; other origins stay. */
67844
+ clearOriginHistory(origin) {
67845
+ this.historyStore?.clearOrigin(origin);
67846
+ }
67178
67847
  /**
67179
67848
  * A URL the USER typed goes through here, and it is deliberately different from
67180
67849
  * an agent navigation in one way and identical in another (design 6.1):
@@ -68278,6 +68947,7 @@ function tabIdOrThrow(value, name) {
68278
68947
  return id2;
68279
68948
  }
68280
68949
  const CONTROL_CHARS = /[\p{Cc}]/u;
68950
+ const PRODUCT_ADDRESS_OVERLAY_EVENT = "browser-address-overlay-event";
68281
68951
  const BROWSER_IPC_CHANNELS = [
68282
68952
  "browser-state",
68283
68953
  "browser-set-chord-focus",
@@ -68291,6 +68961,7 @@ const BROWSER_IPC_CHANNELS = [
68291
68961
  "browser-close-tabs",
68292
68962
  "browser-activate-tab",
68293
68963
  "browser-address-history",
68964
+ "browser-address-overlay-project",
68294
68965
  "browser-navigate",
68295
68966
  "browser-home",
68296
68967
  "browser-reload",
@@ -68591,6 +69262,21 @@ function registerBrowserIpc(options) {
68591
69262
  }
68592
69263
  );
68593
69264
  }
69265
+ if (options.addressOverlayProduct) {
69266
+ electron.ipcMain.handle(
69267
+ "browser-address-overlay-project",
69268
+ (event, payload) => {
69269
+ authorizeSender(event);
69270
+ const project = options.addressOverlayProduct;
69271
+ if (!project) {
69272
+ throw new Error(
69273
+ "address_overlay_unavailable: the overlay projection is not running."
69274
+ );
69275
+ }
69276
+ return project(payload);
69277
+ }
69278
+ );
69279
+ }
68594
69280
  if (options.addressOverlayProject) {
68595
69281
  electron.ipcMain.handle(
68596
69282
  DEV_DRIVER_ADDRESS_OVERLAY_PROJECT,
@@ -68617,21 +69303,17 @@ function addressHistoryRequestOrThrow(value) {
68617
69303
  const request = value;
68618
69304
  const requestId2 = numberOrThrow(request.requestId, "requestId");
68619
69305
  const expectedTabId = tabIdOrThrow(request.expectedTabId, "expectedTabId");
68620
- let scope;
68621
- if (request.scope === "all") {
68622
- scope = "all";
68623
- } else if (request.scope && typeof request.scope === "object" && typeof request.scope.sessionId === "string") {
68624
- const sessionId2 = request.scope.sessionId.trim();
68625
- if (!sessionId2 || sessionId2.length > ADDRESS_HISTORY_MAX_SESSION_ID_LENGTH || CONTROL_CHARS.test(sessionId2)) {
68626
- throw new Error(
68627
- "The address history scope needs a valid conversation id."
68628
- );
68629
- }
68630
- scope = { sessionId: sessionId2 };
68631
- } else {
68632
- throw new Error("The address history scope is invalid.");
69306
+ if (typeof request.query !== "string") {
69307
+ throw new Error("The address history query must be a string.");
68633
69308
  }
68634
- return { requestId: requestId2, expectedTabId, scope };
69309
+ const query = request.query;
69310
+ if (query.length > ADDRESS_HISTORY_MAX_QUERY_LENGTH || CONTROL_CHARS.test(query)) {
69311
+ throw new Error("The address history query is invalid.");
69312
+ }
69313
+ if (typeof request.limit !== "number" || !Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > ADDRESS_HISTORY_MAX_LIMIT) {
69314
+ throw new Error("The address history limit is invalid.");
69315
+ }
69316
+ return { requestId: requestId2, expectedTabId, query, limit: request.limit };
68635
69317
  }
68636
69318
  function closeTabsIntentOrThrow(value) {
68637
69319
  if (!value || typeof value !== "object") {
@@ -69150,7 +69832,7 @@ function installBrowserSessionHandlers(browserSession, hooks = {}) {
69150
69832
  browserSession.removeListener("will-download", onDownload);
69151
69833
  };
69152
69834
  }
69153
- async function clearBrowsingData(browserSession, what) {
69835
+ async function clearBrowsingData(browserSession, what, history) {
69154
69836
  browserSession.flushStorageData();
69155
69837
  if (what === "cache") {
69156
69838
  await browserSession.clearCache();
@@ -69172,8 +69854,9 @@ async function clearBrowsingData(browserSession, what) {
69172
69854
  }
69173
69855
  await browserSession.clearData();
69174
69856
  await browserSession.clearCache();
69857
+ history?.clear();
69175
69858
  }
69176
- async function clearOriginData(browserSession, origin) {
69859
+ async function clearOriginData(browserSession, origin, history) {
69177
69860
  browserSession.flushStorageData();
69178
69861
  await browserSession.clearData({
69179
69862
  origins: [origin],
@@ -69189,6 +69872,7 @@ async function clearOriginData(browserSession, origin) {
69189
69872
  "cache"
69190
69873
  ]
69191
69874
  });
69875
+ history?.clearOrigin(origin);
69192
69876
  }
69193
69877
  function flushBrowserStorage(browserSession) {
69194
69878
  try {
@@ -70475,6 +71159,9 @@ function refusesNavigation(rawUrl) {
70475
71159
  }
70476
71160
  }
70477
71161
  let activeHost = null;
71162
+ electron.app.on("before-quit", () => {
71163
+ activeHost?.host.flushHistory();
71164
+ });
70478
71165
  function routeBrowserZoomInput(event, input) {
70479
71166
  const candidate = browserChordIntent(
70480
71167
  { ...input, isAutoRepeat: false, isComposing: false },
@@ -70588,6 +71275,17 @@ function startBrowserHost(options) {
70588
71275
  * then it reads the current attempt's callback through `bindings` (a retry
70589
71276
  * replaces it) rather than capturing a controller here.
70590
71277
  */
71278
+ ...options.addressOverlay?.product === true ? {
71279
+ addressOverlayProduct: (payload) => {
71280
+ const project = bindings.addressOverlayProject;
71281
+ if (!project) {
71282
+ throw new Error(
71283
+ "address_overlay_unavailable: the overlay controller is not running."
71284
+ );
71285
+ }
71286
+ return project(payload);
71287
+ }
71288
+ } : {},
70591
71289
  ...options.addressOverlay?.enabled ? {
70592
71290
  addressOverlayProbe: (action, argument) => {
70593
71291
  const probe = bindings.addressOverlayProbe;
@@ -70678,8 +71376,18 @@ async function initializeBrowserHost(options, bindings) {
70678
71376
  return view;
70679
71377
  }, wireView = function(view, tabId) {
70680
71378
  const contents = view.webContents;
70681
- contents.on("focus", () => host.setPageFocused(tabId, true));
71379
+ contents.on("focus", () => {
71380
+ host.setPageFocused(tabId, true);
71381
+ addressOverlay?.dismissForTab(tabId, "page_focus");
71382
+ });
70682
71383
  contents.on("blur", () => host.setPageFocused(tabId, false));
71384
+ contents.on(
71385
+ "did-start-navigation",
71386
+ (_event, _url, isInPlace, isMainFrame) => {
71387
+ if (isMainFrame && !isInPlace)
71388
+ addressOverlay?.dismissForTab(tabId, "navigation");
71389
+ }
71390
+ );
70683
71391
  const popupChildren = /* @__PURE__ */ new Set();
70684
71392
  contents.setWindowOpenHandler((details) => {
70685
71393
  const decision = decidePopup(
@@ -70801,7 +71509,10 @@ async function initializeBrowserHost(options, bindings) {
70801
71509
  notifyChrome();
70802
71510
  });
70803
71511
  contents.on("did-stop-loading", notifyChrome);
70804
- contents.on("page-title-updated", notifyChrome);
71512
+ contents.on("page-title-updated", (_event, title) => {
71513
+ host.recordHistoryTitle(contents.getURL(), title);
71514
+ notifyChrome();
71515
+ });
70805
71516
  contents.on("render-process-gone", () => {
70806
71517
  addressOverlay?.onPageGone(tabId);
70807
71518
  });
@@ -70829,12 +71540,14 @@ async function initializeBrowserHost(options, bindings) {
70829
71540
  stopLogCapture(contents.id);
70830
71541
  startLogCapture(contents.id);
70831
71542
  log2(`[browser] tab ${tabId} navigated to ${url}`);
71543
+ host.recordHistoryVisit(url);
70832
71544
  notifyChanged();
70833
71545
  });
70834
71546
  contents.on("did-navigate-in-page", (_event, url, isMainFrame) => {
70835
71547
  if (!isMainFrame) return;
70836
71548
  registry.bumpEpoch(tabId, false);
70837
71549
  log2(`[browser] tab ${tabId} navigated in page to ${url}`);
71550
+ host.recordHistoryVisit(url);
70838
71551
  notifyChanged();
70839
71552
  });
70840
71553
  contents.on("destroyed", () => {
@@ -70973,6 +71686,9 @@ async function initializeBrowserHost(options, bindings) {
70973
71686
  rollback.add(() => webauthn.dispose());
70974
71687
  const detachWebauthn = attachWebauthnChooser(browserSession, webauthn, log2);
70975
71688
  rollback.add(detachWebauthn);
71689
+ const historyClearer = {
71690
+ current: null
71691
+ };
70976
71692
  const cookieJar = createHiddenCookieJarTarget(
70977
71693
  new electron.WebContentsView({
70978
71694
  webPreferences: { ...HIDDEN_COOKIE_JAR_WEB_PREFERENCES }
@@ -70984,7 +71700,7 @@ async function initializeBrowserHost(options, bindings) {
70984
71700
  cipher: browserProfileCipher(),
70985
71701
  ...sessionCookiePaths(options.userDataDir),
70986
71702
  flushStore: () => browserSession.cookies.flushStore(),
70987
- clearSessionData: (what) => clearBrowsingData(browserSession, what),
71703
+ clearSessionData: (what) => clearBrowsingData(browserSession, what, historyClearer.current),
70988
71704
  log: log2
70989
71705
  });
70990
71706
  let restoreReport = null;
@@ -70999,21 +71715,26 @@ async function initializeBrowserHost(options, bindings) {
70999
71715
  const views = /* @__PURE__ */ new Map();
71000
71716
  const releases = /* @__PURE__ */ new Set();
71001
71717
  let stateWriter = null;
71718
+ let notifyAddressOverlayHealth = () => {
71719
+ };
71002
71720
  const addressOverlay = options.addressOverlay?.enabled === true ? new AddressOverlayController({
71003
71721
  window: options.window,
71004
71722
  documentUrl: options.addressOverlay.documentUrl,
71005
71723
  javascript: options.addressOverlay.list === true,
71006
71724
  visible: options.addressOverlay.visible === true,
71725
+ product: options.addressOverlay.product === true,
71007
71726
  // The only consumer of a validated row click: the owner window's own
71008
71727
  // renderer. Guarded because a stop can race a queued mouse event.
71009
71728
  onEvent: (event) => {
71010
- if (!options.window.isDestroyed()) {
71011
- options.window.webContents.send(
71012
- DEV_DRIVER_ADDRESS_OVERLAY_EVENT,
71013
- event
71014
- );
71015
- }
71729
+ if (options.window.isDestroyed()) return;
71730
+ options.window.webContents.send(
71731
+ options.addressOverlay?.product === true ? PRODUCT_ADDRESS_OVERLAY_EVENT : DEV_DRIVER_ADDRESS_OVERLAY_EVENT,
71732
+ event
71733
+ );
71016
71734
  },
71735
+ // The renderer reads Tier 1 out of the projection, so a child that
71736
+ // fails while the field is already raised must push one.
71737
+ onHealthChange: () => notifyAddressOverlayHealth(),
71017
71738
  log: log2,
71018
71739
  appMetrics: () => electron.app.getAppMetrics().map((metric) => ({
71019
71740
  pid: metric.pid,
@@ -71065,6 +71786,7 @@ async function initializeBrowserHost(options, bindings) {
71065
71786
  }
71066
71787
  captureSession();
71067
71788
  };
71789
+ notifyAddressOverlayHealth = notifyChanged;
71068
71790
  const registry = new TabRegistry(
71069
71791
  (_createOptions, tabId) => {
71070
71792
  const view = buildView(options.window);
@@ -71135,8 +71857,17 @@ async function initializeBrowserHost(options, bindings) {
71135
71857
  log: log2,
71136
71858
  onChanged: notifyChanged,
71137
71859
  facts: () => facts,
71138
- forgetSiteData: (origin) => clearOriginData(browserSession, origin)
71860
+ forgetSiteData: (origin) => clearOriginData(browserSession, origin, historyClearer.current),
71861
+ historyFilePath: path$1.join(options.userDataDir, "browser-history.json"),
71862
+ addressOverlayMode: () => addressOverlay ? addressOverlay.mode() : {
71863
+ mode: "suspend",
71864
+ reason: options.addressOverlay?.product === true ? "unavailable" : "platform"
71865
+ }
71139
71866
  });
71867
+ historyClearer.current = {
71868
+ clear: () => host.clearHistory(),
71869
+ clearOrigin: (origin) => host.clearOriginHistory(origin)
71870
+ };
71140
71871
  const ownsChrome = () => browserChromeOwnsChord({
71141
71872
  logicalFocus: host.chordFocus,
71142
71873
  chromeVisible: registry.chromeVisible,
@@ -71384,6 +72115,7 @@ async function initializeBrowserHost(options, bindings) {
71384
72115
  ...addressOverlay ? { addressOverlay: probeAddressOverlay } : {},
71385
72116
  stop: async () => {
71386
72117
  detachChords();
72118
+ host.flushHistory();
71387
72119
  const atStop = captureTabs(
71388
72120
  registry.list(),
71389
72121
  registry.activeTab?.tabId ?? null,
@@ -82715,6 +83447,12 @@ const addressOverlayGate = resolveAddressOverlayGate({
82715
83447
  windowMode: windowLaunch.mode,
82716
83448
  packaged: electron.app.isPackaged
82717
83449
  });
83450
+ const addressOverlayProduct = process.platform === "darwin" && !addressOverlayGate.enabled;
83451
+ if (addressOverlayProduct) {
83452
+ console.log(
83453
+ "[address-overlay] product chrome armed (darwin): popout field over the tab strip, native list child"
83454
+ );
83455
+ }
82718
83456
  if (addressOverlayGate.asked) {
82719
83457
  console.log(
82720
83458
  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}`
@@ -83137,7 +83875,7 @@ electron.app.whenReady().then(async () => {
83137
83875
  });
83138
83876
  const rendererUrl = rendererDocumentUrl();
83139
83877
  const consoleCaptureUrl = consoleCaptureUrlFor(rendererUrl);
83140
- const addressOverlayDocumentUrl = addressOverlayGate.list ? addressListDocumentUrlFor(rendererUrl) : addressOverlayDocumentUrlFor(rendererUrl);
83878
+ const addressOverlayDocumentUrl = addressOverlayGate.list || addressOverlayProduct ? addressListDocumentUrlFor(rendererUrl) : addressOverlayDocumentUrlFor(rendererUrl);
83141
83879
  backendService.observeDesktopFeed(
83142
83880
  (frame) => {
83143
83881
  if (frame.type === "notification") {
@@ -83948,11 +84686,12 @@ electron.app.whenReady().then(async () => {
83948
84686
  consoleCaptureUrl,
83949
84687
  // S1: only a gated headless probe arm gets this option at all. The
83950
84688
  // URL is this app's own packaged document, not a caller's string.
83951
- ...addressOverlayGate.enabled ? {
84689
+ ...addressOverlayGate.enabled || addressOverlayProduct ? {
83952
84690
  addressOverlay: {
83953
84691
  enabled: true,
83954
- list: addressOverlayGate.list,
84692
+ list: addressOverlayGate.list || addressOverlayProduct,
83955
84693
  visible: addressOverlayGate.visible,
84694
+ product: addressOverlayProduct,
83956
84695
  documentUrl: addressOverlayDocumentUrl
83957
84696
  }
83958
84697
  } : {},