2026-07-29 13:54:34 +02:00
<!doctype html>
< html lang = "en" >
< head > < meta charset = "utf-8" > < title > Theseus — Settings< / title >
< style >
2026-08-02 11:39:00 +02:00
:root{ --bg:#0b0e14; --panel:#141a24; --line:rgba(255,255,255,.09);
2026-07-29 13:54:34 +02:00
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d; }
*{box-sizing:border-box}
2026-07-30 19:29:32 +02:00
body{margin:0;height:100vh;background:var(--bg);color:var(--ink);font:15px/1.6 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
.app{display:flex;height:100vh}
/* left sidebar menu */
.side{flex:none;width:220px;background:#0e131c;border-right:1px solid var(--line);padding:20px 12px;display:flex;flex-direction:column;gap:2px}
.brand{font-weight:700;color:var(--acid);font-size:15px;padding:4px 12px 16px}
.side a{display:block;padding:9px 12px;border-radius:8px;color:var(--mut);text-decoration:none;font-size:14px;cursor:pointer}
.side a:hover{background:#ffffff0a;color:var(--ink)}
.side a.active{background:rgba(214,255,61,.12);color:var(--acid)}
/* content */
.content{flex:1;overflow-y:auto;padding:2.4rem 2.4rem 4rem}
section{max-width:640px}
h1{font-size:1.4rem;margin:0 0 .2rem}
.lede{color:var(--mut);margin:0 0 1.8rem;font-size:13.5px}
2026-07-30 23:11:28 +02:00
h2.sub{font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin:1.8rem 0 .2rem;border-top:1px solid var(--line);padding-top:1.4rem}
.subd{color:var(--mut);margin:0 0 1rem;font-size:13px}
2026-07-29 13:54:34 +02:00
.row{display:flex;align-items:center;gap:16px;background:var(--panel);border:1px solid var(--line);
2026-07-30 19:29:32 +02:00
border-radius:12px;padding:14px 18px;margin-bottom:10px}
2026-07-29 13:54:34 +02:00
.row .txt{flex:1}
2026-07-30 19:29:32 +02:00
.row .t{font-weight:600}
2026-07-29 13:54:34 +02:00
.row .d{color:var(--mut);font-size:13px;margin-top:2px}
2026-08-02 11:39:00 +02:00
/* control column — mode select + optional value field on the same row so the
dropdown menus don't get cut off underneath, wraps only when narrow */
.ctl{display:flex;flex-direction:row;flex-wrap:wrap;gap:6px;align-items:center;justify-content:flex-end;flex:none;max-width:60%}
2026-07-30 19:29:32 +02:00
.ctl .coords{display:flex;gap:6px}
2026-08-02 11:39:00 +02:00
select,.ctl input{background:#1b2330;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:13px;outline:none;min-width:140px}
2026-07-30 19:29:32 +02:00
select:focus,.ctl input:focus{border-color:#4b7bec}
2026-08-02 11:39:00 +02:00
.ctl input{width:170px} .ctl .coords input{width:92px;min-width:auto}
/* Chromium's native < select > popup uses the page's color-scheme; when it's
"light dark" (both accepted) Chromium picks by the OS, so a dark-theme app
on a light OS shows a light popup. main.js's applyTheme() maps to
nativeTheme.themeSource, which drives prefers-color-scheme — so pinning
color-scheme via that media query keeps the popup in sync automatically. */
:root { color-scheme: dark; }
@media (prefers-color-scheme: light) { :root { color-scheme: light; } }
/* Explicit option styling — Chromium respects it in the popup on Windows. */
select option { background: #1b2330; color: var(--ink); }
@media (prefers-color-scheme: light) { select option { background: #f1f3f7; color: #1a1f28; } }
2026-07-30 20:58:45 +02:00
code{font-family:ui-monospace,monospace;font-size:12px;background:#0e131b;border:1px solid var(--line);border-radius:5px;padding:1px 5px;color:#bfeae4}
.addeng{display:flex;gap:6px} .addeng input{flex:1;background:#1b2330;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;outline:none}
.addeng input:focus{border-color:#4b7bec}
.btn{border:1px solid #d6ff3d55;background:rgba(214,255,61,.12);color:#eaffb0;border-radius:8px;padding:8px 14px;font-size:13px;cursor:pointer}
.btn:hover{background:rgba(214,255,61,.22)}
2026-08-02 11:39:00 +02:00
/* segmented control — small toggle used elsewhere (kept for reuse) */
.segseg{display:inline-flex;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:2px;gap:2px}
.segseg .seg{background:transparent;color:var(--mut);border:none;border-radius:6px;padding:6px 12px;font-size:13px;cursor:pointer;font:inherit;line-height:1.2}
.segseg .seg:hover{color:var(--ink)}
.segseg .seg.on{background:#1c2432;color:var(--ink);box-shadow:inset 0 0 0 1px var(--line)}
/* theme cards — three visual previews (System / Light / Dark) */
.themeCards{display:flex;gap:14px;margin:6px 0 4px;flex-wrap:wrap}
.themeCards .tc{background:transparent;border:2px solid var(--line);border-radius:10px;padding:10px;
display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;color:var(--ink);
font:inherit;font-size:13px;min-width:150px;transition:border-color .12s}
.themeCards .tc:hover{border-color:#4b7bec55}
.themeCards .tc.on{border-color:#4b7bec;box-shadow:0 0 0 1px #4b7bec inset}
.themeCards .mock{width:130px;height:82px;border-radius:6px;overflow:hidden;display:block;position:relative;
border:1px solid rgba(255,255,255,.08)}
.themeCards .mock .mm-chrome{position:absolute;left:0;right:0;top:0;height:22px;display:block}
.themeCards .mock .mm-chrome::before{content:"";position:absolute;left:8px;top:6px;width:8px;height:8px;border-radius:50%;background:#f6768a}
.themeCards .mock .mm-chrome::after {content:"";position:absolute;left:22px;top:6px;width:8px;height:8px;border-radius:50%;background:#f6c15c;box-shadow:14px 0 0 #6ec27d}
.themeCards .mock .mm-body{position:absolute;left:0;right:0;top:22px;bottom:0;display:block}
.themeCards .mock-light .mm-chrome{background:#e9ecf2}
.themeCards .mock-light .mm-body {background:#ffffff;background-image:linear-gradient(#0000000c 1px,transparent 1px);background-size:100% 12px;background-position:0 10px}
.themeCards .mock-dark .mm-chrome{background:#141b28}
.themeCards .mock-dark .mm-body {background:#0b0e14;background-image:linear-gradient(#ffffff10 1px,transparent 1px);background-size:100% 12px;background-position:0 10px}
.themeCards .mock-system .mm-chrome{background:linear-gradient(90deg,#141b28 0 50%,#e9ecf2 50% 100%)}
.themeCards .mock-system .mm-body{background:linear-gradient(90deg,#0b0e14 0 50%,#ffffff 50% 100%)}
.themeCards .tc-label{font-weight:500;color:var(--ink)}
.polrow{display:flex;align-items:center;gap:10px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:8px 12px;font-size:13px;cursor:pointer}
.polrow:hover{background:#141c28}
.polrow input{accent-color:#d6ff3d}
.pmuted{color:var(--mut)}
2026-07-30 20:58:45 +02:00
.ceng{display:flex;align-items:center;gap:8px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:13px}
2026-07-30 22:09:26 +02:00
.ceng .cs{font-size:14px;flex:none}
2026-07-30 22:55:52 +02:00
/* engine checklist */
.eng{display:flex;align-items:center;gap:10px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:13px}
.eng .eic{width:18px;height:18px;flex:none;display:grid;place-items:center}
.eng .eic .ei{width:16px;height:16px;border-radius:3px} .eng .eic .es{font-size:14px}
.eng .enm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.eng .cx{cursor:pointer;color:var(--dim);border:none;background:transparent;font-size:13px} .eng .cx:hover{color:#f6768a}
2026-07-31 21:53:01 +02:00
.eng{cursor:default}
.eng .grip{flex:none;color:var(--dim);cursor:grab;font-size:14px;line-height:1;padding:0 2px;user-select:none}
.eng .grip:active{cursor:grabbing}
.eng.dragging{opacity:.45}
.eng.over{border-color:var(--acid);box-shadow:0 -2px 0 var(--acid) inset}
2026-08-06 01:41:31 +02:00
.eng.off{opacity:.55}
.eng.off .enm{color:var(--mut)}
2026-08-02 11:39:00 +02:00
.ehdr{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin:14px 0 6px;padding-top:2px}
.ehdr:first-child{margin-top:0}
2026-08-06 01:41:31 +02:00
/* right-click context menu for an engine row */
.ctxmenu{position:fixed;z-index:9999;background:#1c222c;border:1px solid var(--line);border-radius:8px;
box-shadow:0 12px 34px #000c;padding:4px;min-width:180px;font-size:13px;color:var(--ink)}
.ctxmenu .mi{padding:7px 12px;border-radius:5px;cursor:pointer;white-space:nowrap}
.ctxmenu .mi:hover{background:#ffffff10}
.ctxmenu .mi.danger{color:#f6768a}
.ctxmenu .mi.danger:hover{background:rgba(246,118,138,.12)}
@media (prefers-color-scheme: light){
.ctxmenu{background:#ffffff;border-color:rgba(0,0,0,.15)}
.ctxmenu .mi:hover{background:rgba(0,0,0,.05)}
}
2026-08-02 11:39:00 +02:00
/* engine catalog panel — appears under the enabled list when "+ Add" is clicked */
.engcat{margin-top:6px;padding:12px 12px 10px;background:#0e131c;border:1px solid var(--line);border-radius:10px}
.engcat .cat{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;font-size:13px}
.engcat .cat:hover{background:#141b26}
.engcat .cat .eic{width:18px;height:18px;flex:none;display:grid;place-items:center}
.engcat .cat .eic .ei{width:16px;height:16px;border-radius:3px} .engcat .cat .eic .es{font-size:14px}
.engcat .cat .enm{flex:1}
.engcat .cat .kind{font-size:10.5px;letter-spacing:.05em;color:var(--dim);padding:1px 6px;border:1px solid var(--line);border-radius:999px}
.engcat .cat .add{background:transparent;border:1px solid var(--line);color:var(--ink);border-radius:6px;padding:4px 10px;font-size:12px;cursor:pointer}
.engcat .cat .add:hover{background:rgba(214,255,61,.14);border-color:#d6ff3d55;color:#eaffb0}
.engcat .cempty2{color:var(--dim);font-size:12.5px;padding:6px 8px}
2026-07-30 22:55:52 +02:00
.sw.sm{width:38px;height:22px} .sw.sm .knob{width:15px;height:15px} .sw.sm input:checked + .track .knob{transform:translateX(16px)}
2026-07-30 20:58:45 +02:00
.ceng .cn{font-weight:600} .ceng .cu{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
.ceng .cx{cursor:pointer;color:#8b98a9;border:none;background:transparent;font-size:13px} .ceng .cx:hover{color:#f6768a}
.cempty{color:var(--dim);font-size:12.5px}
2026-07-29 13:54:34 +02:00
/* toggle */
.sw{position:relative;width:46px;height:26px;flex:none;cursor:pointer}
.sw input{opacity:0;width:0;height:0}
.track{position:absolute;inset:0;background:#2b3444;border:1px solid var(--line);border-radius:999px;transition:.15s}
.knob{position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#8b98a9;transition:.15s}
.sw input:checked + .track{background:rgba(214,255,61,.25);border-color:#d6ff3d55}
.sw input:checked + .track .knob{transform:translateX(20px);background:var(--acid)}
.note{color:var(--dim);font-size:12.5px;margin-top:1.4rem;border-top:1px solid var(--line);padding-top:1rem}
2026-07-30 22:00:01 +02:00
/* light theme (placed last so these win over the dark base rules) */
@media (prefers-color-scheme: light){
:root{ --bg:#eef0f4; --panel:#ffffff; --line:rgba(0,0,0,.11); --ink:#1a1f28; --mut:#4a5262; --dim:#7b8494; }
.side{ background:#f6f8fb; }
.side a:hover{ background:rgba(0,0,0,.05); }
select, .ctl input, .addeng input{ background:#f1f3f7; }
.track{ background:#c8cfdb; }
.knob{ background:#8a93a2; }
2026-07-30 22:55:52 +02:00
code, .ceng, .eng{ background:#eef1f6; }
2026-07-30 22:00:01 +02:00
}
2026-07-29 13:54:34 +02:00
< / style > < / head >
< body >
2026-07-30 19:29:32 +02:00
< div class = "app" >
< nav class = "side" >
< div class = "brand" > ⛓ Theseus< / div >
< a data-sec = "general" class = "active" > General< / a >
2026-08-02 11:39:00 +02:00
< a data-sec = "search" > Search< / a >
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
< a data-sec = "passwords" > Passwords< / a >
2026-08-02 13:02:47 +02:00
< a data-sec = "naming" > Registries< / a >
2026-07-30 19:29:32 +02:00
< a data-sec = "performance" > Performance< / a >
< a data-sec = "privacy" > Privacy< / a >
2026-08-31 16:00:34 +02:00
< a data-sec = "addons" > Extensions< / a >
2026-07-30 19:29:32 +02:00
< / nav >
< div class = "content" >
<!-- GENERAL -->
< section id = "general" >
< h1 > General< / h1 >
< p class = "lede" > Changes apply immediately and are saved for next time.< / p >
2026-08-02 11:39:00 +02:00
< h2 class = "sub" style = "border-top:0;padding-top:0;margin-top:0" > Startup< / h2 >
2026-07-30 22:00:01 +02:00
< div class = "row" >
2026-08-02 11:39:00 +02:00
< div class = "txt" > < div class = "t" > Open previous windows and tabs< / div > < div class = "d" > Restore the tabs from your last session when Theseus starts.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "restoreSession" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< h2 class = "sub" > Appearance< / h2 >
< p class = "subd" > Choose a theme for the browser. < b > System< / b > follows your operating system's light/dark setting.< / p >
< div class = "themeCards" id = "theme" role = "radiogroup" aria-label = "Theme" >
< button type = "button" class = "tc" data-val = "system" role = "radio" aria-checked = "false" >
< span class = "mock mock-system" > < span class = "mm-chrome" > < / span > < span class = "mm-body" > < / span > < / span >
< span class = "tc-label" > System< / span >
< / button >
< button type = "button" class = "tc" data-val = "light" role = "radio" aria-checked = "false" >
< span class = "mock mock-light" > < span class = "mm-chrome" > < / span > < span class = "mm-body" > < / span > < / span >
< span class = "tc-label" > Light< / span >
< / button >
< button type = "button" class = "tc" data-val = "dark" role = "radio" aria-checked = "false" >
< span class = "mock mock-dark" > < span class = "mm-chrome" > < / span > < span class = "mm-body" > < / span > < / span >
< span class = "tc-label" > Dark< / span >
< / button >
2026-07-30 22:00:01 +02:00
< / div >
2026-09-06 17:14:57 +02:00
< h2 class = "sub" style = "margin-top:1.8rem" > Toolbar< / h2 >
< p class = "subd" > Shrink the address bar and search box to make more room for extension icons.< / p >
< div class = "row" >
< div class = "txt" > < div class = "t" > Address bar size< / div > < div class = "d" > Wide fills the row; compact leaves space for the extension dock.< / div > < / div >
< div class = "ctl" >
< select id = "urlBarSize" >
< option value = "wide" > Wide (default)< / option >
< option value = "medium" > Medium< / option >
< option value = "compact" > Compact< / option >
< / select >
< / div >
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Search box< / div > < div class = "d" > Right-side quick-search that uses the engine you pick above.< / div > < / div >
< div class = "ctl" >
< select id = "searchBoxSize" >
< option value = "wide" > Wide (400 px)< / option >
< option value = "normal" > Normal (300 px)< / option >
< option value = "compact" > Compact (180 px)< / option >
< option value = "hidden" > Hidden< / option >
< / select >
< / div >
< / div >
2026-08-02 11:39:00 +02:00
< / section >
<!-- SEARCH -->
< section id = "search" hidden >
< h1 > Search< / h1 >
< p class = "lede" > Pick what your address bar and the toolbar dropdown search with.< / p >
2026-07-30 19:29:32 +02:00
< div class = "row" >
2026-08-02 11:39:00 +02:00
< div class = "txt" > < div class = "t" > Default search engine< / div > < div class = "d" > Used when you type into the address bar.< / div > < / div >
2026-07-30 19:29:32 +02:00
< div class = "ctl" > < select id = "searchEngine" > < / select > < / div >
2026-07-29 13:54:34 +02:00
< / div >
2026-07-30 22:55:52 +02:00
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:10px" >
2026-08-02 11:39:00 +02:00
< div class = "txt" style = "display:flex;align-items:center;justify-content:space-between;gap:12px" >
< div >
< div class = "t" > Additional search engines< / div >
< div class = "d" > These appear in the toolbar dropdown. Drag to reorder. Toggle off to move an engine back to the catalog.< / div >
< / div >
< button id = "engAddBtn" class = "btn" type = "button" > + Add search engine< / button >
< / div >
2026-07-30 22:55:52 +02:00
< div id = "engineList" > < / div >
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
<!-- Catalog: hidden until "Add" is clicked. Three tiers, top to bottom:
1. curated built-ins the user hasn't enabled (tier="catalog")
2. wider bank filtered by a search box (tier="extra")
3. custom-URL form -->
2026-08-02 11:39:00 +02:00
< div id = "engineCatalog" class = "engcat" hidden >
< div class = "ehdr" > Add from catalog< / div >
< div id = "catalogList" > < / div >
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
< div class = "ehdr" style = "display:flex;align-items:center;justify-content:space-between;gap:8px" >
< span > Discover more engines< / span >
< input id = "engineFilter" type = "search" placeholder = "Filter by name…" autocomplete = "off"
style="background:#1b2330;color:var(--ink);border:1px solid var(--line);border-radius:6px;padding:4px 8px;font-size:12px;min-width:auto;width:170px">
< / div >
< div id = "extraList" > < / div >
2026-08-02 11:39:00 +02:00
< div class = "ehdr" > Or add a custom URL< / div >
< div class = "addeng" >
< input id = "engSym" placeholder = "🔍" style = "max-width:52px;text-align:center;flex:none" >
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
< input id = "engName" placeholder = "Name (e.g. My SearXNG)" >
2026-08-02 11:39:00 +02:00
< input id = "engUrl" placeholder = "https://example.com/search?q=%s" >
< button id = "engAdd" class = "btn" > Add< / button >
< / div >
2026-07-30 20:58:45 +02:00
< / div >
< / div >
2026-08-02 11:39:00 +02:00
< / section >
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
<!-- PASSWORDS -->
< section id = "passwords" hidden >
< h1 > Passwords< / h1 >
< p class = "lede" > Local password vault. Set once, unlocked with a master password. Encrypted at rest; nothing leaves your machine.< / p >
<!-- State A: no vault yet — set up -->
< div id = "pwSetup" hidden >
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:10px" >
< div class = "txt" > < div class = "t" > Master password< / div >
< div class = "d" > Used to unlock the vault every session. This is separate from your Ariadne wallet passphrase — memorize it, we can't recover it.< / div > < / div >
< div class = "addeng" > < input id = "pwSetupPw1" type = "password" placeholder = "Master password" > < input id = "pwSetupPw2" type = "password" placeholder = "Confirm" > < / div >
< / div >
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:10px" >
< div class = "txt" > < div class = "t" > Seed for deterministic passwords< / div >
< div class = "d" > The "Generate" button in an entry derives a password from this seed. Same seed on another device → same passwords for the same site + username.< / div > < / div >
< div style = "display:flex;flex-direction:column;gap:6px" >
< label class = "polrow" > < input type = "radio" name = "pwSeedSource" value = "mnemonic" checked > < span > < b > Use my Ariadne wallet mnemonic< / b > < span class = "pmuted" > — unified identity, one seed to back up< / span > < / span > < / label >
< label class = "polrow" > < input type = "radio" name = "pwSeedSource" value = "generate" > < span > < b > Generate a new independent seed< / b > < span class = "pmuted" > — isolated from any BCH funds< / span > < / span > < / label >
< / div >
< textarea id = "pwSetupMnemonic" placeholder = "12 or 24 BIP39 words separated by spaces" rows = "3" style = "background:#1b2330;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;font-family:ui-monospace,monospace;outline:none;resize:vertical" > < / textarea >
< div class = "pmuted" style = "font-size:12px" > The mnemonic is used only to derive the password-purpose subtree (m/1381'/0'). It is not stored — only the derived subtree key is persisted, encrypted with your master password.< / div >
< / div >
< div class = "row" style = "justify-content:flex-end" >
< button id = "pwSetupBtn" class = "btn" type = "button" > Create vault< / button >
< / div >
< / div >
<!-- State B: vault exists but locked -->
< div id = "pwLocked" hidden >
< div class = "row" >
< div class = "txt" > < div class = "t" > Unlock vault< / div > < div class = "d" > Enter your master password to view or add entries.< / div > < / div >
< div class = "ctl" style = "align-items:stretch" > < input id = "pwUnlockPw" type = "password" placeholder = "Master password" > < button id = "pwUnlockBtn" class = "btn" type = "button" > Unlock< / button > < / div >
< / div >
< div id = "pwUnlockErr" class = "pmuted" style = "color:#f6768a;font-size:12.5px;margin-top:4px" hidden > < / div >
< / div >
<!-- State C: vault unlocked -->
< div id = "pwUnlocked" hidden >
< div class = "row" style = "justify-content:space-between" >
< div class = "txt" > < div class = "t" > Your passwords< / div > < div class = "d" > Reveal, copy, or edit any entry. The vault re-locks when Theseus quits.< / div > < / div >
< button id = "pwLockBtn" class = "btn" type = "button" > Lock now< / button >
< / div >
< div id = "pwList" > < / div >
< h2 class = "sub" > Add an entry< / h2 >
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:8px" >
< div class = "addeng" > < input id = "pwAddDomain" placeholder = "Site (e.g. github.com)" > < input id = "pwAddUser" placeholder = "Username or email" > < / div >
< div style = "display:flex;flex-direction:column;gap:6px" >
< label class = "polrow" > < input type = "radio" name = "pwAddKind" value = "generated" checked > < span > < b > Generate deterministically< / b > < span class = "pmuted" > — derived from your seed; same across devices< / span > < / span > < / label >
< label class = "polrow" > < input type = "radio" name = "pwAddKind" value = "literal" > < span > < b > Paste an existing password< / b > < span class = "pmuted" > — for legacy accounts you already set elsewhere< / span > < / span > < / label >
< / div >
< input id = "pwAddLiteral" type = "password" placeholder = "Paste password" hidden >
< div class = "addeng" > < button id = "pwAddPreview" class = "btn" type = "button" style = "flex:none" > Preview< / button > < input id = "pwAddPreviewOut" readonly placeholder = "preview appears here" style = "font-family:ui-monospace,monospace" > < / div >
< div style = "display:flex;justify-content:flex-end" > < button id = "pwAddBtn" class = "btn" type = "button" > Save entry< / button > < / div >
< / div >
< div class = "note" > There is no autofill yet (phase 2). Copy the password from an entry and paste it into the site.< / div >
< / div >
< / section >
2026-08-02 11:39:00 +02:00
<!-- NAMING -->
< section id = "naming" hidden >
2026-08-02 13:02:47 +02:00
< h1 > Registries< / h1 >
2026-08-02 14:43:31 +02:00
< p class = "lede" > How Theseus picks between the < b > BCNR / BCDN< / b > and < b > ICANN / IANA< / b > , when a name exists in both.< / p >
2026-08-02 11:39:00 +02:00
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:10px" >
< div class = "txt" > < div class = "t" > Collision policy< / div >
2026-08-02 14:43:31 +02:00
< div class = "d" > A name only exists in both registries when its TLD isn't BCNR-unique (e.g. < code > .de< / code > ). BCNR-unique TLDs (that only exist on BCNR) never conflict.< / div > < / div >
2026-08-02 11:39:00 +02:00
< div id = "policyList" style = "display:flex;flex-direction:column;gap:6px" >
2026-08-02 13:02:47 +02:00
< label class = "polrow" > < input type = "radio" name = "collisionPolicy" value = "bcnr-first" > < span > < b > BCDN first< / b > < span class = "pmuted" > — BCDN wins conflicts; falls back to ICANN for anything BCDN doesn't have.< / span > < / span > < / label >
< label class = "polrow" > < input type = "radio" name = "collisionPolicy" value = "icann-first" > < span > < b > ICANN first< / b > < span class = "pmuted" > — ICANN wins conflicts; BCDN fills gaps for non-ICANN TLDs.< / span > < / span > < / label >
< label class = "polrow" > < input type = "radio" name = "collisionPolicy" value = "soft" > < span > < b > Ask each time< / b > < span class = "pmuted" > — an "Open with…" prompt on conflict, remembered per name or per TLD.< / span > < / span > < / label >
2026-08-02 11:39:00 +02:00
< / div >
< / div >
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:8px" >
< div class = "txt" > < div class = "t" > Remembered choices< / div >
< div class = "d" > "Always use …" picks you made from the switcher or the prompt. Reset them to be asked again.< / div > < / div >
< div id = "colSummary" class = "pmuted" style = "font-size:12.5px" > < / div >
< div > < button id = "resetCollisions" class = "btn" > Reset remembered choices< / button > < / div >
2026-07-29 13:54:34 +02:00
< / div >
2026-07-30 19:29:32 +02:00
< / section >
<!-- PERFORMANCE -->
< section id = "performance" hidden >
< h1 > Performance< / h1 >
< p class = "lede" > Keep Theseus light on resources.< / p >
< div class = "row" >
< div class = "txt" > < div class = "t" > Throttle inactive tabs< / div > < div class = "d" > Background and inactive tabs use far less CPU. Recommended.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "backgroundThrottle" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< / section >
<!-- PRIVACY -->
< section id = "privacy" hidden >
< h1 > Privacy< / h1 >
< p class = "lede" > Reduce tracking and lock down device access.< / p >
< div class = "row" >
2026-07-30 22:00:01 +02:00
< div class = "txt" > < div class = "t" > WebRTC IP policy< / div >
< div class = "d" > Controls which IP addresses WebRTC may reveal — the same thing the "WebRTC Network Limiter" extension does, built in. < b > Public interface only< / b > hides your local IP; < b > Disable non-proxied UDP< / b > is strongest. Tor forces the strongest automatically.< / div > < / div >
< div class = "ctl" > < select id = "webrtcMode" >
< option value = "default" > Allow all (default)< / option >
< option value = "public_only" > Public interface only< / option >
< option value = "public_private" > Public + private interfaces< / option >
< option value = "disable_udp" > Disable non-proxied UDP< / option >
< / select > < / div >
2026-07-30 19:29:32 +02:00
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Block camera< / div > < div class = "d" > Deny camera by default — also hides its name from fingerprinting.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "blockCamera" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Block microphone< / div > < div class = "d" > Deny microphone by default — also hides its name from fingerprinting.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "blockMicrophone" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
2026-07-30 23:11:28 +02:00
< div class = "row" >
< div class = "txt" > < div class = "t" > Hide media devices< / div > < div class = "d" > Blank the labels and IDs of all cameras, microphones < b > and speakers< / b > from < code > enumerateDevices()< / code > — closes a WebRTC fingerprinting leak, like Firefox.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "hideMediaDevices" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< h2 class = "sub" > Anti-fingerprinting< / h2 >
< p class = "subd" > For each: < b > Show< / b > (real), < b > Hide< / b > (neutral value), < b > Spoof< / b > (a decoy), or < b > Manual< / b > (set your own).< / p >
2026-07-30 19:29:32 +02:00
< div class = "row" >
< div class = "txt" > < div class = "t" > Timezone< / div > < div class = "d" > What sites read via JavaScript (Intl / Date).< / div > < / div >
< div class = "ctl" >
< select id = "timezoneMode" > < option value = "show" > Show real< / option > < option value = "hide" > Hide (UTC)< / option > < option value = "spoof" > Spoof (auto)< / option > < option value = "manual" > Manual…< / option > < / select >
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
<!-- Native <select> instead of an <input list=""> datalist — the
datalist popup was flaky in Electron and never rendered on some
displays; a real select is unambiguous. -->
< select id = "timezoneValue" hidden >
< option value = "UTC" > UTC< / option >
< option value = "Europe/London" > Europe/London< / option >
< option value = "Europe/Berlin" > Europe/Berlin< / option >
< option value = "Europe/Paris" > Europe/Paris< / option >
< option value = "Europe/Madrid" > Europe/Madrid< / option >
< option value = "Europe/Rome" > Europe/Rome< / option >
< option value = "Europe/Moscow" > Europe/Moscow< / option >
< option value = "America/New_York" > America/New_York< / option >
< option value = "America/Chicago" > America/Chicago< / option >
< option value = "America/Denver" > America/Denver< / option >
< option value = "America/Los_Angeles" > America/Los_Angeles< / option >
< option value = "America/Sao_Paulo" > America/Sao_Paulo< / option >
< option value = "America/Mexico_City" > America/Mexico_City< / option >
< option value = "America/Toronto" > America/Toronto< / option >
< option value = "Asia/Tokyo" > Asia/Tokyo< / option >
< option value = "Asia/Shanghai" > Asia/Shanghai< / option >
< option value = "Asia/Seoul" > Asia/Seoul< / option >
< option value = "Asia/Kolkata" > Asia/Kolkata< / option >
< option value = "Asia/Dubai" > Asia/Dubai< / option >
< option value = "Asia/Singapore" > Asia/Singapore< / option >
< option value = "Asia/Bangkok" > Asia/Bangkok< / option >
< option value = "Australia/Sydney" > Australia/Sydney< / option >
< option value = "Australia/Perth" > Australia/Perth< / option >
< option value = "Africa/Nairobi" > Africa/Nairobi< / option >
< option value = "Africa/Cairo" > Africa/Cairo< / option >
< option value = "Africa/Johannesburg" > Africa/Johannesburg< / option >
< option value = "__other__" > Other…< / option >
< / select >
< input id = "timezoneValueOther" type = "text" placeholder = "IANA zone, e.g. America/Anchorage" hidden >
2026-07-30 19:29:32 +02:00
< / div >
< / div >
< div class = "row" >
2026-07-30 23:11:28 +02:00
< div class = "txt" > < div class = "t" > Language< / div > < div class = "d" > navigator.language and the Accept-Language header. < b > Spoof< / b > reports a major world language; < b > Manual< / b > lets you type any locale.< / div > < / div >
2026-07-30 19:29:32 +02:00
< div class = "ctl" >
2026-07-30 23:11:28 +02:00
< select id = "languageMode" > < option value = "show" > Show real< / option > < option value = "hide" > Hide (en-US)< / option > < option value = "spoof" > Spoof (choose)< / option > < option value = "manual" > Manual…< / option > < / select >
< select id = "languageSpoof" hidden >
< option value = "en-US" > English< / option >
< option value = "zh-CN" > Chinese (中文)< / option >
< option value = "es-ES" > Spanish (Español)< / option >
< option value = "hi-IN" > Hindi (हिन्दी)< / option >
< option value = "ar" > Arabic (العربية)< / option >
< option value = "pt-BR" > Portuguese (Português)< / option >
< option value = "ru-RU" > Russian (Русский)< / option >
< option value = "fr-FR" > French (Français)< / option >
< option value = "de-DE" > German (Deutsch)< / option >
< option value = "ja-JP" > Japanese (日本語)< / option >
< / select >
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
<!-- Native <select> (same reason as timezone above). -->
< select id = "languageValue" hidden >
< option value = "en-US" > English (US) — en-US< / option >
< option value = "en-GB" > English (UK) — en-GB< / option >
< option value = "zh-CN" > Chinese (Simplified) — zh-CN< / option >
< option value = "zh-TW" > Chinese (Traditional) — zh-TW< / option >
< option value = "es-ES" > Spanish (Spain) — es-ES< / option >
< option value = "es-MX" > Spanish (Mexico) — es-MX< / option >
< option value = "pt-BR" > Portuguese (Brazil) — pt-BR< / option >
< option value = "pt-PT" > Portuguese (Portugal) — pt-PT< / option >
< option value = "fr-FR" > French — fr-FR< / option >
< option value = "de-DE" > German — de-DE< / option >
< option value = "it-IT" > Italian — it-IT< / option >
< option value = "nl-NL" > Dutch — nl-NL< / option >
< option value = "ru-RU" > Russian — ru-RU< / option >
< option value = "ja-JP" > Japanese — ja-JP< / option >
< option value = "ko-KR" > Korean — ko-KR< / option >
< option value = "hi-IN" > Hindi — hi-IN< / option >
< option value = "ar" > Arabic — ar< / option >
< option value = "tr-TR" > Turkish — tr-TR< / option >
< option value = "pl-PL" > Polish — pl-PL< / option >
< option value = "uk-UA" > Ukrainian — uk-UA< / option >
< option value = "sv-SE" > Swedish — sv-SE< / option >
< option value = "fi-FI" > Finnish — fi-FI< / option >
< option value = "el-GR" > Greek — el-GR< / option >
< option value = "he-IL" > Hebrew — he-IL< / option >
< option value = "vi-VN" > Vietnamese — vi-VN< / option >
< option value = "th-TH" > Thai — th-TH< / option >
< option value = "id-ID" > Indonesian — id-ID< / option >
< option value = "__other__" > Other…< / option >
< / select >
< input id = "languageValueOther" type = "text" placeholder = "BCP-47 tag, e.g. cs-CZ" hidden >
2026-07-30 19:29:32 +02:00
< / div >
< / div >
< div class = "row" >
2026-07-30 23:11:28 +02:00
< div class = "txt" > < div class = "t" > Location< / div > < div class = "d" > < b > Hide< / b > denies geolocation; < b > Spoof< / b > reports a chosen region; < b > Manual< / b > reports exact coordinates.< / div > < / div >
2026-07-30 19:29:32 +02:00
< div class = "ctl" >
2026-07-30 23:11:28 +02:00
< select id = "locationMode" > < option value = "show" > Show real< / option > < option value = "hide" > Hide (block)< / option > < option value = "spoof" > Spoof (region)< / option > < option value = "manual" > Manual…< / option > < / select >
< select id = "locationRegion" hidden >
< option value = "europe" > Europe< / option >
< option value = "asia" > Asia< / option >
< option value = "north_america" > North America< / option >
< option value = "south_america" > South America< / option >
< option value = "africa" > Africa< / option >
< option value = "middle_east" > Middle East< / option >
< option value = "australia" > Australia< / option >
< / select >
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
<!-- Manual location = pick a city from a native select; its lat/lon
are looked up client-side (see CITIES below) and written to
locationLat / locationLon settings, which drive the
navigator.geolocation override in main.js. The raw lat/lon
inputs used to sit here but nobody types coordinates by hand;
the city picker gives the same override with a single click. -->
< select id = "locationCity" hidden >
< option value = "" > Pick a city…< / option >
< option value = "london" > London< / option >
< option value = "berlin" > Berlin< / option >
< option value = "paris" > Paris< / option >
< option value = "madrid" > Madrid< / option >
< option value = "rome" > Rome< / option >
< option value = "moscow" > Moscow< / option >
< option value = "istanbul" > Istanbul< / option >
< option value = "dubai" > Dubai< / option >
< option value = "mumbai" > Mumbai< / option >
< option value = "singapore" > Singapore< / option >
< option value = "bangkok" > Bangkok< / option >
< option value = "shanghai" > Shanghai< / option >
< option value = "tokyo" > Tokyo< / option >
< option value = "seoul" > Seoul< / option >
< option value = "sydney" > Sydney< / option >
< option value = "new_york" > New York< / option >
< option value = "los_angeles" > Los Angeles< / option >
< option value = "chicago" > Chicago< / option >
< option value = "toronto" > Toronto< / option >
< option value = "mexico_city" > Mexico City< / option >
< option value = "sao_paulo" > São Paulo< / option >
< option value = "buenos_aires" > Buenos Aires< / option >
< option value = "cairo" > Cairo< / option >
< option value = "nairobi" > Nairobi< / option >
< option value = "johannesburg" > Johannesburg< / option >
< option value = "__other__" > Other…< / option >
< / select >
< div id = "locationOther" class = "coords" hidden >
< input id = "locationLatOther" type = "number" step = "0.0001" placeholder = "lat" style = "width:100px" >
< input id = "locationLonOther" type = "number" step = "0.0001" placeholder = "lon" style = "width:100px" >
2026-07-30 19:29:32 +02:00
< / div >
< / div >
< / div >
< div class = "note" > These reduce tracking and hide your IP, but a custom browser can still be fingerprinted. For maximum anonymity, use the Tor Browser.< / div >
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
< h2 class = "sub" > Storage< / h2 >
< p class = "subd" > By default Theseus keeps < b > nothing< / b > across sessions — everything toggled on here is wiped when you quit. Untoggle a bucket to keep it (e.g. cookies to stay signed in on trusted sites).< / p >
< div class = "row" >
< div class = "txt" > < div class = "t" > Clear cookies on quit< / div > < div class = "d" > Drops session + persistent cookies. You'll sign in again next launch.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "clearCookiesOnQuit" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Clear HTTP cache on quit< / div > < div class = "d" > Drops cached images / scripts / stylesheets. Sites re-download; small disk win.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "clearCacheOnQuit" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Clear site storage on quit< / div > < div class = "d" > Drops localStorage, IndexedDB, service workers, and the cache API. Web-app state resets.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "clearStorageOnQuit" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Clear history on quit< / div > < div class = "d" > Drops navigation history + the saved-tabs session file (overrides "Reopen previous tabs").< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "clearHistoryOnQuit" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< div class = "row" style = "justify-content:flex-end" >
< button id = "clearNow" class = "btn" type = "button" > Clear all now< / button >
< / div >
< div class = "note" > There is no persistent password manager — passwords are never stored to disk regardless of these toggles. Use a dedicated password manager (Bitwarden, KeePass, etc.).< / div >
2026-07-30 19:29:32 +02:00
< / section >
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
<!-- ADD - ONS -->
< section id = "addons" hidden >
2026-08-31 16:00:34 +02:00
< h1 > Extensions< / h1 >
< p class = "lede" > Small modules that add capabilities to Theseus. Extensions live as folders under
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
< code style = "background:transparent;border:none;padding:0" id = "addonsPathHint" > %APPDATA%\Theseus Navigator\addons\< / code > . Drop a folder in, restart, it's live.
2026-08-31 16:00:34 +02:00
Bundled reference extensions (like the Notepad) are copied there on first run — you can edit or remove them
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
without losing anything the browser needs.< / p >
< div class = "row" style = "justify-content:flex-end;gap:8px" >
< button id = "addonsReload" class = "btn" type = "button" > Reload< / button >
2026-08-31 16:00:34 +02:00
< button id = "addonsOpenDir" class = "btn" type = "button" > Open extensions folder< / button >
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
< / div >
< h2 class = "sub" style = "border-top:0;padding-top:0;margin-top:1.5rem" > Installed< / h2 >
< div id = "addonsList" > < div class = "d" style = "color:var(--dim)" > Loading…< / div > < / div >
2026-08-31 16:00:34 +02:00
< div class = "note" > Extensions run with full app access — treat installing one like installing an unsigned executable.
Only load extensions whose source you trust.< / div >
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
< / section >
2026-07-29 13:54:34 +02:00
< / div >
< / div >
< script >
const C = window.cfg;
2026-07-30 19:29:32 +02:00
// sidebar navigation
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
const sections = ["general", "search", "passwords", "naming", "performance", "privacy", "addons"];
2026-08-06 01:41:31 +02:00
function showSection(sec) {
if (!sections.includes(sec)) return;
document.querySelectorAll(".side a").forEach((x) => x.classList.toggle("active", x.dataset.sec === sec));
for (const s of sections) document.getElementById(s).hidden = (s !== sec);
}
document.querySelectorAll(".side a").forEach((a) => a.onclick = () => showSection(a.dataset.sec));
// Main-process asks us to jump to a section (e.g. picker's "Search settings…"
// click routes to the Search section instead of the General default).
if (C & & C.onFocusSection) C.onFocusSection((sec) => showSection(sec));
2026-07-30 19:29:32 +02:00
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
const TOGGLES = ["restoreSession", "backgroundThrottle", "blockCamera", "blockMicrophone", "hideMediaDevices",
"clearCookiesOnQuit", "clearCacheOnQuit", "clearStorageOnQuit", "clearHistoryOnQuit"];
2026-07-29 13:54:34 +02:00
C.get().then((s) => {
2026-07-30 19:29:32 +02:00
for (const k of TOGGLES) {
const el = document.getElementById(k); if (!el) continue;
2026-07-29 13:54:34 +02:00
el.checked = !!s[k];
el.addEventListener("change", () => C.set(k, el.checked));
}
2026-07-30 20:58:45 +02:00
// search engines: default picker + custom-engine list + add form
const sel = document.getElementById("searchEngine");
const esc = (s) => String(s || "").replace(/< /g, "< ");
2026-07-30 22:55:52 +02:00
const engIcon = (e) => e.favicon
? `< img class = "ei" src = "${esc(e.favicon)}" onerror = "this.replaceWith(Object.assign(document.createElement('span'),{className:'es',textContent:'${e.sym || " 🔍 " } ' } ) ) " > `
: `< span class = "es" > ${e.sym || "🔍"}< / span > `;
2026-08-02 11:39:00 +02:00
const ENGINE_KINDS = [
{ key: "search", label: "Search engines" },
{ key: "llm", label: "AI answer engines" },
];
2026-07-30 20:58:45 +02:00
function renderEngines(d) {
2026-07-30 22:55:52 +02:00
// default engine: pick from the ENABLED engines (those shown in the dropdown)
const enabled = d.engines.filter((e) => e.enabled);
2026-08-02 11:39:00 +02:00
// group the dropdown too, so search and LLM don't intermix
sel.innerHTML = ENGINE_KINDS.map(({ key, label }) => {
const opts = enabled.filter((e) => (e.kind || "search") === key)
.map((e) => `< option value = "${e.id}" > ${(e.sym ? e.sym + " " : "")}${esc(e.name)}< / option > `).join("");
return opts ? `< optgroup label = "${label}" > ${opts}< / optgroup > ` : "";
}).join("");
2026-07-30 19:29:32 +02:00
sel.value = d.current;
2026-08-06 01:41:31 +02:00
// Main list = engines the user has INSTALLED. The toggle only flips
// enabled/disabled — the row STAYS. Right-click a row → "Remove from
// list" is what actually removes an engine (back to the catalog for
// built-ins, permanently for customs).
2026-07-30 22:55:52 +02:00
const list = document.getElementById("engineList");
2026-08-06 01:41:31 +02:00
const installed = d.engines.filter((e) => e.installed);
const rowFor = (e) => `< div class = "eng${e.enabled ? " " : " off " } " data-id = "${e.id}" data-kind = "${e.kind || " search " } " data-builtin = "${e.builtin ? 1 : 0}" draggable = "true" > ` +
2026-07-31 21:53:01 +02:00
`< span class = "grip" title = "Drag to reorder" > ⠿< / span > ` +
2026-07-30 23:26:00 +02:00
`< span class = "eic" > ${engIcon(e)}< / span > < span class = "enm" > ${esc(e.name)}< / span > ` +
2026-08-06 01:41:31 +02:00
`< label class = "sw sm" title = "${e.enabled ? " Turn off " : " Turn on " } " > < input type = "checkbox" data-id = "${e.id}" $ { e . enabled ? " checked " : " " } > < span class = "track" > < span class = "knob" > < / span > < / span > < / label > ` +
`< / div > `;
2026-08-02 11:39:00 +02:00
list.innerHTML = ENGINE_KINDS.map(({ key, label }) => {
2026-08-06 01:41:31 +02:00
const rows = installed.filter((e) => (e.kind || "search") === key).map(rowFor).join("");
2026-08-02 11:39:00 +02:00
if (!rows) return "";
return `< div class = "ehdr" > ${label}< / div > ${rows}`;
}).join("");
2026-08-06 01:41:31 +02:00
// Two catalog panes, split by tier — filtered by !installed now, not
// !enabled (a toggled-off engine stays in the enabled list, not here):
// catalog — curated first-class built-ins the user hasn't installed
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
// extra — wider bank, filtered live by the "Discover more" search box
2026-08-02 11:39:00 +02:00
const cat = document.getElementById("catalogList");
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
const extra = document.getElementById("extraList");
const filterInput = document.getElementById("engineFilter");
const catRow = (e) => `< div class = "cat" data-id = "${e.id}" > ` +
`< span class = "eic" > ${engIcon(e)}< / span > ` +
`< span class = "enm" > ${esc(e.name)}< / span > ` +
`< span class = "kind" > ${(e.kind || "search") === "llm" ? "AI" : "Search"}< / span > ` +
`< button class = "add" data-add = "${e.id}" > + Add< / button > < / div > `;
2026-08-06 01:41:31 +02:00
const uninstalled = d.engines.filter((e) => e.builtin & & !e.installed);
const catalogOff = uninstalled.filter((e) => (e.tier || "catalog") === "catalog");
const extraOff = uninstalled.filter((e) => e.tier === "extra");
2026-08-02 11:39:00 +02:00
if (cat) {
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
cat.innerHTML = catalogOff.length
? catalogOff.map(catRow).join("")
2026-08-06 01:41:31 +02:00
: `< div class = "cempty2" > All curated engines are already in your list. Discover more below or add a custom URL.< / div > `;
2026-08-02 11:39:00 +02:00
cat.querySelectorAll(".add").forEach((b) => b.onclick = () => C.setEngineEnabled(b.dataset.add, true).then(renderEngines));
}
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
if (extra) {
const paintExtras = (q) => {
const filt = String(q || "").trim().toLowerCase();
const shown = filt ? extraOff.filter((e) => e.name.toLowerCase().includes(filt)) : extraOff;
extra.innerHTML = shown.length
? shown.map(catRow).join("")
2026-08-06 01:41:31 +02:00
: `< div class = "cempty2" > ${filt ? "No engines match that filter." : "All discoverable engines are already in your list."}< / div > `;
Theseus: download tracker, search split, discover-more tier, UX polish
Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
2026-08-02 15:31:47 +02:00
extra.querySelectorAll(".add").forEach((b) => b.onclick = () => C.setEngineEnabled(b.dataset.add, true).then(renderEngines));
};
paintExtras(filterInput ? filterInput.value : "");
if (filterInput & & !filterInput.dataset.wired) {
filterInput.dataset.wired = "1";
filterInput.addEventListener("input", () => paintExtras(filterInput.value));
}
}
2026-08-06 01:41:31 +02:00
// Toggle: pure on/off in the enabled set — the row stays visible either way.
2026-07-30 22:55:52 +02:00
list.querySelectorAll('input[type="checkbox"]').forEach((cb) => cb.onchange = () => C.setEngineEnabled(cb.dataset.id, cb.checked).then(renderEngines));
2026-08-06 01:41:31 +02:00
// Right-click any row → context menu with "Remove from list" (moves a
// built-in back to the catalog; deletes a custom entirely).
list.querySelectorAll(".eng").forEach((row) => {
row.addEventListener("contextmenu", (e) => {
e.preventDefault();
openEngineMenu(row, e.clientX, e.clientY);
});
});
2026-08-02 11:39:00 +02:00
// drag-and-drop reorder — same-kind only (dropping a Search engine into
// the LLM section would just re-group visually on next render, so we
// reject cross-kind drags outright).
2026-07-31 21:53:01 +02:00
let dragId = null;
2026-08-02 11:39:00 +02:00
let dragKind = null;
const sameKind = (row) => row.dataset.kind === dragKind;
2026-07-31 21:53:01 +02:00
list.querySelectorAll(".eng").forEach((row) => {
2026-08-02 11:39:00 +02:00
row.addEventListener("dragstart", (e) => { dragId = row.dataset.id; dragKind = row.dataset.kind; e.dataTransfer.effectAllowed = "move"; row.classList.add("dragging"); });
2026-07-31 21:53:01 +02:00
row.addEventListener("dragend", () => { row.classList.remove("dragging"); list.querySelectorAll(".eng").forEach((r) => r.classList.remove("over")); });
2026-08-02 11:39:00 +02:00
row.addEventListener("dragover", (e) => {
if (!sameKind(row)) { e.dataTransfer.dropEffect = "none"; return; }
e.preventDefault(); e.dataTransfer.dropEffect = "move";
if (row.dataset.id !== dragId) row.classList.add("over");
});
2026-07-31 21:53:01 +02:00
row.addEventListener("dragleave", () => row.classList.remove("over"));
row.addEventListener("drop", (e) => {
e.preventDefault(); row.classList.remove("over");
2026-08-02 11:39:00 +02:00
if (!dragId || dragId === row.dataset.id || !sameKind(row)) return;
2026-07-31 21:53:01 +02:00
const ids = [...list.querySelectorAll(".eng")].map((el) => el.dataset.id);
const from = ids.indexOf(dragId), to = ids.indexOf(row.dataset.id);
ids.splice(from, 1); ids.splice(to, 0, dragId);
C.setEngineOrder(ids).then(renderEngines);
});
});
2026-07-30 20:58:45 +02:00
}
2026-08-06 01:41:31 +02:00
// Floating right-click menu for an engine row. Only one open at a time.
let ctxOpen = null;
function closeEngineMenu() { if (ctxOpen) { ctxOpen.remove(); ctxOpen = null; } }
function openEngineMenu(row, x, y) {
closeEngineMenu();
const id = row.dataset.id;
const builtin = row.dataset.builtin === "1";
const m = document.createElement("div");
m.className = "ctxmenu";
m.innerHTML = `< div class = "mi danger" data-act = "remove" > Remove from list< / div > `;
document.body.appendChild(m);
// Position, keeping the menu inside the viewport.
const rect = m.getBoundingClientRect();
const vw = document.documentElement.clientWidth, vh = document.documentElement.clientHeight;
m.style.left = Math.min(x, vw - rect.width - 6) + "px";
m.style.top = Math.min(y, vh - rect.height - 6) + "px";
m.querySelector('[data-act="remove"]').onclick = () => {
closeEngineMenu();
const call = builtin ? C.removeFromList(id) : C.removeEngine(id);
call.then(renderEngines);
};
ctxOpen = m;
setTimeout(() => {
const off = (ev) => { if (!m.contains(ev.target)) { closeEngineMenu(); document.removeEventListener("mousedown", off); document.removeEventListener("keydown", esc); } };
const esc = (ev) => { if (ev.key === "Escape") { closeEngineMenu(); document.removeEventListener("mousedown", off); document.removeEventListener("keydown", esc); } };
document.addEventListener("mousedown", off);
document.addEventListener("keydown", esc);
}, 0);
}
2026-07-30 20:58:45 +02:00
sel.onchange = () => C.set("searchEngine", sel.value);
2026-08-02 11:39:00 +02:00
// appearance (theme) — three visual cards: system | light | dark. Any
// unrecognised saved value falls back to "system" (follow the OS).
2026-07-30 22:00:01 +02:00
const th = document.getElementById("theme");
2026-08-02 11:39:00 +02:00
let themeValue = ["system", "light", "dark"].includes(s.theme) ? s.theme : "system";
const paintTheme = () => th.querySelectorAll(".tc").forEach((b) => {
const on = b.dataset.val === themeValue;
b.classList.toggle("on", on);
b.setAttribute("aria-checked", on ? "true" : "false");
});
paintTheme();
th.querySelectorAll(".tc").forEach((b) => b.onclick = () => {
themeValue = b.dataset.val; paintTheme(); C.set("theme", themeValue);
});
2026-07-30 22:00:01 +02:00
// WebRTC IP policy
const wm = document.getElementById("webrtcMode");
wm.value = s.webrtcMode || "public_only";
wm.onchange = () => C.set("webrtcMode", wm.value);
2026-09-06 17:14:57 +02:00
// Toolbar sizing — pushes to main which broadcasts settings-update back
// to the chrome renderer, so the URL bar / search box resize live.
const ub = document.getElementById("urlBarSize");
ub.value = s.urlBarSize || "wide";
ub.onchange = () => C.set("urlBarSize", ub.value);
const sb = document.getElementById("searchBoxSize");
sb.value = s.searchBoxSize || "normal";
sb.onchange = () => C.set("searchBoxSize", sb.value);
2026-07-30 20:58:45 +02:00
document.getElementById("engAdd").onclick = () => {
const name = document.getElementById("engName").value.trim();
const url = document.getElementById("engUrl").value.trim();
2026-07-30 22:09:26 +02:00
const sym = document.getElementById("engSym").value.trim();
2026-07-30 20:58:45 +02:00
if (!name || !url.includes("%s")) { alert("Enter a name and a URL containing %s (where the query goes)."); return; }
2026-07-30 22:09:26 +02:00
C.addEngine({ name, url, sym }).then((d) => {
document.getElementById("engName").value = ""; document.getElementById("engUrl").value = ""; document.getElementById("engSym").value = "";
renderEngines(d);
});
2026-07-30 20:58:45 +02:00
};
2026-08-02 11:39:00 +02:00
// "+ Add search engine" toggles the catalog panel below the enabled list.
const catBtn = document.getElementById("engAddBtn");
const catBox = document.getElementById("engineCatalog");
if (catBtn & & catBox) {
catBtn.onclick = () => {
const open = catBox.hidden;
catBox.hidden = !open;
catBtn.textContent = open ? "− Hide catalog" : "+ Add search engine";
};
}
2026-07-30 20:58:45 +02:00
C.engines().then(renderEngines);
2026-07-30 19:29:32 +02:00
// anti-fingerprinting mode selectors, with value field(s) shown on "manual"
const bind = (mode, showValIf, apply) => {
const m = document.getElementById(mode);
2026-07-29 13:54:34 +02:00
m.value = s[mode] || "show";
2026-07-30 19:29:32 +02:00
apply(m.value === "manual");
m.addEventListener("change", () => { C.set(mode, m.value); apply(m.value === "manual"); });
};
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
// Value fields: < select > for tz + lang manual mode, < select > city for
// location manual mode. Each has an "Other…" sentinel at the end that
// reveals a text input so the user can enter a value not in the built-in
// list (any IANA zone, any BCP-47 locale). selectWithOther handles the
// round-trip: if a saved value isn't in the predefined options, "Other"
// is auto-selected on load and the input pre-fills with that value.
function selectWithOther(selectId, otherInputId, settingsKey = selectId) {
const sel = document.getElementById(selectId);
const inp = document.getElementById(otherInputId);
const saved = s[settingsKey] ?? "";
const known = new Set([...sel.options].map((o) => o.value).filter((v) => v & & v !== "__other__"));
const isCustom = saved & & !known.has(saved);
sel.value = isCustom ? "__other__" : saved;
if (isCustom) inp.value = saved;
const applyVis = () => { inp.hidden = sel.value !== "__other__"; };
applyVis();
sel.addEventListener("change", () => {
if (sel.value === "__other__") { applyVis(); setTimeout(() => inp.focus(), 0); return; }
C.set(settingsKey, sel.value); applyVis();
});
inp.addEventListener("change", () => { const v = String(inp.value).trim(); if (v) C.set(settingsKey, v); });
return sel;
}
const tzV = selectWithOther("timezoneValue", "timezoneValueOther");
const lgV = selectWithOther("languageValue", "languageValueOther");
const tzOther = document.getElementById("timezoneValueOther");
const lgOther = document.getElementById("languageValueOther");
// "Manual" mode reveals both the select AND the Other input (if Other was picked).
const applyTzVis = (manual) => { tzV.hidden = !manual; tzOther.hidden = !manual || tzV.value !== "__other__"; };
bind("timezoneMode", null, applyTzVis);
// language: fixed dropdown when spoofing (top 10), broader dropdown when manual
2026-07-30 23:11:28 +02:00
const lngMode = document.getElementById("languageMode");
const lngSpoof = document.getElementById("languageSpoof");
lngSpoof.value = s.languageSpoof || "en-US";
lngSpoof.addEventListener("change", () => C.set("languageSpoof", lngSpoof.value));
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
const applyLng = () => {
lngSpoof.hidden = lngMode.value !== "spoof";
lgV.hidden = lngMode.value !== "manual";
lgOther.hidden = lngMode.value !== "manual" || lgV.value !== "__other__";
};
2026-07-30 23:11:28 +02:00
lngMode.value = s.languageMode || "show";
applyLng();
lngMode.addEventListener("change", () => { C.set("languageMode", lngMode.value); applyLng(); });
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
// location: region dropdown when spoofing, city dropdown when manual.
// Manual writes lat/lon into settings via the CITIES map below; the raw
// coord inputs were dropped from the UI (nobody types them by hand).
2026-07-30 23:11:28 +02:00
const locMode = document.getElementById("locationMode");
const locRegion = document.getElementById("locationRegion");
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
const locCity = document.getElementById("locationCity");
2026-07-30 23:11:28 +02:00
locRegion.value = s.locationRegion || "europe";
locRegion.addEventListener("change", () => C.set("locationRegion", locRegion.value));
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
// Location manual reveals the city select; picking "Other…" reveals a
// lat/lon pair, whose values are pushed to locationLat/Lon settings.
const locOther = document.getElementById("locationOther");
const locLatOther = document.getElementById("locationLatOther");
const locLonOther = document.getElementById("locationLonOther");
const applyLoc = () => {
locRegion.hidden = locMode.value !== "spoof";
locCity.hidden = locMode.value !== "manual";
locOther.hidden = locMode.value !== "manual" || locCity.value !== "__other__";
};
2026-07-30 23:11:28 +02:00
locMode.value = s.locationMode || "show";
applyLoc();
locMode.addEventListener("change", () => { C.set("locationMode", locMode.value); applyLoc(); });
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
locCity.addEventListener("change", applyLoc);
// Prefill Other lat/lon inputs from saved settings.
locLatOther.value = s.locationLat ?? "";
locLonOther.value = s.locationLon ?? "";
const saveOtherCoords = () => {
const la = String(locLatOther.value).trim();
const lo = String(locLonOther.value).trim();
if (la !== "") C.set("locationLat", la);
if (lo !== "") C.set("locationLon", lo);
C.set("locationCity", "__other__");
};
locLatOther.addEventListener("change", saveOtherCoords);
locLonOther.addEventListener("change", saveOtherCoords);
// If the previously-saved city was "__other__", set the select to it so the
// Other inputs stay visible on reload.
if (s.locationCity === "__other__") locCity.value = "__other__";
// Picking a city writes its lat/lon to settings — that's what
// navigator.geolocation returns to pages once "manual" mode is active.
const CITIES = {
london:[51.5074,-0.1278], berlin:[52.52,13.405], paris:[48.8566,2.3522],
madrid:[40.4168,-3.7038], rome:[41.9028,12.4964], moscow:[55.7558,37.6173],
istanbul:[41.0082,28.9784], dubai:[25.2048,55.2708], mumbai:[19.076,72.8777],
singapore:[1.3521,103.8198], bangkok:[13.7563,100.5018], shanghai:[31.2304,121.4737],
tokyo:[35.6762,139.6503], seoul:[37.5665,126.978], sydney:[-33.8688,151.2093],
new_york:[40.7128,-74.006], los_angeles:[34.0522,-118.2437], chicago:[41.8781,-87.6298],
toronto:[43.6532,-79.3832], mexico_city:[19.4326,-99.1332], sao_paulo:[-23.5505,-46.6333],
buenos_aires:[-34.6037,-58.3816], cairo:[30.0444,31.2357], nairobi:[-1.2921,36.8219],
johannesburg:[-26.2041,28.0473],
};
// Restore prior city selection when possible by matching stored lat/lon.
const restoreCity = () => {
const la = Number(s.locationLat), lo = Number(s.locationLon);
for (const [key, [x, y]] of Object.entries(CITIES))
if (Math.abs(x - la) < 0.01 & & Math . abs ( y - lo ) < 0 . 01 ) { locCity . value = key; return ; }
};
restoreCity();
locCity.addEventListener("change", () => {
const c = CITIES[locCity.value]; if (!c) return;
C.set("locationLat", String(c[0]));
C.set("locationLon", String(c[1]));
C.set("locationCity", locCity.value); // save the pick so we can restore it later
2026-07-30 23:26:00 +02:00
});
2026-08-02 11:39:00 +02:00
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
// Storage: "Clear all now" wipes everything the toggles cover, without
// waiting for quit. Confirm first — this signs the user out of everything.
const clrBtn = document.getElementById("clearNow");
if (clrBtn) clrBtn.onclick = async () => {
if (!confirm("Clear cookies, cache, site storage, and history now?\n\nYou'll be signed out of everything and open tabs won't be restored.")) return;
clrBtn.disabled = true; clrBtn.textContent = "Clearing…";
try {
await C.clearBrowsingData({ cookies: true, cache: true, storage: true, history: true });
clrBtn.textContent = "Cleared ✓";
} catch (e) { clrBtn.textContent = "Clear failed"; console.error(e); }
setTimeout(() => { clrBtn.textContent = "Clear all now"; clrBtn.disabled = false; }, 1600);
};
2026-08-02 11:39:00 +02:00
// ---- Naming section: BCNR/ICANN collision policy + remembered choices ----
function refreshCollisions() {
C.collisionState().then((cs) => {
// pick the current radio
document.querySelectorAll('input[name="collisionPolicy"]').forEach((r) => { r.checked = (r.value === cs.policy); });
const nn = Object.keys(cs.byName || {}).length, tn = Object.keys(cs.byTld || {}).length;
const bc = (cs.bcnrTlds || []).length;
document.getElementById("colSummary").textContent =
`Remembered: ${nn} name${nn === 1 ? "" : "s"}, ${tn} TLD${tn === 1 ? "" : "s"}. `
+ `BCNR-native TLDs on chain: ${bc}.`;
}).catch(() => {});
}
refreshCollisions();
document.querySelectorAll('input[name="collisionPolicy"]').forEach((r) => {
r.addEventListener("change", () => { if (r.checked) C.setCollisionPolicy(r.value).then(refreshCollisions); });
});
document.getElementById("resetCollisions").onclick = () => {
C.resetCollisions().then(refreshCollisions);
};
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
// ---- Passwords section: three states (setup / locked / unlocked) ---------
// The vault lives in main.js — this UI just calls IPC. No plaintext ever
// sits in this DOM except the value produced by a specific Show/Copy click.
const pwSetupEl = document.getElementById("pwSetup");
const pwLockedEl = document.getElementById("pwLocked");
const pwUnlockedEl = document.getElementById("pwUnlocked");
const pwListEl = document.getElementById("pwList");
function pwShow(which) {
pwSetupEl.hidden = which !== "setup";
pwLockedEl.hidden = which !== "locked";
pwUnlockedEl.hidden = which !== "unlocked";
}
async function pwRefresh() {
const st = await C.pwStatus();
if (!st.setup) return pwShow("setup");
if (!st.unlocked) return pwShow("locked");
pwShow("unlocked");
const res = await C.pwList();
renderPwList(res.ok ? res.entries : []);
}
function renderPwList(entries) {
if (!entries.length) {
pwListEl.innerHTML = `< div class = "cempty" style = "padding:12px 0" > No entries yet — add one below.< / div > `;
return;
}
pwListEl.innerHTML = entries.map((e) => `< div class = "eng" data-id = "${esc(e.id)}" > ` +
`< span class = "eic" > < img class = "ei" src = "https://icons.duckduckgo.com/ip3/${esc(e.domain)}.ico" onerror = "this.replaceWith(Object.assign(document.createElement('span'),{className:'es',textContent:'🔑'}))" > < / span > ` +
`< span class = "enm" > < b > ${esc(e.domain)}< / b > < span class = "pmuted" > · ${esc(e.username || "—")}< / span > < span class = "pmuted" style = "font-size:11px" > · ${e.kind === "generated" ? "generated" : "pasted"}< / span > < / span > ` +
`< button class = "cx pwShow" title = "Show + copy" > 👁< / button > ` +
`< button class = "cx pwDel" title = "Remove" > ✕< / button > ` +
`< / div > `).join("");
pwListEl.querySelectorAll(".pwShow").forEach((b) => b.onclick = async (ev) => {
const id = ev.target.closest(".eng").dataset.id;
const res = await C.pwGet(id);
if (!res.ok) return alert("Couldn't read: " + res.err);
try { await navigator.clipboard.writeText(res.password); }
catch { /* browser may block clipboard in dev — fall through to a prompt */ prompt("Password (copy manually):", res.password); return; }
b.textContent = "copied ✓"; setTimeout(() => (b.textContent = "👁"), 1600);
});
pwListEl.querySelectorAll(".pwDel").forEach((b) => b.onclick = async (ev) => {
const id = ev.target.closest(".eng").dataset.id;
if (!confirm("Remove this entry?")) return;
await C.pwRemove(id); pwRefresh();
});
}
// Setup — create vault
document.querySelectorAll('input[name="pwSeedSource"]').forEach((r) => r.addEventListener("change", () => {
document.getElementById("pwSetupMnemonic").hidden = document.querySelector('input[name="pwSeedSource"]:checked').value !== "mnemonic";
}));
document.getElementById("pwSetupBtn").onclick = async () => {
const p1 = document.getElementById("pwSetupPw1").value;
const p2 = document.getElementById("pwSetupPw2").value;
if (!p1 || p1.length < 8 ) return alert ( " Master password must be at least 8 characters . " ) ;
if (p1 !== p2) return alert("Passwords don't match.");
const kind = document.querySelector('input[name="pwSeedSource"]:checked').value;
const seedSource = kind === "mnemonic"
? { kind: "mnemonic", mnemonic: document.getElementById("pwSetupMnemonic").value }
: { kind: "generate" };
if (kind === "mnemonic" & & !seedSource.mnemonic.trim()) return alert("Paste your mnemonic or switch to 'Generate a new independent seed'.");
const res = await C.pwSetup(p1, seedSource);
if (!res.ok) return alert("Setup failed: " + res.err);
// Vault created AND unlocked by main. Clear the setup fields.
document.getElementById("pwSetupPw1").value = "";
document.getElementById("pwSetupPw2").value = "";
document.getElementById("pwSetupMnemonic").value = "";
pwRefresh();
};
// Unlock
document.getElementById("pwUnlockBtn").onclick = async () => {
const err = document.getElementById("pwUnlockErr");
err.hidden = true;
const pw = document.getElementById("pwUnlockPw").value;
const res = await C.pwUnlock(pw);
if (!res.ok) { err.textContent = res.err; err.hidden = false; return; }
document.getElementById("pwUnlockPw").value = "";
pwRefresh();
};
document.getElementById("pwUnlockPw").addEventListener("keydown", (e) => { if (e.key === "Enter") document.getElementById("pwUnlockBtn").click(); });
// Lock
document.getElementById("pwLockBtn").onclick = async () => { await C.pwLock(); pwRefresh(); };
// Add-entry form: toggle literal input; wire preview + save
document.querySelectorAll('input[name="pwAddKind"]').forEach((r) => r.addEventListener("change", () => {
const kind = document.querySelector('input[name="pwAddKind"]:checked').value;
document.getElementById("pwAddLiteral").hidden = kind !== "literal";
document.getElementById("pwAddPreview").hidden = kind !== "generated";
document.getElementById("pwAddPreviewOut").hidden = kind !== "generated";
}));
document.getElementById("pwAddPreview").onclick = async () => {
const domain = document.getElementById("pwAddDomain").value.trim();
const username = document.getElementById("pwAddUser").value.trim();
if (!domain) return alert("Enter a site.");
const res = await C.pwGenerate({ domain, username });
if (!res.ok) return alert("Preview failed: " + res.err);
document.getElementById("pwAddPreviewOut").value = res.password;
};
document.getElementById("pwAddBtn").onclick = async () => {
const domain = document.getElementById("pwAddDomain").value.trim();
const username = document.getElementById("pwAddUser").value.trim();
if (!domain) return alert("Enter a site.");
const kind = document.querySelector('input[name="pwAddKind"]:checked').value;
const spec = { domain, username };
if (kind === "literal") {
const lit = document.getElementById("pwAddLiteral").value;
if (!lit) return alert("Paste the password to save.");
spec.literal = lit;
}
const res = await C.pwAdd(spec);
if (!res.ok) return alert("Add failed: " + res.err);
document.getElementById("pwAddDomain").value = "";
document.getElementById("pwAddUser").value = "";
document.getElementById("pwAddLiteral").value = "";
document.getElementById("pwAddPreviewOut").value = "";
pwRefresh();
};
// Initial state — decide which panel to show now, and every time the user
// switches to Passwords in the sidebar (so a lock elsewhere is reflected).
pwRefresh();
document.querySelector('.side a[data-sec="passwords"]').addEventListener("click", pwRefresh);
2026-07-29 13:54:34 +02:00
});
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
// ---- Add-ons management ----
const addonsList = document.getElementById("addonsList");
function renderAddons(snap) {
const items = (snap & & snap.installed) || [];
if (!items.length) {
2026-08-31 16:00:34 +02:00
addonsList.innerHTML = '< div class = "d" style = "color:var(--dim)" > No extensions installed. Drop a folder into the extensions directory to install one.< / div > ';
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
return;
}
addonsList.innerHTML = items.map((a) => {
if (a.error) {
return '< div class = "row" > < div class = "txt" > < div class = "t" > ⚠ Load failed < span style = "color:var(--dim);font-weight:400" > ' + escapeHtml(a.folder) + '< / span > < / div > < div class = "d" style = "color:#f6768a" > ' + escapeHtml(a.error) + '< / div > < / div > < div > < button class = "btn" data-reveal = "' + escapeAttr(a.folder) + '" > Show folder< / button > < / div > < / div > ';
}
const caps = (a.capabilities || []).length ? '< span style = "color:var(--dim);font-size:11.5px;margin-left:8px" > ' + a.capabilities.map(escapeHtml).join(", ") + '< / span > ' : "";
return '< div class = "row" > < div class = "txt" > < div class = "t" > ' + a.icon + ' ' + escapeHtml(a.name) + ' < span style = "color:var(--dim);font-weight:400" > v' + escapeHtml(a.version) + '< / span > ' + caps + '< / div > < div class = "d" > ' + escapeHtml(a.description || "") + (a.author ? ' < span style = "color:var(--dim)" > — ' + escapeHtml(a.author) + '< / span > ' : '') + '< / div > < / div > < div style = "display:flex;gap:8px;align-items:center" > < button class = "btn" data-reveal = "' + escapeAttr(a.folder) + '" > Show folder< / button > < label class = "sw" > < input type = "checkbox" data-toggle = "' + escapeAttr(a.id) + '" ' + ( a . enabled ? " checked " : " " ) + ' > < span class = "track" > < span class = "knob" > < / span > < / span > < / label > < / div > < / div > ';
}).join("");
addonsList.querySelectorAll('input[data-toggle]').forEach((cb) => {
cb.addEventListener("change", async () => {
await C.setAddonEnabled(cb.dataset.toggle, cb.checked);
loadAddons();
});
});
addonsList.querySelectorAll('button[data-reveal]').forEach((btn) => {
btn.addEventListener("click", () => C.revealAddon(btn.dataset.reveal));
});
}
function escapeHtml(s) { return String(s || "").replace(/[& < >"']/g, (c) => ({ "&":"& ","< ":"< ",">":"> ",'"':"" ","'":"' " })[c]); }
function escapeAttr(s) { return escapeHtml(s); }
async function loadAddons() {
try { renderAddons(await C.listAddons()); }
2026-08-31 16:00:34 +02:00
catch (e) { addonsList.textContent = "Failed to load extensions: " + (e?.message || e); }
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
}
document.getElementById("addonsReload").addEventListener("click", async () => {
await C.reloadAddons(); loadAddons();
});
document.getElementById("addonsOpenDir").addEventListener("click", () => C.openAddonsDir());
document.querySelector('.side a[data-sec="addons"]').addEventListener("click", loadAddons);
// Populate on first paint so the tab is ready when the user clicks in.
loadAddons();
2026-07-29 13:54:34 +02:00
< / script >
< / body >
< / html >