diff --git a/main.js b/main.js
index 8426a2e..fbfb09d 100644
--- a/main.js
+++ b/main.js
@@ -532,34 +532,8 @@ async function serveBns(request) {
const host = url.hostname.toLowerCase();
const reqPath = decodeURIComponent(url.pathname) || "/";
- // Special host: apply a soft-mode "Open with…" choice submitted by
- // collision.html, then redirect the tab to the actual target. Persistent
- // memory (byName/byTld) is written HERE; the one-shot BCDN choice rides
- // along via a ?_collision=bcnr param that loadBns consumes.
- if (host === "collision-choose") {
- const p = url.searchParams;
- const target = String(p.get("host") || "").toLowerCase();
- const cTld = String(p.get("tld") || "").toLowerCase();
- const cChoice = p.get("choice");
- const cRem = p.get("remember") || "no";
- const rest = p.get("resturl") || "/";
- if (cChoice === "bcnr" || cChoice === "icann") {
- rememberCollision(target, cTld, cChoice, cRem);
- }
- let dest;
- if (cChoice === "icann") {
- dest = "https://" + target + rest;
- } else {
- // BCNR/BCDN: bounce back through the bns:// resolver with a one-shot
- // marker so loadBns won't re-prompt for this specific load.
- const sep = rest.includes("?") ? "&" : "?";
- dest = "bns://" + target + rest + sep + "_collision=bcnr";
- }
- const html = `
Opening…` +
- `` +
- `Opening ${target}…`;
- return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
- }
+ // (bns://collision-choose/ is handled by the will-navigate listener attached
+ // to each tab — it fires BEFORE the request reaches this protocol handler.)
let rec = entries.get(host);
if (!rec) { try { await resolveHost(host); } catch {} rec = entries.get(host); }
@@ -778,7 +752,30 @@ function createTab(initial, opts = {}) {
wc.on("did-stop-loading", () => setLoading(tab, false));
wc.on("will-navigate", (e, u) => {
try {
+ // Skip our own programmatic loads. fallbackToWeb calls loadURL("https:///")
+ // and that host is often a BCNR-registered dotted name — without this guard,
+ // isBnsHost() would send us right back into navigateTab, canceling the
+ // fallback (blank-page bug 2026-08-02).
+ if (tab.internalNav) return;
const parsed = new URL(u);
+ // Intercept the collision-choose posted by the in-tab "Open with…" page,
+ // apply the remember flag, set a one-shot transient override so loadBns
+ // doesn't re-prompt, and route via navigateTab so chrome/prov stay in sync.
+ if (parsed.protocol === "bns:" && parsed.hostname === "collision-choose") {
+ e.preventDefault();
+ const p = parsed.searchParams;
+ const target = String(p.get("host") || "").toLowerCase();
+ const cTld = String(p.get("tld") || "").toLowerCase();
+ const cChoice = p.get("choice");
+ const cRem = p.get("remember") || "no";
+ const rest = p.get("resturl") || "/";
+ if (!target) return;
+ if (cChoice === "bcnr" || cChoice === "icann") {
+ rememberCollision(target, cTld, cChoice, cRem);
+ tab.collisionOverride = cChoice; // one-shot, consumed by loadBns
+ }
+ return navigateTab(id, target + rest);
+ }
if (parsed.protocol === "bns:") return;
if (isBnsHost(parsed.hostname)) { e.preventDefault(); navigateTab(id, parsed.hostname + parsed.pathname); }
} catch {}
@@ -901,7 +898,10 @@ async function loadBns(t, id, host, rest, tld) {
if (id === activeId) pushNav({ host, kind: "resolving", tld, registry });
const fallbackToWeb = async (reason) => {
t.url = "https://" + host + (rest === "/" ? "" : rest);
- try { await t.view.webContents.loadURL(t.url); } catch {}
+ t.internalNav = true;
+ try { await t.view.webContents.loadURL(t.url); }
+ catch (e) { console.warn("fallback loadURL failed:", e?.message); }
+ finally { t.internalNav = false; }
t.prov = { host, kind: "web", note: `fallback: ${reason}`, tld, registry };
if (id === activeId) pushNav(t.prov);
emitTabs();