- Settings > Extensions: the Community list showed an entry even for
extensions already listed above under Installed, so docx-editor
appeared twice as soon as it landed in the gateway catalog. The
render now filters out any catalog item whose id is installed —
the Installed list handles updates via the poll + "Check for
updates" button, so the Community list only needs to surface
things you don't have. Empty-state copy branches on whether the
catalog is empty vs "everything is installed".
- theseus.x/extensions catalog renderer accepts data:image/... icons
in addition to emoji, so an extension whose manifest.icon is an
SVG data URI (pdf-editor, docx-editor) renders as an <img> instead
of the raw URI text.
- Static PDF Editor entry on that page now uses the same inline SVG
badge the browser dock renders.
The profile folder was Electron's default from the product name
("Theseus Navigator") and add-ons lived in addons\ under it. Now:
%APPDATA%\Theseus\extensions\ installed extensions
%APPDATA%\Theseus\extensions-data\ per-extension storage + scratch
%APPDATA%\Theseus\extensions-backups\ replaced copies
%APPDATA%\Theseus\extensions-staged\ staged updates
Both moves are one-time migrations on the first start that finds the old
layout: the profile folder is renamed (same volume, instant) or copied
when a rename is refused, with the old folder left in place in that case;
the four sub-folders are renamed before the extension host first reads
them. Nothing is deleted. THESEUS_USER_DATA still overrides everything.
The host now hands each extension its data folder as api.dataDir; the
Screenshot and PDF editor add-ons used to rebuild the old path from their
own folder for scratch files (so they recreated addons-data\ after the
move) and now use the field, with versions bumped so the bundles reseed.
Anyone who owns a BCDN name can now publish a Theseus extension, and every
Theseus can install it with the publisher's signature verified locally.
Gateway (Argus/src/gateway/public-gateway.mjs):
PUT /api/ext/<name>/<id>/<version> takes the gzipped tar, checks two BCH
message signatures against the name's current NFT owner (one authorises
the upload, one is stored in the channel), inspects the package
(addon.json at the root, id/version/main match, 8 MB cap), enforces
first-publisher ownership of an id and monotonic versions, and writes the
tarball, the extension's updates.json and community/catalog.json to Sia.
GET /api/ext/catalog reads the catalog back with CORS.
Theseus:
lib/publisher-sig.mjs recovers the signer of a channel entry; main.js
compares it with the publisher name's owner from Theseus's own chain
index before installing or updating, so neither the relay nor a tampered
catalog can pass off code under a trusted name. addon-updater.js gains
installCommunity() and accepts publisher-signed entries in the regular
update check (operator Ed25519 entries unchanged). Settings › Extensions
shows the community catalog with Install / Update; Settings › Plug-ins
links to theseus.x/plug-ins.
theseus.x:
/plug-ins/ is a separate page for the first-party plug-ins (Aegis,
Ariadne's Thread) with live versions and hashes; /extensions/ lists the
bundled extensions, the community catalog, and how to build and publish;
/extensions/publish/ signs and uploads a package in the browser with the
wallet that holds the publisher's name (session helper + wallet bundle
copied alongside).
- Address bar: the chrome view keeps document.activeElement on the URL
input after the user clicks into the page (focus moved to the tab's own
view), and the "don't clobber typed text" guard then froze the bar until
something blurred the field. The guard now requires real focus
(document.hasFocus()), and the field is blurred when the view loses focus.
- Tab strip: rebuilt with innerHTML on every tabs event, which recreated
every favicon <img> — a blink on the other tabs whenever one tab loaded,
reloaded or changed title — and dropped drag state mid-gesture. Elements
are now keyed by tab id (chips by group colour), updated in place, and
moved into order; the strip is never rebuilt.
- Settings › Extensions links to theseus.x/extensions. That page now has a
card per bundled add-on with the current signed version, tarball and
hash read from each add-on's updates.json at load (it still claimed
Screenshot 0.2.4 while the channel serves 0.6.5).
Theseus core:
- addons-host: manifest.category ("plugin") propagates through snapshot(); new
addon API surface checkAndStageSelfUpdate() + restartApp() so a plug-in
can offer in-panel "update now → restart to apply" without pushing the
user to Settings.
- main.js: wires the two new hooks into the AddonHost constructor.
- settings.html: Extensions listing filters out category==="plugin"; those
add-ons live in Plug-ins instead, single source of truth.
Aegis 0.6.31:
- BTC picker trimmed to Signet only; testnet3 hidden (adapter kept so any
existing wallet still loads).
- Wallet strip groups by chain, not chain:network; ticker gets a ▾ chevron
and a dropdown listing every subnetwork with its own totals. Mainnet
reads as the plain ticker; testnets carry a small Chipnet/Signet/Sepolia
pill inline.
- Per-unit price sits directly under the ticker; amount + fiat mirror on
the right — one glance covers name/price/holding/value.
- + Add and ⋯ More promoted from the strip into the header's action row,
next to the new ✎ chip (was the redundant top ⋯). Duplicate "Manage
current wallet" entry removed from the More menu.
- Footer update chip is a two-step flow via the new API: stage → restart.
Falls back to opening Settings on any Theseus that lacks the hooks.
- Manifest declares "category": "plugin".
Aegis Wallet 0.4.4 → 0.6.1:
- Vault lifecycle from the wallet gate. The locked / not-yet-created states
now show a master-password form (with optional BIP39 mnemonic on setup)
instead of redirecting users to Settings › Passwords. New
api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by
the existing "vault-derive" capability. api.openSettings(section) also
added; settings.html honours a #section hash on open.
- Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44
path or a WIF; the cashaddr is derived in the add-on, the signer material
goes to a separate wallet-imports.enc via api.vault.imports {list, add,
remove, signer}. Argus password-vault gains createImports / unlockImports /
saveImports with its own KDF salt so the imports key is disjoint from the
passwords key. lib/chain-bch-imported.js is a single-address Electrum
adapter; spend support is deferred to M.1b.
- Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted
in add-on storage. Fiat lines under balances, in the wallet picker, and a
portfolio total when 2+ wallets are open. Settings tab is now reachable
while the vault is locked so the toggle is always available.
- WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js).
@wizardconnect/{core,wallet} are loaded dynamically via api.import to stay
on the right side of LGPL §4d. Sign requests go through approvalModal and
are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS.
- DGB adapter load is now soft-fail: when Aegis runs from userData/addons the
bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of
taking the whole add-on down.
The Extensions page had a "Pending updates" strip at the top listing the
staged versions AND a "Check for updates" button that dumped a summary of
every extension's status into a global status blob just below the button.
Two places to look for what a single card was doing.
Fold both surfaces into the extension card itself:
- Each card grows a small update line under its description: green ↻
"Update vX.Y.Z staged — restart to apply" when a staged tarball is
waiting, red "Update failed" (with the addon-updater's detail) when
the last check-updates run couldn't advance the version, plain "Up to
date" when it could and there was nothing newer.
- The top strip is gone. The "Check for updates" button now just prints a
one-line summary (N staged / N failed / all up to date) — the detail
lives on each card.
- listStagedAddonUpdates fires on tab visit and after Reload, so the
card badge reflects the background poll without needing the user to
click Check.
New settings.devToolsDock (default 'bottom') read by the F12 handler
in main.js on each open. Values:
bottom - Chrome's own default, docked under the tab
sidebar - right-side dock (mode:right). Add-on sidebar tucks
out of the way while DevTools is up.
two-sidebars - right-side dock with the add-on sidebar left in
place, so both share the right area.
Settings > General > Developer tools now hosts a 3-option radio group
(same .polrow style as the collision policy). Changes apply instantly
- the F12 handler reads settings.devToolsDock every time it opens, so
no relaunch is needed.
New sidebar tab between Privacy and Extensions. The two long
system-scoped cards (Ariadne's Thread system-wide resolver, Aegis
built-in wallet) were bloating the General section; they cluster
naturally as "components that live alongside Theseus, each with
install/update/on-off controls of their own" and now have their
own home.
- Adds <a data-sec="plugins">Plug-ins</a> to nav.side
- New <section id="plugins"> with a short lede
- Ariadne + Aegis row blocks moved from General to Plug-ins verbatim
- sections array in showSection() extended with "plugins"
- Tab-switch handler clicks ariadneRefresh so status is never stale;
Aegis card auto-loads on page-init and its "Check for updates"
button stays user-initiated (avoid firing a network request every
time the user opens the Plug-ins tab)
Screenshot showed Registries > Collision policy rows rendering as
solid dark bars in light mode — .polrow hardcoded background:#10151f
with no light override, so the whole card blob-ed dark on the white
page and the text disappeared. Same story for .segseg, .engcat,
select/input backgrounds, .ctxmenu, .themeCards etc.
The light-media block gains a proper mapping keyed to the user's
BCH palette (#0AC18E / #253A49 / #F8FDFF):
- --bg → #F8FDFF (BCH white), --ink → #253A49 (BCH dark)
- .polrow → #f4f8fb card on white; hover #eaf0f5; SELECTED row
gets an acid-green tint via :has(input:checked) so BCDN-first
reads as the chosen option without an ugly dark bar
- .segseg, .engcat, .themeCards, .ctxmenu, select/input all get
the same treatment — subtle off-white surfaces on top of the
panel, dark-navy text
- Sidebar (.side) already had a light override; refined the border
and hover states to use rgba(37,58,73,X) so they match the ink
Verified via CDP: --bg=#F8FDFF, --ink=#253A49, .polrow color=
rgb(37,58,73), engcat bg=rgb(244,248,251), theme card bg=white.
Two follow-ups from the on-device test.
Two Aegis addons showing up (bchwallet + aegis):
- migrateAegisRename previously only ran when addons/aegis/ didn't exist,
which meant any bchwallet copy the signed OTA update endpoint reinstalls
after the first migration stays there forever, and AddonHost loads both
as separate wallets. Rewritten to always retire addons/bchwallet/ when
it's present, regardless of whether aegis/ is already installed. The
storage-copy (bchwallet.json → aegis.json) still only runs the first
time so a downgrade doesn't clobber fresh 0.4+ state.
- Also flushes any stray addons/siawallet/ that comes back the same way.
Sidebar dock and Extensions list icons showed the raw 🛡 emoji:
- chrome.html's dock-button renderer and settings.html's extensions-list
renderer now accept `data:image/svg+xml…` values for manifest.icon and
render them as <img> instead of text. Emoji strings still render as
before.
- aegis addon.json's icon is now the exact hex-aspis mark from
aegis.x/brand/favicon.svg (URL-encoded inline). Version bumped to
0.4.1 so seedBundledAddons reseeds the new addon.json on next launch.
CDP test surfaced two shape mismatches:
- cfg.listAddons() returns {installed:[...], sidebarPanels:[...]} — my
destructure treated it as a plain array, so .find() blew up with
'installed || []).find is not a function'.
- cfg.checkAddonUpdates() returns {report, skipped, staged:[...]} —
same problem, .find on an object.
Both call sites now normalise (installed = listRes.installed || [],
staged = res.staged || []) before finding the aegis entry. Verified
via CDP against a fresh install: card renders 'You're on v0.4.0.
Updates arrive over-the-air…'; clicking Check for updates keeps
that status (no staged update live); Restart-to-apply stays hidden.
Cleans up the naming that leaked from the wallet's origin story (BCH-only)
into the actual bundle layout. Aegis is one integrated addon now:
- Bundle folder: TheseusNavigator/bundled-addons/aegis/ (was bchwallet/).
- Addon id: "aegis" (was "bchwallet"). Vault-derive still accepts
legacy "bchwallet/*" and "siawallet/*" paths via the
absorbs list, so no on-chain funds move.
- Version: 0.4.0 (bumped to trigger seedBundledAddons's reseed).
- Retired: TheseusNavigator/bundled-addons/siawallet/. Sia is
folded into Aegis as a chain adapter (lib/sia/*.js
already in-tree) and Aegis's manifest lists siawallet
under absorbs so pre-Aegis SC keys derive identically.
main.js migrateAegisRename() runs before seedBundledAddons on every
launch. First run does the move; subsequent runs are no-ops:
- addons/bchwallet/ -> addons-backups/bchwallet-migrated-<stamp>/
- addons-data/bchwallet.json COPIED to addons-data/aegis.json (kept
copied not moved so a downgrade to 0.3.x can still boot).
- addons/siawallet/ -> addons-backups/siawallet-migrated-<stamp>/
(addons-data/siawallet.json left untouched — its walletdUrl is
per-user config Aegis's Sia wallet takes fresh via Settings).
settings.html Aegis update card now matches either "aegis" (new id) or
"bchwallet" (pre-rename) so upgraders coming from 0.3.x see the same
one card while the OTA endpoint's next signed bundle catches up.
Internal purpose paths inside index.js/chain-*.js are unchanged —
LEGACY_BCH_PURPOSE stays "bchwallet/mainnet/0" and every purposePrefix
still starts with "bchwallet/*". The addon absorbs its own former id,
so those paths keep resolving to the same seed the shipping Aegis has
been using since 0.3.14.
The Settings > Extensions "Check for updates" button used to report
one of two lines: "N updates staged; restart to apply" or "All
extensions are up to date". The second collapsed several distinct
outcomes into one indistinguishable line, so a user seeing "up to
date" couldn't tell whether the check actually reached the endpoint
or the fetch had silently failed.
checkAndStageUpdates now returns { report, skipped? } with one entry
per installed add-on and a status of:
no-update-url — addon.json doesn't declare updateURL
fetch-failed — DNS / connection / HTTP error on updates.json or the tarball (detail carries the message)
up-to-date — endpoint reached, no version strictly newer than installed
signature-invalid — offered version's sig didn't verify against any baked-in pubkey
sha256-mismatch — downloaded tarball's hash didn't match the signed one
extract-failed — tar could not extract (detail carries the message)
manifest-mismatch — extracted addon.json didn't match signed id/version
staged / already-staged — success
The Settings UI now renders one row per add-on with that status, so
a "no update" outcome is never mistaken for a silent fetch failure.
Return shape is back-compat: if a caller expects a bare array, the
UI normalizes.
Two additions:
1) Aegis (bchwallet) update card lands in Settings > General beside
the Ariadne one. Same look, different substance: Aegis is a
bundled add-on, not a system service, so no Install/Uninstall
buttons — the checkboxes there are 'Check for updates' and (only
when an update is staged) 'Restart to apply update'. Reuses the
existing signed OTA endpoint (addons-check-updates IPC) and
addons-list-staged for the pending-update surface, so new wallet
versions ship without a Theseus release.
New app-restart IPC (app.relaunch + app.quit) does the promotion
handoff — addons-host promotes staged updates on next boot.
2) DevTools (F12 / Ctrl+Shift+I) opens docked to the right of the
tab view (mode: 'right') instead of popping a detached window. A
user debugging a page gets the tools alongside it, matching stock
Chrome; anyone who prefers detached can still drag it out via the
DevTools own toolbar.
DuckDuckGo's icons.duckduckgo.com/ip3/… service was returning 404 for
Brave, Bing, Yandex and a few others in the SEARCH_ENGINES catalog —
so the settings row would fall through to the hardcoded emoji sym
(🦁 lion, 🔍 magnifier, etc.) instead of the real brand mark.
Two-part fix:
1) main.js: faviconUrl() switched from DDG's icons.duckduckgo.com to
Google's www.google.com/s2/favicons?domain=…&sz=32 as the primary
source. Google's service is materially more reliable — returns a
real 32×32 PNG for essentially every host.
2) settings.html: the engIcon renderer now stacks a two-source
fallback. If Google's PNG fails, retry with DDG's ico URL; if that
also fails, THEN drop to the emoji sym. Row is never blank, and
real brand favicons win over emoji whenever either service resolves.
The <option> in the dropdown still uses emoji because <option> can't
render <img> — that's a native <select> limitation, not fixable here.
The Updates card was placeholder-dashed until the user clicked Check
for updates. Show 'You're on v<current>' immediately on load using a
new app-version IPC (app.getVersion, no network) so the user can
answer 'which version am I on?' without a click.
Three tied-together fixes:
1) captureTab moves from WebContents.capturePage() to CDP
Page.captureScreenshot for every mode (visible / full / region).
Blank-screenshot symptom: after a toolbar-menu selection, the OS
popup teardown left the tab view marked occluded for a few frames
on some Windows setups, so capturePage() snapshotted a
stale/transparent frame at the correct dimensions — no 0x0, no
retry hit. CDP forces a fresh composite regardless of occlusion
state (same path the "Full page" mode was already using) and
returns a base64 PNG directly; PNG dimensions come out of the
IHDR chunk (bytes 16-24). Attach only when nothing else has, and
detach after only if WE attached, so an open DevTools stays
attached.
2) Editor gets a Discard button. Toolbar picks up an "×" glyph next
to Save/Copy that closes the editor tab and drops the working
screenshot. Top-level Escape now falls through the same path
after unwinding an in-flight text placement or crop rectangle. A
new "addon-tab-close" IPC lets an add-on's own tab close itself
(main matches the sender's webContents id against the tab list,
so a page can only close its own tab); window.silentmode.closeTab()
exposes it from addon-tab-preload.js.
3) Manual update controls in Settings > Extensions. New "Check for
updates" button at the top of the Extensions surface calls the
same signed-update polling the boot timer runs; the result is
surfaced inline ("All extensions are up to date" / "N updates
staged; restart Theseus to apply"). A "Pending updates" box
below lists what's in <userData>/addons-updates-staged/ so the
user knows what will be promoted on next restart.
Toolbar-menu popup settle bumped from 120 ms to 250 ms with an
explicit win.focus() in the popup close callback — the previous
window wasn't enough on slower Windows setups. CDP capture no longer
depends on this delay anyway, but the settle still helps any add-on
that does DOM work in its click handler before capture.
Screenshot add-on bumped 0.2.2 → 0.2.3 (Discard button; capture
fixes come from the host, not the add-on).
The sections array in showSection() still listed 'naming' — the id I
deleted when Registries got folded into General in 0.3.21. Clicking
Performance / Privacy / Extensions in the sidebar looked up
getElementById('naming'), got null, and threw a TypeError setting
.hidden on it. The loop crashed before the target section was
un-hidden, so nothing appeared to happen — the currently-shown
section stayed visible and the click looked like a no-op.
One-line fix: drop 'naming' from the sections array. Also documented
why it's absent so no one puts it back.
BCH-teal #0AC18E is ~2.9:1 on white — great for filled backgrounds and
tints, marginal for small text. Introduce --acid-text (dark mode: same
as --acid, light mode: #253A49 BCH-dark = ~12:1 on white). Rewrote
every text usage in settings.html to var(--acid-text):
- .brand (⛓ Theseus in sidebar)
- .side a.active (selected section)
- .btn text (Check for updates / add engine)
- .engcat .cat .add:hover text
- inline <b style="color:var(--acid)"> in the Ariadne status + Update
status renders
Fills, borders, and background tints stay var(--acid) so the BCH-green
brand splash is preserved everywhere it works. Also drops the manual
.brand override in the light-media block — no longer needed since the
variable does the job.
Two spots where the light-mode swap didn't take effect because the
color was hardcoded to a dark-mode pastel yellow:
- .btn (used by 'Check for updates' and the engine 'Add' button) had
color:#eaffb0 — pale yellow tinted on top of the acid tint, invisible
on a white ground. Route through var(--acid) so light mode picks up
#0AC18E (BCH teal) with proper contrast.
- .brand ('⛓ Theseus' in Settings sidebar) was var(--acid). In light
mode that's #0AC18E on #f6f8fb — ~2.7:1 contrast. Swap to #253A49
(BCH dark navy) in the light-media block so it matches the Theseus
toolbar chip treatment and reads as the primary UI accent.
Same #eaffb0 sweep applied to the two chrome.html spots that used it
(upchip download button, bcnrbar open button). Dark mode unchanged.
Two follow-ups on the light-mode acid work:
1) Every hardcoded #d6ff3d and rgba(214,255,61,X) in the browser
chrome and every addon panel now goes through var(--acid), so the
light-mode BCH-teal (#0AC18E) takes effect everywhere — not just
where var(--acid) was already used. Hex-with-alpha (#d6ff3d55 etc.)
converts to color-mix(); rgba() converts to rgb(from var(--acid)…)
for the same alpha with the current --acid hue. Chromium 128+
supports both. Files touched: chrome / settings / error / home /
approval / bchwallet / siawallet / screenshot (html + css).
Screenshot editor.js's #d6ff3d stays — that's the drawing colour
swatch, not UI chrome.
2) The Theseus button (.logo) and update chip (.upchip) become dark
BCH-navy chips (#253A49 background, #F8FDFF text) in light mode.
Previously the .logo hardcoded #d6ff3d text on a bright-acid tint —
invisible on a light toolbar. The dark chip stands out and gives
the light theme a distinct accent using the BCH secondary from
whybitcoincash.com's palette.
Two related visibility fixes:
1) Light-mode --acid → #0AC18E (Bitcoin Cash brand primary, from
whybitcoincash.com's palette per user). Direct swap from #088A66
(darkened variant) to the on-brand primary. Applied across chrome /
settings / error / home / approval / messages / bchwallet /
siawallet / screenshot editor. Dark mode's #d6ff3d is unchanged.
2) User-Agent no longer includes 'theseus-navigator/<ver>' or
'Electron/<ver>' tokens. Cloudflare's WAF was returning HTTP 503
'Service Unavailable' to any request carrying those (verified
directly against whybitcoincash.com — same URL, same headers, only
the UA differed; plain Chrome UA got 200, Theseus UA got 503).
Strip both tokens via a stockChromeUA() helper called from
applyAcceptLanguage(), which whenReady already invokes at boot.
Standard practice: Brave, Vivaldi, Slack all do the same.
Verified via CDP: navigator.userAgent now reports
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/130.0.6723.191 Safari/537.36
— indistinguishable from stock Chrome.
Prior light-mode --acid was #3a5c00 (dark olive-green) — legible but
off-brand. The Bitcoin Cash brand primary is #0AC18E (a teal-leaning
green already used in bchwallet's --bch variable). Darken it a step to
#088A66 for AA text contrast on white (~5:1) while staying in the BCH
family — the light-mode accent now reads as "Bitcoin Cash green,
darkened for legibility" instead of an arbitrary olive.
Applied across every chrome page + addon panel that carries the light
override (chrome / settings / error / home / approval / messages /
bchwallet / siawallet / screenshot editor). Dark mode's #d6ff3d
untouched.
New "Updates" card under Startup: a single "Check for updates" button
that hits the release manifest immediately (rather than waiting for
the boot-time and 6h-interval auto-check). Reuses the existing
recheck-update IPC; extended it to un-dismiss any chip the user closed
this session AND to return the current app version so the button can
render either:
- "You're on the latest (v0.3.21)." when nothing newer exists
- "vX.Y.Z is available — the update chip in the toolbar will offer it."
Button disables + shows "Checking…" during the fetch. Silent failures
report their reason ("Check failed: …") so the user isn't left staring
at a dash.
Two housekeeping changes to Settings > General:
- Toolbar block (Address bar size + Search box size selects) removed —
the toolbar drag handles landed in 0.3.15 do the same job in-place,
and the discrete presets were duplicating that. Underlying settings
keys and drag-set widthPx values still live in main; the UI dropdown
was the only thing gone.
- Registries section merged into General as a subheading. Sidebar
navigation entry "Registries" removed; the naming section is now
reachable from Settings > General. Collision-policy radios + reset-
remembered-choices + Ariadne install/turn on/off/uninstall/update
card all move as-is. No IDs changed, so the JS wiring (radios,
ariadne buttons, collision summary) rebinds against the same nodes.
The naming section stub stays commented so the section-nav JS doesn't
crash if it looks up the old id.
Extends the Ariadne toggle card in Settings > Registries with the three
lifecycle actions the user asked for:
- Install: runs the bundled AriadneResolver-Setup-<ver>.exe silently
and elevated (/VERYSILENT /SUPPRESSMSGBOXES /NORESTART). Single UAC
prompt, no wizard.
- Update: same installer, run over the top. Inno Setup detects the
matching AppId and upgrades in place. Only shown when the bundled
version is newer than what's installed.
- Uninstall: reads Inno's QuietUninstallString from
HKLM\...\Uninstall\{7E7A5F1C-...}_is1 and runs it elevated with
/VERYSILENT /SUPPRESSMSGBOXES /NORESTART.
Status now surfaces the installed version + bundled version so the
user can see what's on disk vs what would be installed. Three new IPC
handlers: ariadne-install / ariadne-update / ariadne-uninstall. Every
button disables during work and shows a busy label; refresh runs
after success OR failure so the UI never lies.
Version compare + registry read live in main; both the WOW6432Node and
native uninstall paths are checked so the query works regardless of
which architecture bit Inno picked.
Previous #4d7300 (0.3.10) was still too light against actual white
backgrounds — several tint fills (rgba(214,255,61,X)) and unpatched
addon panels were making the effective color feel bright green. Two
fixes bundled:
1) Bump --acid in every top-level page's light-media block from
#4d7300 to #3a5c00 — same hue, ~7:1 contrast on #ffffff (was ~5.5:1).
2) Add the missing light-media --acid override to the addon panels
that were still resolving to #d6ff3d: bchwallet/panel.html,
siawallet/panel.html, and screenshot/editor.css (was #b4e024, now
#3a5c00 to match).
Dark mode unchanged. Tint fills (rgba backgrounds at low alpha) still
stay as-is — at 8–15% opacity the specific hue barely matters and the
darker foreground now dominates.
New card under Settings > Registries: shows whether the system-wide
resolver daemon is running, stopped, or not installed on this machine,
and lets the user turn it on/off without opening the installer.
Ariadne runs as two elevated Windows Scheduled Tasks ("BNS Resolver
Daemon" + "BNS Sia Bridge"). Toggling requires admin — main spawns an
elevated PowerShell (Start-Process -Verb RunAs) that UAC-prompts once
per action, then re-queries state. Query is unelevated
Get-ScheduledTask so status checks are silent.
Three surfaced states:
running - "every browser on this machine resolves BCDN names"
stopped - "only Theseus resolves BCDN names; other browsers won't"
not-installed - link to silentmode.st/tools to grab the standalone installer
Theseus's own resolver is unaffected either way — it lives in-process
and doesn't depend on Ariadne. This toggle only controls what non-
Theseus browsers on the same box can resolve.
#d6ff3d on a #ffffff / #f6f8fb background sits at ~1.3:1 contrast — the
acid green went almost invisible any time the user flipped Settings >
Theme to light. Override --acid to #4d7300 in every chrome page's
prefers-color-scheme: light block. Same hue family, ~5.5:1 contrast
on white, still reads as the same brand color.
Also: chrome.html was missing --acid: #d6ff3d in :root entirely (every
site used var(--acid, #d6ff3d) fallbacks). Adding the real declaration
means the light override can actually take effect.
Files touched: chrome.html, home.html, settings.html, error.html,
approval.html, messages.html. downloads.html is a dark-only overlay
(hardcoded), collision.html already had a proper light-mode --bcdn.
Tint fills (rgba(214,255,61,X) at low alpha) stay as-is — the specific
hue barely matters through 8% opacity.
Verified live via CDP: getComputedStyle(--acid) returned #d6ff3d in
dark, #4d7300 after cfg.set('theme','light').
Two new settings under Settings > Appearance > Toolbar:
urlBarSize: wide (default) | medium | compact
searchBoxSize: hidden | compact | normal (default) | wide
Applied as data-attrs on the .bar element in chrome.html; CSS switches
the flex-basis of .urlwrap and the width of .searchbox. min-width on
.urlwrap guards against squeezing the URL invisible.
Settings-set now broadcasts settings-update to the chrome renderer, so
resizing takes effect live without a relaunch. Same channel is exposed
for future chrome-side settings.
Motivation: the extension dock grew a per-addon button per install, and
the URL bar (flex:1) had been eating all the remaining space. Users who
want more room for extensions can now shrink or hide the search box and
cap the URL bar width.
Setup 972f6209639122f32f032d5f2f9fc5a4808e0d4a810f88ae38ec9a1275ac52ac
Portable a9771f054ec36b9aff6ddee958cecf7e84cdacd4d7932aa8385c445aab4d29be
User-visible rename: the Settings tab and its labels say "Extensions"
now instead of "Add-ons". Internal identifiers (disabledAddons, the
addons/ folder, IPC channels, capability strings) stay put — code
churn wasn't worth it, and users only see the user-facing text.
Draggable sidebar. sidebar-preload.js now injects a 5px grip strip
along the LEFT edge of every panel document. mousedown+mousemove
streams delta-x px to main via sidebar-drag IPC; main clamps to
[200, 800] and debounces a save to settings.sidebarWidth. Width is
restored on next launch. The default is still 340. Faint acid-green
highlight on hover so the affordance is discoverable.
New extension capability: session-proxy. An extension whose addon.json
declares "session-proxy" gets api.setSessionProxy(rules) which routes
to session.defaultSession.setProxy — the same primitive Tor already
uses under the hood. Rules can be a string ("socks5://host:port") or
an object matching Electron's setProxy shape; null clears. The
capability is opt-in: an extension without the declaration gets an
error if it tries to call setSessionProxy. This is the framework
surface a private 3-VPS relay extension would build on (extension
folder stays on the operator's disk only; nothing about it appears in
the public build).
Deployed: scp installers + manifest + tools/ + releases/ pages to
VPS, sia-upload of both trees, verified HEAD 200 and manifest 0.2.2.
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).
Three follow-up asks from the previous ship:
1. Shield "secure" colour bumped from #4fd1a5 (mint) to #3fb950 — the
GitHub-style saturated green, matches the +N/-N diff colour the user
pointed at as reference.
2. Engine-picker "Search settings…" now opens the Search section
directly instead of General. New IPC channel `focus-section` fires
from main after picker-open-settings, carried through
settings-preload as `onFocusSection`, and the settings.html sidebar
handler exposes showSection(sec) so any section can be focused
programmatically. Works for both a fresh settings tab (fires on
did-finish-load) and an already-open one (fires immediately).
3. Toggle no longer removes an engine from the list. Two-tier state:
INSTALLED (visible in the Settings list) and ENABLED (toggled on in
the toolbar dropdown). Toggling off keeps the row visible with an
.off class (dimmed 55%). Right-click any row → new context menu with
"Remove from list" is what actually removes an engine (built-ins go
back to the catalog, customs are dropped entirely).
Model changes:
- New settings.installedEngines persistent array (defaults to
DEFAULT_ENABLED). enabledEngines becomes a subset of installedEngines.
- isInstalled(id) helper; allEngines() carries `installed: bool` alongside
`enabled`.
- New IPC `remove-from-list` (right-click action); exposed as
removeFromList in settings-preload.
- set-engine-enabled now also INSTALLS when enabling (the catalog "+ Add"
flow), preserves installed state when disabling.
- add-engine (custom URL) auto-adds the new id to enabledEngines too.
- remove-engine (custom delete) prunes from enabledEngines as well.
- Never-empty invariant kept: enabledEngines falls back to ["duckduckgo"]
if everything gets removed.
Settings UI:
- Enabled list shows all INSTALLED engines (was: only enabled), rendered
with toggle reflecting enabled state; rows carry data-builtin so the
context menu picks the right remove IPC.
- Catalog panel and Discover-more pane filter on !installed instead of
!enabled — a toggled-off engine stays in the enabled list, not here.
- Ctxmenu is a floating .ctxmenu div; closes on outside click / Escape.
- .eng.off dims the row and mutes the name colour.
Preview harness stubs updated to include the `installed` field on every
engine + `removeFromList` and `onFocusSection` no-op stubs so
_settings-preview.html renders the new UI accurately.
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.
Three UX fixes based on operator feedback (2026-08-02):
1) 'Open with…' is now a FULL-PAGE in-tab interstitial (was a modal window).
- loadBns loads collision.html via loadFile with query params instead of
opening a BrowserWindow.
- collision.html is navigation-based: buttons redirect to a special
bns://collision-choose/?host=…&choice=…&remember=…&resturl=… URL.
- serveBns handles that URL: persists remember=name/tld choice, then returns
a meta-refresh to the real target. For BCDN it appends ?_collision=bcnr
one-shot marker; for ICANN it redirects to https://<host><path>.
- loadBns strips and honors the ?_collision one-shot marker so BCDN
redirects don't re-trigger the prompt.
- Old modal path (collisionPromptOnce) removed.
2) Auto-switch from the address-bar chip: clicking BCDN/ICANN chip sets a
transient per-tab collisionOverride that loadBns consumes ONCE for this
navigation only, bypassing the soft-mode prompt entirely.
3) Address-bar toggle: chips stay visible during 'resolving' (no more
flash-and-reappear); active chip is colored (acid green for BCDN, blue for
ICANN — matching collision.html's palette); inactive is greyed with border,
clickable to switch. Feels like a proper toggle.
Settings > Registries copy: operator's exact wording adopted (title stays
'Registries', body uses 'BCNR-unique' term for TLDs that only exist on BCNR).
Terminology (user-facing labels now match the product/registry distinction):
- BCDN = Bitcoin Cash Domain Names (the product — what you view)
- BCNR = Bitcoin Cash Name Registry (the on-chain system that backs it)
Chrome badge, popover, Settings section and 'Open with…' prompt all show BCDN
for a resolved name. BCNR stays only where the registry itself is the subject.
'Open with…' prompt (collision.html):
- Larger window (640x520, was 480x340)
- Radio-select pattern with explicit Open / Cancel buttons (was auto-fire on click)
- Enter = Open, Esc = Cancel, keyboard-first
- Body copy per operator spec (no product-name-in-parens; blockchain-generic)
- Card titles kept short: 'BCNR / BCDN' + 'ICANN / IANA'
Address-bar chip:
- Says BCDN (not BCNR) for on-chain names; ICANN for web
- Dropped the '·.tld' suffix — TLD is already in the URL bar
- On collision candidates: shows BOTH chips (BCDN | ICANN) with active
highlighted; clicking the inactive one flips the tab (via collision-switch)
Settings section renamed Naming → Registries. Copy rewritten in the same
BCDN/BCNR frame; policy labels are now 'BCDN first' / 'ICANN first' / 'Ask
each time'.
Passive 'also on BCNR' bar copy updated to BCDN.
Verified: all preload/settings/main/chrome JS + HTML parse; no secrets in
staging.
Theseus soft-mode UX: 'Open with...' modal on collision, per-name/per-TLD
overrides, live per-tab switcher in the site-info popover, and a Naming section
in Settings for policy + reset. Backed by an on-chain root TLD certificate
(tlds.bch) that resolver-web.js discovers via fetchBcnrTlds()/isBcnrNativeTld().
Companion pieces:
- Argus/src/indexer/ELECTRUM-SOURCE-README.md — the featherweight VPS variant
(no BCHN node, no Fulcrum) now live as bns-indexer.service.
- Argus/DESIGN-root-tld-cert.md — clarified: NOT a governance workflow, just
ordinary key management (single wallet MVP -> 2-of-3 multisig). List gates
registration / surgical NRPT / soft-mode classifier — never resolution.
- ROADMAP-IDEAS.md — recorded SiaGit/GitHub.sia + user-friendly Sia UI ideas.
Full spec: Argus/DESIGN-collision-modes.md (already tracked).
Each engine row in Settings has a drag handle (⠿) and is draggable; dropping
onto another row reorders and persists via setEngineOrder, driving the
toolbar dropdown order. Visual drag/over states included.
- Search engines can be reordered (▲/▼ per row); order persists in
settings.engineOrder and drives the toolbar dropdown order.
- Manual anti-fingerprinting values are now dropdown-or-type (datalist):
timezone (common IANA zones), language (common locales), and a city picker
for location that fills exact lat/lon — each still accepts free typing.
- Privacy fix: enumerateDevices() leaked speaker (audiooutput) labels + device/
group IDs even with camera/mic blocked. New "Hide media devices" (default on)
blanks every device's label/deviceId/groupId and collapses to one per kind,
matching Firefox — closes the WebRTC device-fingerprinting leak.
- Anti-fingerprinting moved into the Privacy section (own sidebar item removed).
- Location spoof: pick a world region (Europe/Asia/N&S America/Africa/Middle
East/Australia) → representative coordinates, or Manual for exact lat/lon.
- Language spoof: pick from the top-10 world languages, or Manual for any locale.
- Custom engine dropdown as a floating overlay view (engine-picker.html) that
renders REAL favicons per engine — a native <select> can't show images.
- Larger catalog (DuckDuckGo/Google/Brave/Bing/Startpage/Yandex/Ecosia/Mojeek/
SearXNG/Wikipedia/Perplexity); Settings has a favicon checklist to choose
which appear in the dropdown (enabledEngines), plus manual add/remove.
- OpenSearch "scan": pages advertising a search engine surface an
"Add <site>'s search" entry in the dropdown.
- Toolbar search box now just a magnifier button that opens the dropdown
(no per-engine icon in the bar).
- Each search engine shows a symbol in the toolbar dropdown, the placeholder,
and Settings (🦆 DuckDuckGo, 🦁 Brave, 🔵 Google, 🔎 Bing, 🛡️ Startpage,
🔴 Yandex); custom engines take an optional symbol (defaults to 🔍).
- Fix: light-theme @media blocks for settings/popover/home were placed before
the base rules and lost the cascade (sidebar stayed dark) — moved them last.
- Light/dark/system theme (Settings > General) via nativeTheme.themeSource
driving prefers-color-scheme across chrome, settings, popover, and home.
- Search box redesigned: a magnifier icon opens the engine dropdown, the rest
is a wider typing field; styled to match the browser (was a raw <select>).
- Trimmed built-in engines to DuckDuckGo/Google/Brave/Bing/Startpage/Yandex;
custom engines still add/remove via "Add / edit engines…".
- WebRTC: the "WebRTC Network Limiter" extension can't run in Electron
(chrome.privacy API is unavailable), so its function is now a native
4-mode WebRTC IP policy in Settings > Privacy.
- Address-bar placeholder: "Ask a search engine or enter web address".
- Security popover redesigned to resemble Firefox's site-info panel:
lock/shield hero, "Connection secure" status, host, plain-language
subtitle, and a details grid; the overlay view auto-sizes to content.
- Search-engine picker returned to the toolbar with an "Add / edit engines…"
entry that opens Settings; added Ecosia, Mojeek, Presearch.
- Custom search engines: add (name + URL template with %s) / remove in
Settings > General; used by address-bar search.
- Address-bar placeholder: "Ask a search engine or enter web address".
- Performance: cache the chain index (was rebuilt on every navigation) and
warm it at startup — .bch pages open near-instantly after the first.
- Loading indicator: indeterminate bar under the toolbar, per-tab spinner,
and reload⇄stop button (driven by did-start/stop-loading + BNS resolve).
- Chrome cleanup: removed the bottom status bar and the redundant search
box; security padlock now always present at the front of the address bar;
minimal ICANN/BCNR pill at the end; favorites bar shows only on new-tab.
- Search: added Yandex; engine picker moved into Settings > General.
- Settings redesigned with a left sidebar (General / Performance / Privacy /
Anti-fingerprinting). Anti-fingerprinting now Show/Hide/Spoof/Manual for
timezone, language, AND location (geolocation coords overridden in-page).