Theseus: address-picker URL bar + reload keys (F5 / Ctrl+R / Ctrl+Shift+R)

Address-picker fix:
Picking a suggestion from the address dropdown loaded the URL but
left the address bar showing the 3-4 letters the user had typed.
Root cause: onTabs's focus guard
  if (document.activeElement !== $("url")) $("url").value = d.url
skipped its write while the URL input still held DOM focus, and
clicking a sibling WebContentsView doesn't always deliver the blur
to the chrome renderer in time. Fix: address-pick sends an explicit
address-picked IPC to the chrome, which force-blurs and writes the
full picked URL before the tabs event arrives.

Reload keys:
None of the standard reload accelerators worked (Menu.setApplicationMenu(null)
drops Chromium's default menu accelerators, and hard-reload was never
wired at any layer). Now:
  F5 / Ctrl+R         -> soft reload
  Ctrl+F5 / Ctrl+Shift+R -> hard reload (reloadIgnoringCache)
  Shift-click on the toolbar reload button -> hard reload
Same before-input-event hook that carries Ctrl+Shift+M; always targets
the active tab regardless of which view got the key, and skips the
Settings tab. Toolbar tooltip updated to advertise Shift-click.
This commit is contained in:
Local Dev 2026-08-31 02:50:57 +02:00
parent 755e97b5bb
commit 10cddcacca
3 changed files with 34 additions and 8 deletions

View file

@ -267,7 +267,9 @@
$("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) T.search(q); } }); $("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) T.search(q); } });
$("back").onclick = () => T.back(); $("back").onclick = () => T.back();
$("fwd").onclick = () => T.forward(); $("fwd").onclick = () => T.forward();
$("reload").onclick = () => T.reload(); // Shift-click on the reload button = hard reload (bypass cache), same
// convention as Chrome/Firefox. Keyboard alternative: Ctrl+Shift+R.
$("reload").onclick = (e) => T.reload(e.shiftKey);
$("home").onclick = () => T.goHome(); $("home").onclick = () => T.goHome();
$("logo").onclick = () => T.openSettings(); $("logo").onclick = () => T.openSettings();
$("secbadge").onclick = () => { const r = $("secbadge").getBoundingClientRect(); T.toggleSiteInfo({ x: Math.round(r.left), y: Math.round(r.bottom + 4) }); }; $("secbadge").onclick = () => { const r = $("secbadge").getBoundingClientRect(); T.toggleSiteInfo({ x: Math.round(r.left), y: Math.round(r.bottom + 4) }); };
@ -511,7 +513,7 @@
$("loadbar").classList.toggle("on", !!d.loading); $("loadbar").classList.toggle("on", !!d.loading);
const rb = $("reload"); const rb = $("reload");
if (d.loading) { rb.innerHTML = STOP_SVG; rb.title = "Stop"; rb.onclick = () => T.stop(); } if (d.loading) { rb.innerHTML = STOP_SVG; rb.title = "Stop"; rb.onclick = () => T.stop(); }
else { rb.innerHTML = RELOAD_SVG; rb.title = "Reload"; rb.onclick = () => T.reload(); } else { rb.innerHTML = RELOAD_SVG; rb.title = "Reload (Shift-click: hard reload)"; rb.onclick = (ev) => T.reload(ev.shiftKey); }
if (document.activeElement !== $("url")) $("url").value = d.url || ""; if (document.activeElement !== $("url")) $("url").value = d.url || "";
current.url = d.url || ""; current.url = d.url || "";
const active = d.tabs.find((t) => t.active); const active = d.tabs.find((t) => t.active);

34
main.js
View file

@ -1761,7 +1761,11 @@ ipcMain.handle("home-cards-set", (e, cards) => { if (!isHomePageSender(e.sende
ipcMain.handle("home-cards-reset", (e) => { if (!isHomePageSender(e.sender)) return false; try { fs.unlinkSync(homeCardsFile()); } catch {} return true; }); ipcMain.handle("home-cards-reset", (e) => { if (!isHomePageSender(e.sender)) return false; try { fs.unlinkSync(homeCardsFile()); } catch {} return true; });
ipcMain.handle("go-back", () => { const wc = activeTab()?.view.webContents; if (wc?.navigationHistory.canGoBack()) wc.navigationHistory.goBack(); }); ipcMain.handle("go-back", () => { const wc = activeTab()?.view.webContents; if (wc?.navigationHistory.canGoBack()) wc.navigationHistory.goBack(); });
ipcMain.handle("go-forward", () => { const wc = activeTab()?.view.webContents; if (wc?.navigationHistory.canGoForward()) wc.navigationHistory.goForward(); }); ipcMain.handle("go-forward", () => { const wc = activeTab()?.view.webContents; if (wc?.navigationHistory.canGoForward()) wc.navigationHistory.goForward(); });
ipcMain.handle("reload", () => activeTab()?.view.webContents.reload()); ipcMain.handle("reload", (_e, hard) => {
const wc = activeTab()?.view.webContents;
if (!wc) return;
try { hard ? wc.reloadIgnoringCache() : wc.reload(); } catch {}
});
ipcMain.handle("stop", () => { const t = activeTab(); try { t?.view.webContents.stop(); } catch {} setLoading(t, false); }); ipcMain.handle("stop", () => { const t = activeTab(); try { t?.view.webContents.stop(); } catch {} setLoading(t, false); });
ipcMain.handle("toggle-tor", () => { torState === "off" ? startTor() : stopTor(); }); ipcMain.handle("toggle-tor", () => { torState === "off" ? startTor() : stopTor(); });
ipcMain.handle("open-settings", () => { const ex = tabs.find((t) => t.settings); if (ex) return setActive(ex.id); createTab(null, { settings: true }); }); ipcMain.handle("open-settings", () => { const ex = tabs.find((t) => t.settings); if (ex) return setActive(ex.id); createTab(null, { settings: true }); });
@ -2543,15 +2547,35 @@ ipcMain.handle("bcnr:getRecordVersion", async (_e, name) => {
} catch { return null; } } catch { return null; }
}); });
// Ctrl+Shift+M anywhere in Theseus opens (or focuses) the Messages panel. // App-level keyboard shortcuts. One `web-contents-created` hook covers
// One app-level hook: no per-window wiring, no per-tab plumbing, matches // every WebContents (chrome, tab views, overlays) without per-view wiring.
// however many WebContents Theseus ends up owning. // Reload/hard-reload always target the active tab, regardless of which
// view received the key (URL bar focused, overlay focused, etc.), so the
// user's mental model matches every browser they've ever used.
app.on("web-contents-created", (_event, wc) => { app.on("web-contents-created", (_event, wc) => {
wc.on("before-input-event", (e, input) => { wc.on("before-input-event", (e, input) => {
if (input.type !== "keyDown") return; if (input.type !== "keyDown") return;
// Ctrl+Shift+M -> Messages panel
if (input.control && input.shift && (input.key === "M" || input.key === "m")) { if (input.control && input.shift && (input.key === "M" || input.key === "m")) {
openHermesWindow(); openHermesWindow();
e.preventDefault(); return e.preventDefault();
}
// Reload: F5 or Ctrl+R soft; Ctrl+F5 or Ctrl+Shift+R hard (bypass cache).
// Chromium's built-in accelerators are unreliable once the app menu is
// null (Menu.setApplicationMenu(null) above), and none of them cover
// the hard variants anyway — so we wire all four explicitly.
const isR = input.key === "R" || input.key === "r";
const isF5 = input.key === "F5";
if (isF5 || (input.control && isR)) {
const t = activeTab();
if (t && !t.settings) {
const hard = (input.control && input.shift && isR) || (input.control && isF5);
try {
if (hard) t.view.webContents.reloadIgnoringCache();
else t.view.webContents.reload();
} catch {}
}
return e.preventDefault();
} }
}); });
}); });

View file

@ -20,7 +20,7 @@ contextBridge.exposeInMainWorld("theseus", {
goHome: () => ipcRenderer.invoke("go-home"), goHome: () => ipcRenderer.invoke("go-home"),
back: () => ipcRenderer.invoke("go-back"), back: () => ipcRenderer.invoke("go-back"),
forward: () => ipcRenderer.invoke("go-forward"), forward: () => ipcRenderer.invoke("go-forward"),
reload: () => ipcRenderer.invoke("reload"), reload: (hard) => ipcRenderer.invoke("reload", !!hard),
stop: () => ipcRenderer.invoke("stop"), stop: () => ipcRenderer.invoke("stop"),
toggleTor: () => ipcRenderer.invoke("toggle-tor"), toggleTor: () => ipcRenderer.invoke("toggle-tor"),
openSettings: () => ipcRenderer.invoke("open-settings"), openSettings: () => ipcRenderer.invoke("open-settings"),