diff --git a/bundled-addons/pithos/addon.json b/bundled-addons/pithos/addon.json index 61ad32fd..04b6be20 100644 --- a/bundled-addons/pithos/addon.json +++ b/bundled-addons/pithos/addon.json @@ -6,6 +6,7 @@ "author": "Silent Mode", "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIwIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2RkYzk3Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMWU4ZjZhIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTIyIDZoMjB2NWMwIDItMiAzLTIgNSA4IDMgMTQgMTEgMTQgMjEgMCAxMi05IDIxLTIyIDIxUzEwIDQ5IDEwIDM3YzAtMTAgNi0xOCAxNC0yMSAwLTItMi0zLTItNXoiIGZpbGw9InVybCgjZykiLz48cGF0aCBkPSJNMTQgMzBoMzZNMTMgNDBoMzgiIHN0cm9rZT0iIzBiMWYxOCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9Ii41NSIvPjwvc3ZnPgo=", "main": "index.js", + "activation": "on-demand", "capabilities": [ "sidebar-panel", "vault-derive", diff --git a/home.html b/home.html index 81f658e5..94e2d1b0 100644 --- a/home.html +++ b/home.html @@ -107,9 +107,9 @@ specific slot. Order matches the grid's cell index. Subtly visible so users see where they can drop; brighter on hover of the strip. */ .slot-empty { display: block; width: 48px; height: 48px; margin: 0 auto; - border-radius: 10px; border: 2px dashed rgb(from var(--line2) r g b / .55); + border-radius: 10px; border: 2px dashed transparent; background: transparent; transition: border-color .12s, background .12s; } - .slots-list:hover .slot-empty { border-color: var(--line2); } + .slots-list:hover .slot-empty { border-color: rgb(from var(--line2) r g b / .55); } .slot-empty.drop-hot { border-color: var(--acid); background: rgb(from var(--acid) r g b / .06); } /* RIGHT COLUMN — just the 3-column grid of Silent Mode cards; search @@ -148,11 +148,15 @@ } .card.drag-before::before { left: -8px; } .card.drag-after::after { right: -8px; } - .card-add { display: flex; align-items: center; justify-content: center; min-height: 128px; - border: 2px dashed var(--line2); border-radius: 14px; background: transparent; + /* Matches .card's horizontal shape so a "+ Add card" cell sits in the + grid as a peer of the real cards instead of a short dashed strip. */ + .card-add { display: flex; align-items: center; justify-content: center; + gap: 14px; padding: 14px 16px; min-height: 92px; + border: 2px dashed var(--line2); border-radius: 14px; + background: rgba(20,26,36,.35); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); color: var(--mut); font-size: 13.5px; cursor: pointer; transition: color .12s, border-color .12s, background .12s; } - .card-add:hover { color: var(--acid); border-color: var(--acid); background: rgb(from var(--acid) r g b / .04); } + .card-add:hover { color: var(--acid); border-color: var(--acid); background: rgb(from var(--acid) r g b / .06); } /* Context menus. One for the background (edit actions), one for images (copy / edit / remove). Positioned at the pointer by JS. */ @@ -237,11 +241,9 @@
-
Your links
-
Silent Mode
diff --git a/main.js b/main.js index 23b48e28..2ea4082a 100644 --- a/main.js +++ b/main.js @@ -549,6 +549,8 @@ const SETTINGS_DEFAULTS = { // extension's activate() runs at launch, before the window exists. extensionsOnDemand: true, // extensions whose manifest allows it start on first use walletAtLaunch: false, // start Aegis at launch even once it allows on-demand (no effect while it is startup-only) + pithosAtLaunch: false, // start Pithos at launch (otherwise it wakes on first panel open or a pithos.sia navigation) + hermesAutoRestore: true, // open your Nostr relays 1.5s after launch so Hermes messages arrive before you open the app (off = relays connect on first open) preloadMenus: true, // load address suggestions / link pill / site info after the first page (prewarmOverlays) addonsStartAtLaunch: [], // on-demand add-ons that asked (api.startAtLaunch) to start at launch anyway // Storage retention — nothing persists by default. Auto-clear on quit @@ -3280,6 +3282,7 @@ function initAddons() { if (m.activation !== "on-demand") return "startup"; if (settings.extensionsOnDemand === false) return "startup"; if (m.id === "aegis" && settings.walletAtLaunch) return "startup"; + if (m.id === "pithos" && settings.pithosAtLaunch) return "startup"; if (Array.isArray(settings.addonsStartAtLaunch) && settings.addonsStartAtLaunch.includes(m.id)) return "startup"; return "on-demand"; }, @@ -9705,6 +9708,7 @@ ipcMain.handle("settings-set", (_e, key, val) => { // leaves running add-ons alone; it applies from the next launch. if (addonHost && key === "extensionsOnDemand" && val === false) addonHost.activateAllDormant("setting").catch(() => {}); if (addonHost && key === "walletAtLaunch" && val && addonHost.isDormant("aegis")) addonHost.ensureActive("aegis", "setting").catch(() => {}); + if (addonHost && key === "pithosAtLaunch" && val && addonHost.isDormant("pithos")) addonHost.ensureActive("pithos", "setting").catch(() => {}); if (key === "preloadMenus" && val && chromeReadyDone) prewarmOverlays(); if (key === "freezeBackgroundTabs") applyFreezeSetting(); if (["timezoneMode", "timezoneValue", "languageMode", "languageSpoof", "languageValue", @@ -10648,7 +10652,14 @@ async function hermesAutoRestore() { } // Fire a little after app ready so loadHermesLib's dynamic import and // safeStorage both exist. Non-blocking. -app.whenReady().then(() => setTimeout(() => { void hermesAutoRestore(); }, 1500)); +app.whenReady().then(() => setTimeout(() => { + // Gated so a user who doesn't want background relay traffic (laptop on + // battery, metered connection, or they just don't use Hermes) can opt + // out in Settings › Performance › Startup. Default on so first-run + // users keep getting messages before they open the panel. + if (settings.hermesAutoRestore === false) return; + void hermesAutoRestore(); +}, 1500)); const hOk = (o = {}) => ({ ok: true, ...o }); const hErr = (e) => ({ ok: false, err: String((e && e.message) || e) }); @@ -10729,17 +10740,44 @@ async function hermesReverseResolve(pkHex) { // One receive subscription per relay. If the socket dies we resurrect it on a // backoff — a locked / logged-out state tears them all down cleanly. +// Zombie-socket mitigation: ping every PING_MS, close + reconnect if no pong +// within PONG_TIMEOUT_MS. Without this the TCP state can be "connected" while +// the relay has silently stopped delivering, and new incoming kind:1059s only +// arrive after the user's next outbound send forces a write-error and triggers +// close → reconnect. Nasty invisible lag on long-lived Electron sessions. +const HERMES_PING_MS = 30_000; +const HERMES_PONG_TIMEOUT_MS = 10_000; async function hermesConnectRelay(url) { const H = await loadHermesLib(); - const state = { ws: null, ready: false, subId: "hermes-inbox" }; + const state = { ws: null, ready: false, subId: "hermes-inbox", pingTimer: null, pongTimer: null }; const open = () => { if (!hermesState) return; // torn down while reconnecting const ws = new WebSocket(url); state.ws = ws; + const clearPingers = () => { + if (state.pingTimer) { clearInterval(state.pingTimer); state.pingTimer = null; } + if (state.pongTimer) { clearTimeout(state.pongTimer); state.pongTimer = null; } + }; + const schedulePing = () => { + clearPingers(); + state.pingTimer = setInterval(() => { + try { ws.ping(); } catch { return; } + if (state.pongTimer) clearTimeout(state.pongTimer); + state.pongTimer = setTimeout(() => { + // No pong in time → the socket is zombie. Terminate forces an + // immediate close event which drives the reconnect loop below. + try { ws.terminate(); } catch {} + }, HERMES_PONG_TIMEOUT_MS); + }, HERMES_PING_MS); + }; ws.on("open", () => { state.ready = true; hermesEmitStatus(); ws.send(JSON.stringify(["REQ", state.subId, { kinds: [1059], "#p": [hermesState.pkHex] }])); + schedulePing(); + }); + ws.on("pong", () => { + if (state.pongTimer) { clearTimeout(state.pongTimer); state.pongTimer = null; } }); ws.on("message", async (buf) => { let msg; try { msg = JSON.parse(buf.toString()); } catch { return; } @@ -10775,6 +10813,7 @@ async function hermesConnectRelay(url) { }); ws.on("close", () => { state.ready = false; + clearPingers(); hermesEmitStatus(); // Attempt reconnect if we're still supposed to be running. if (hermesState && hermesState.relays.get(url) === state) { @@ -10790,6 +10829,8 @@ async function hermesConnectRelay(url) { function hermesTeardown() { if (!hermesState) return; for (const state of hermesState.relays.values()) { + if (state.pingTimer) { try { clearInterval(state.pingTimer); } catch {} state.pingTimer = null; } + if (state.pongTimer) { try { clearTimeout(state.pongTimer); } catch {} state.pongTimer = null; } try { state.ws?.close(1000); } catch {} } hermesState = null; diff --git a/package.json b/package.json index 5457ebf6..71149242 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "theseus-navigator", - "version": "0.3.111", + "version": "0.3.112", "license": "MPL-2.0", "description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.", "author": "Silent Mode", diff --git a/quicklinks.html b/quicklinks.html index 0d8f545c..f834d45a 100644 --- a/quicklinks.html +++ b/quicklinks.html @@ -64,7 +64,7 @@
- + @@ -91,6 +91,9 @@ const r = llmBtn.getBoundingClientRect(); Q && Q.llmMenu && Q.llmMenu({ x: r.left, y: r.top, width: r.width, height: r.height }); }); + llmChev.addEventListener("keydown", (e) => { + if (e.key === "Enter" || e.key === " ") { e.preventDefault(); e.stopPropagation(); const r = llmBtn.getBoundingClientRect(); Q && Q.llmMenu && Q.llmMenu({ x: r.left, y: r.top, width: r.width, height: r.height }); } + }); llmChev.addEventListener("click", (e) => { e.stopPropagation(); const r = llmBtn.getBoundingClientRect(); diff --git a/settings.html b/settings.html index 0e363f86..2a9a3731 100644 --- a/settings.html +++ b/settings.html @@ -887,6 +887,14 @@
Start the wallet at launch
Aegis is ready the moment a site asks for it, at the cost of a slower start.
+
+
Start Pithos at launch
Pithos boots its control server for the Sia S3 gateway right away. Off: it wakes on first panel open or when you visit a pithos.sia page.
+ +
+
+
Connect Hermes relays at launch
Open your Nostr relays shortly after launch so Hermes messages arrive before you open the panel. Off: relays connect the first time you open Messages.
+ +
Preload common menus
After the first page loads, get address suggestions, the link preview and site info ready in the background so they open instantly. Off saves memory; each opens a moment slower the first time.
@@ -1416,7 +1424,7 @@ } onDemand.addEventListener("change", syncWallet); $("lightStart").addEventListener("click", () => { - const want = { extensionsOnDemand: true, walletAtLaunch: false, preloadMenus: false }; + const want = { extensionsOnDemand: true, walletAtLaunch: false, pithosAtLaunch: false, hermesAutoRestore: false, preloadMenus: false }; for (const [k, v] of Object.entries(want)) { $(k).checked = v; C.set(k, v); } syncWallet(); }); @@ -1424,7 +1432,7 @@ // Running counts change as extensions start; refresh when the page is shown. document.addEventListener("visibilitychange", () => { if (!document.hidden) refresh(); }); })(); - const TOGGLES = ["restoreSession", "backgroundThrottle", "freezeBackgroundTabs", "extensionsOnDemand", "walletAtLaunch", "preloadMenus", "blockCamera", "blockMicrophone", "hideMediaDevices", "blockScreenCapture", "gpc", + const TOGGLES = ["restoreSession", "backgroundThrottle", "freezeBackgroundTabs", "extensionsOnDemand", "walletAtLaunch", "pithosAtLaunch", "hermesAutoRestore", "preloadMenus", "blockCamera", "blockMicrophone", "hideMediaDevices", "blockScreenCapture", "gpc", "clearCookiesOnQuit", "keepSignInsOnQuit", "clearCacheOnQuit", "clearStorageOnQuit", "clearHistoryOnQuit", "pwOfferSave", "pwOfferFill", "quickLinksShow", "translateAutoOffer",