From c2ec0f0d0240c33ff4d87a269126129647e30cfb Mon Sep 17 00:00:00 2001 From: Silent Mode Date: Sat, 3 Oct 2026 11:03:53 +0200 Subject: [PATCH] Theseus 0.3.70: fully-lazy session restore, quick-links strip reordered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session restore no longer loads any page on launch. In 0.3.63 the strip was built from saved titles + favicons and only the previously-active tab's URL was navigated at startup, so a 20-tab session cost one renderer load instead of twenty — but that one load is still a real page, often the heaviest one in the whole session, and it fought every other startup task for the main thread while the window sat not-responding. Now every restored tab, the previously-active one included, comes up dormant: zero page renderers at launch, no page starts loading until the user asks for a specific tab (clicks the chip, hits reload, types in the URL bar). The previously-active tab stays highlighted in the strip so one click brings it back; the content area sits with the tab's own background colour until that click. RAM-at- launch is now just the chrome, the overlays and the strip — a 500 MB saved page never materialises as a renderer the user did not even ask to see. A pending tab that is navigated explicitly (URL bar, link, search) drops its saved URL at the top of navigateTab, so a later reload or chip click can't snap it back. Quick-links strip default set is now Telegram, WhatsApp, X, YouTube — in that order. Messenger and Spotify are out of the default; users who want them can still add them via Settings › General › Quick links. Existing installs whose list still matches the previous untouched default (same six ids in the same order) migrate on next launch; any customisation (reorder, add, remove) is left alone. --- main.js | 60 +++++++++++++++++++++++++++++++++++----------------- package.json | 2 +- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/main.js b/main.js index f897bac8..77d3f271 100644 --- a/main.js +++ b/main.js @@ -522,12 +522,10 @@ const SETTINGS_DEFAULTS = { // service shortcuts; click opens the URL in a new tab. Toggle via settings. quickLinksShow: true, quickLinks: [ - { id: "messenger", url: "https://www.messenger.com/", title: "Messenger" }, - { id: "whatsapp", url: "https://web.whatsapp.com/", title: "WhatsApp" }, { id: "telegram", url: "https://web.telegram.org/k/", title: "Telegram" }, + { id: "whatsapp", url: "https://web.whatsapp.com/", title: "WhatsApp" }, { id: "x", url: "https://x.com/", title: "X" }, { id: "youtube", url: "https://www.youtube.com/", title: "YouTube" }, - { id: "spotify", url: "https://open.spotify.com/", title: "Spotify" }, ], // Toolbar bar sizes. The URL bar is flex:1 by default so it eats all // remaining space; `compact`/`medium` cap it so the extension dock has @@ -580,6 +578,17 @@ function loadSettings() { settings.searchEngine = enabled.find((x) => !isFrozen(x)) || DEFAULT_ENABLED[0]; } } catch (e) { console.warn("engine normalize:", e?.message); } + // Quick-links default set changed in 0.3.70: drop Messenger + Spotify and + // reorder to Telegram, WhatsApp, X, YouTube. Users who kept the previous + // untouched default (same 6 ids, same order) move to the new set; any + // customisation (reorder, add, remove) is left alone. + try { + const PRIOR = ["messenger", "whatsapp", "telegram", "x", "youtube", "spotify"]; + const have = Array.isArray(settings.quickLinks) ? settings.quickLinks.map((L) => String(L?.id || "")) : []; + if (have.length === PRIOR.length && have.every((id, i) => id === PRIOR[i])) { + settings.quickLinks = SETTINGS_DEFAULTS.quickLinks.map((L) => ({ ...L })); + } + } catch (e) { console.warn("quickLinks migrate:", e?.message); } } function saveSettings() { try { fs.writeFileSync(settingsFile(), JSON.stringify(settings, null, 2)); } catch (e) { console.error("settings save failed:", e.message); } @@ -1272,26 +1281,35 @@ function loadSession() { return { tabs: urls.map((url) => ({ url, title: "", favicon: null })), active }; } catch { return empty; } } -// Session restore, LAZY: only the active tab actually loads a page at startup. -// Every other restored tab is a dormant WebContentsView in the strip — title -// and favicon from the saved session — and only navigates when the user clicks -// it (setActive consumes `pending`). This drops N renderer loads at startup -// to 1, so launch time is roughly flat whether the user has 2 or 25 tabs. +// Session restore, FULLY LAZY: nothing navigates at launch. Every restored +// tab — including the one that was active at close — comes up as a dormant +// WebContentsView in the strip, painted from the saved title + favicon, and +// only triggers a page load when the user asks for it specifically (clicks +// the chip, hits reload, types in the URL bar). This drops the whole startup +// renderer load to zero page renderers (chrome.html + the overlays + the +// strip only), so launch is near-flat whether the session has 2 or 50 tabs +// and the RAM-at-launch footprint is just the chrome plus the strip — +// a saved heavy page doesn't come back as a 500 MB renderer the user +// didn't even ask to see. The previously active tab stays highlighted in +// the strip so the user can one-click it back; its content area sits with +// the tab's solid background until that happens. function restoreTabs() { const saved = settings.restoreSession ? loadSession() : { tabs: [], active: 0 }; if (!saved.tabs.length) { createTab(); return; } - const activeMeta = saved.tabs[saved.active] || saved.tabs[0]; - createTab(activeMeta.url); - // Build the rest as lazy. Insert at their saved slot so the strip order - // matches the save. for (let i = 0; i < saved.tabs.length; i++) { - if (i === saved.active) continue; - try { - createTab(null, { background: true, pending: saved.tabs[i] }); - // createTab appends; move it into position. - const t = tabs.pop(); - tabs.splice(Math.min(i, tabs.length), 0, t); - } catch (e) { console.warn("session restore: tab failed:", e?.message); } + try { createTab(null, { background: true, pending: saved.tabs[i] }); } + catch (e) { console.warn("session restore: tab failed:", e?.message); } + } + // Pick the saved active slot as the chip the strip highlights at launch. + // A plain setActive() would materialise it (defeating the point of fully- + // lazy restore), so apply activeId + visibility by hand — the user + // clicking that same chip is what triggers the first navigation. + const idx = Math.min(Math.max(0, saved.active | 0), tabs.length - 1); + const target = tabs[idx]; + if (target) { + activeId = target.id; + try { target.view.setVisible(true); } catch {} + for (const t of tabs) if (t.id !== activeId) { try { t.view.setVisible(false); } catch {} } } emitTabs(); } @@ -4089,6 +4107,10 @@ function createWindow() { async function navigateTab(id, input) { const t = tabById(id); if (!t) return; + // A pending tab the user navigates explicitly (URL bar, link follow, + // search) has outgrown its saved URL — otherwise a later reload or chip + // click would snap it back to that stale URL via materializePending. + if (t.pending) t.pending = null; let q = String(input).trim(); if (!q) return; // theseus://extensions/install/ typed or pasted into the address bar. diff --git a/package.json b/package.json index c058d4c1..92f620be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "theseus-navigator", - "version": "0.3.69", + "version": "0.3.70", "license": "MPL-2.0", "description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.", "author": "Silent Mode",