From d0db4ac495cc9adfc5b78c4af21c0f1c8e244196 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 2 Aug 2026 11:39:00 +0200 Subject: [PATCH] Collision modes (BCNR/ICANN) + root TLD cert + VPS electrum-source indexer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Theseus soft-mode UX: 'Open with...' modal on collision, per-name/per-TLD overrides, live per-tab switcher in the site-info popover, and a Naming section in Settings for policy + reset. Backed by an on-chain root TLD certificate (tlds.bch) that resolver-web.js discovers via fetchBcnrTlds()/isBcnrNativeTld(). Companion pieces: - Argus/src/indexer/ELECTRUM-SOURCE-README.md — the featherweight VPS variant (no BCHN node, no Fulcrum) now live as bns-indexer.service. - Argus/DESIGN-root-tld-cert.md — clarified: NOT a governance workflow, just ordinary key management (single wallet MVP -> 2-of-3 multisig). List gates registration / surgical NRPT / soft-mode classifier — never resolution. - ROADMAP-IDEAS.md — recorded SiaGit/GitHub.sia + user-friendly Sia UI ideas. Full spec: Argus/DESIGN-collision-modes.md (already tracked). --- collision-preload.js | 7 ++ collision.html | 94 +++++++++++++++++ main.js | 214 ++++++++++++++++++++++++++++++++----- package.json | 2 + popover-preload.js | 3 + popover.html | 47 +++++++++ preload.js | 8 ++ settings-preload.js | 4 + settings.html | 243 +++++++++++++++++++++++++++++++++++++------ 9 files changed, 568 insertions(+), 54 deletions(-) create mode 100644 collision-preload.js create mode 100644 collision.html diff --git a/collision-preload.js b/collision-preload.js new file mode 100644 index 0000000..e0c97a8 --- /dev/null +++ b/collision-preload.js @@ -0,0 +1,7 @@ +// Minimal, sandboxed IPC bridge for the "Open with…" collision prompt window. +// Only exposes the exact call the page needs: send the user's choice back to +// the main process (which awaits it via ipcMain.once). +const { contextBridge, ipcRenderer } = require("electron"); +contextBridge.exposeInMainWorld("collisionApi", { + choose(host, payload) { ipcRenderer.send(`collision-choose:${host}`, payload); }, +}); diff --git a/collision.html b/collision.html new file mode 100644 index 0000000..e129565 --- /dev/null +++ b/collision.html @@ -0,0 +1,94 @@ + + + + + Open with… + + + + +

exists in two registries

+
Choose which one to open. You can change this later in Settings.
+ +
+ + +
+ +
+
+
+
+
+ +
+ +
+ + + + diff --git a/main.js b/main.js index b13be3e..c7fe59e 100644 --- a/main.js +++ b/main.js @@ -4,7 +4,7 @@ // h, Sia s3, direct ip, redirect u). Tabs, nav controls, a search box, a home // page, and optional Tor onion routing. No system daemon; the app is the trust // boundary. -const { app, BrowserWindow, WebContentsView, ipcMain, protocol, session, Menu, clipboard, nativeTheme } = require("electron"); +const { app, BrowserWindow, WebContentsView, ipcMain, protocol, session, Menu, clipboard, nativeTheme, shell } = require("electron"); const path = require("path"); const http = require("http"); const https = require("https"); @@ -21,27 +21,29 @@ const RES_DIR = app.isPackaged ? process.resourcesPath : __dirname; const RESOLVER = app.isPackaged ? path.join(RES_DIR, "resolver-web.mjs") : path.join(__dirname, "..", "Argus", "src", "lib", "resolver-web.js"); -// Built-in search engines. Users can also add their own (settings.customEngines, +// Built-in engines. Users can also add their own (settings.customEngines, // each { id, name, url } where the url contains "%s" for the query). // Catalog of built-in engines (users pick which to enable + can add their own). // fav = the domain to load a real favicon from; sym = emoji fallback. +// kind = "search" (traditional search engines) or "llm" (AI answer engines). +// The picker + settings render each kind in its own section; kind stays a +// display-only grouping (URL routing is the same for both). const SEARCH_ENGINES = { - duckduckgo: { name: "DuckDuckGo", sym: "🦆", fav: "duckduckgo.com", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) }, - google: { name: "Google", sym: "🔵", fav: "www.google.com", url: (q) => "https://www.google.com/search?q=" + encodeURIComponent(q) }, - brave: { name: "Brave", sym: "🦁", fav: "search.brave.com", url: (q) => "https://search.brave.com/search?q=" + encodeURIComponent(q) }, - bing: { name: "Bing", sym: "🔎", fav: "www.bing.com", url: (q) => "https://www.bing.com/search?q=" + encodeURIComponent(q) }, - startpage: { name: "Startpage", sym: "🛡️", fav: "www.startpage.com", url: (q) => "https://www.startpage.com/sp/search?query=" + encodeURIComponent(q) }, - yandex: { name: "Yandex", sym: "🔴", fav: "yandex.com", url: (q) => "https://yandex.com/search/?text=" + encodeURIComponent(q) }, - ecosia: { name: "Ecosia", sym: "🌱", fav: "www.ecosia.org", url: (q) => "https://www.ecosia.org/search?q=" + encodeURIComponent(q) }, - mojeek: { name: "Mojeek", sym: "🧭", fav: "www.mojeek.com", url: (q) => "https://www.mojeek.com/search?q=" + encodeURIComponent(q) }, - searxng: { name: "SearXNG", sym: "🧩", fav: "searx.be", url: (q) => "https://searx.be/search?q=" + encodeURIComponent(q) }, - wikipedia: { name: "Wikipedia", sym: "📖", fav: "en.wikipedia.org", url: (q) => "https://en.wikipedia.org/wiki/Special:Search?search=" + encodeURIComponent(q) }, - perplexity: { name: "Perplexity", sym: "🧠", fav: "www.perplexity.ai", url: (q) => "https://www.perplexity.ai/search?q=" + encodeURIComponent(q) }, - // AI / LLM answer engines (accept a URL query and answer it directly). - chatgpt: { name: "ChatGPT", sym: "🤖", fav: "chatgpt.com", url: (q) => "https://chatgpt.com/?q=" + encodeURIComponent(q) }, - claude: { name: "Claude", sym: "✳️", fav: "claude.ai", url: (q) => "https://claude.ai/new?q=" + encodeURIComponent(q) }, - phind: { name: "Phind", sym: "🧑‍💻", fav: "www.phind.com", url: (q) => "https://www.phind.com/search?q=" + encodeURIComponent(q) }, - you: { name: "You.com", sym: "🟣", fav: "you.com", url: (q) => "https://you.com/search?q=" + encodeURIComponent(q) + "&tbm=youchat" }, + duckduckgo: { kind: "search", name: "DuckDuckGo", sym: "🦆", fav: "duckduckgo.com", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) }, + google: { kind: "search", name: "Google", sym: "🔵", fav: "www.google.com", url: (q) => "https://www.google.com/search?q=" + encodeURIComponent(q) }, + brave: { kind: "search", name: "Brave", sym: "🦁", fav: "search.brave.com", url: (q) => "https://search.brave.com/search?q=" + encodeURIComponent(q) }, + bing: { kind: "search", name: "Bing", sym: "🔎", fav: "www.bing.com", url: (q) => "https://www.bing.com/search?q=" + encodeURIComponent(q) }, + startpage: { kind: "search", name: "Startpage", sym: "🛡️", fav: "www.startpage.com", url: (q) => "https://www.startpage.com/sp/search?query=" + encodeURIComponent(q) }, + yandex: { kind: "search", name: "Yandex", sym: "🔴", fav: "yandex.com", url: (q) => "https://yandex.com/search/?text=" + encodeURIComponent(q) }, + ecosia: { kind: "search", name: "Ecosia", sym: "🌱", fav: "www.ecosia.org", url: (q) => "https://www.ecosia.org/search?q=" + encodeURIComponent(q) }, + mojeek: { kind: "search", name: "Mojeek", sym: "🧭", fav: "www.mojeek.com", url: (q) => "https://www.mojeek.com/search?q=" + encodeURIComponent(q) }, + searxng: { kind: "search", name: "SearXNG", sym: "🧩", fav: "searx.be", url: (q) => "https://searx.be/search?q=" + encodeURIComponent(q) }, + wikipedia: { kind: "search", name: "Wikipedia", sym: "📖", fav: "en.wikipedia.org", url: (q) => "https://en.wikipedia.org/wiki/Special:Search?search=" + encodeURIComponent(q) }, + // AI / LLM answer engines that ANSWER the URL query without requiring a login. + // ChatGPT / Claude / You.com's youchat all bounce to sign-in before running + // ?q=, so they'd fail silently as a "search engine" — omitted deliberately. + perplexity: { kind: "llm", name: "Perplexity", sym: "🧠", fav: "www.perplexity.ai", url: (q) => "https://www.perplexity.ai/search?q=" + encodeURIComponent(q) }, + phind: { kind: "llm", name: "Phind", sym: "🧑‍💻", fav: "www.phind.com", url: (q) => "https://www.phind.com/search?q=" + encodeURIComponent(q) }, }; // Engines enabled by default (shown in the toolbar dropdown). The rest are in the // catalog and can be turned on from Settings. Custom + detected engines are always on. @@ -55,9 +57,9 @@ function isEnabled(id) { return (settings.enabledEngines || DEFAULT_ENABLED).inc // enabledEngines; custom engines are always enabled. function allEngines() { const list = Object.entries(SEARCH_ENGINES).map(([id, e]) => - ({ id, name: e.name, sym: e.sym, favicon: faviconUrl(e.fav), builtin: true, enabled: isEnabled(id) })); + ({ id, name: e.name, sym: e.sym, favicon: faviconUrl(e.fav), kind: e.kind || "search", builtin: true, enabled: isEnabled(id) })); for (const c of settings.customEngines || []) - list.push({ id: c.id, name: c.name, sym: c.sym || "🔍", favicon: customFavicon(c.url), builtin: false, enabled: true }); + list.push({ id: c.id, name: c.name, sym: c.sym || "🔍", favicon: customFavicon(c.url), kind: c.kind || "search", builtin: false, enabled: true }); // Apply the user's custom order; ids not in engineOrder keep their natural order (stable sort). const order = settings.engineOrder || []; return list.slice().sort((a, b) => { @@ -518,7 +520,7 @@ async function ensureIndex(force = false) { if (!force && sharedIndex && Date.now() - indexBuiltAt < INDEX_TTL) return sharedIndex; if (indexBuilding) return indexBuilding; // dedupe concurrent builds indexBuilding = buildIndex({ WebSocket: currentWS(), directIP: true, electrum: electrumPool }) - .then((idx) => { sharedIndex = idx; indexBuiltAt = Date.now(); return idx; }) + .then((idx) => { sharedIndex = idx; indexBuiltAt = Date.now(); refreshBcnrTlds(idx); return idx; }) .finally(() => { indexBuilding = null; }); // If we have a stale index, don't block on the rebuild — serve stale, refresh async. return (sharedIndex && !force) ? sharedIndex : indexBuilding; @@ -584,6 +586,13 @@ const POP_W = 360; let popH = 210; // popH is updated to fit the popover's conte // engine favicons, like Firefox — a native Just this once + + + + diff --git a/preload.js b/preload.js index 636cddc..5a9d413 100644 --- a/preload.js +++ b/preload.js @@ -29,4 +29,12 @@ contextBridge.exposeInMainWorld("theseus", { onTor: (cb) => ipcRenderer.on("tor", (_e, d) => cb(d)), onTabs: (cb) => ipcRenderer.on("tabs", (_e, d) => cb(d)), onBcnrOffer: (cb) => ipcRenderer.on("bcnr-offer", (_e, d) => cb(d)), + // Collision-mode (BCNR ↔ ICANN) live switcher for the active tab + collisionSwitch: (arg) => ipcRenderer.invoke("collision-switch", arg), + collisionState: () => ipcRenderer.invoke("collision-state"), + // Downloads — the toolbar button subscribes to `downloads` to update its + // badge, and toggleDownloads opens/closes the floating panel. + getDownloads: () => ipcRenderer.invoke("downloads-get"), + toggleDownloads: (rect) => ipcRenderer.invoke("toggle-downloads", rect), + onDownloads: (cb) => ipcRenderer.on("downloads", (_e, d) => cb(d)), }); diff --git a/settings-preload.js b/settings-preload.js index 162a7e6..3f361db 100644 --- a/settings-preload.js +++ b/settings-preload.js @@ -7,4 +7,8 @@ contextBridge.exposeInMainWorld("cfg", { removeEngine: (id) => ipcRenderer.invoke("remove-engine", id), setEngineEnabled: (id, on) => ipcRenderer.invoke("set-engine-enabled", id, on), setEngineOrder: (ids) => ipcRenderer.invoke("set-engine-order", ids), + // Collision-mode: BCNR/ICANN policy + per-name/per-TLD overrides + collisionState: () => ipcRenderer.invoke("collision-state"), + setCollisionPolicy: (p) => ipcRenderer.invoke("collision-set-policy", p), + resetCollisions: () => ipcRenderer.invoke("collision-reset"), }); diff --git a/settings.html b/settings.html index 1a60e4e..923d526 100644 --- a/settings.html +++ b/settings.html @@ -2,7 +2,7 @@ Theseus — Settings