Commit graph

88 commits

Author SHA1 Message Date
Local Dev
828100e2ce Theseus: warn before opening a name a blocklist flags
The blocklist consumer existed in the resolver library and the gateway, but
the browser opened a flagged name without comment. Now the indexer process
reads the subscribed lists from the chain every ten minutes and hands the
flags to main. A flagged name loads a warning page naming the reason, the
list and the report; the user may continue, and that is remembered per name.

Two gates, because content is reached two ways. loadBns shows the real
interstitial. serveBns refuses with an inline page on every path that skips
it: reload, back and forward, bns:// links, web app windows. The inline page
has no button, since a page at the site's own origin must not be able to
approve itself; only blocked.html may ask to continue, checked by file URL.

Settings › Naming has the policy: warn (default), never open, or ignore the
lists. The csam reason is never offered a way through. A list that cannot be
read keeps the last known flags and never stops a name from resolving.

The gateway put its own warning in front of flagged sites, which this
browser could not get past: it fetches files itself, with no cookie jar. It
now sends x-bns-policy: client and the gateway stays out of the way for a
client that says it decides for itself.

dev/blocklist-selftest.js runs the real protocol handler and decision
functions against a scratch profile.
2026-10-04 15:50:35 +02:00
Silent Mode
0ce2ab95dd Theseus: translator provider setting — LibreTranslate or Bergamot
A new row in Settings › Language, Translator engine: a radio between the
hosted LibreTranslate backend (the current default) and the on-device
Bergamot engine. The chip and auto-translate route through a single
dispatcher (translatorCall) that picks the provider by setting; adding
or removing an engine only touches the dispatcher.

Bergamot's WASM runtime and model manager are not bundled yet, so the
Bergamot provider falls back to LibreTranslate for now and prints a
one-time console notice. The setting itself is real today — a user can
declare their preference and the real engine lands in a later release
without the user touching Settings again. See bergamot.x for status.
2026-10-04 14:20:18 +02:00
Local Dev
b99d3a6e54 Merge branch 'master' into claude/goofy-dubinsky-1f8f33 2026-10-04 04:32:44 +02:00
Local Dev
9d6d8c3cc5 Theseus: one PIN — the vault PIN, offered to Aegis through api.vault.pin
Theseus and Aegis each wrapped the same master password under their
own PIN: two offline targets, two guess budgets, and two PINs to keep
in step. The vault PIN is now the only one. Built-in add-ons get
api.vault.pin {status, unlock, set, clear} (advertised by
features.vaultPin); unlock(pin) opens the vault in main and answers
only { ok } or why not, so the master password stays in main.

The policy is the one Aegis's PIN screens describe: five wrong PINs
lock the PIN for 15 minutes, every further wrong one locks it again,
and the master password always works. The unlock prompt uses the same
PIN pad and the same wording as Aegis, and Settings says so.
2026-10-04 04:15:15 +02:00
Local Dev
b9ffe7e1a0 Theseus Settings: the vault notes describe the vault that exists
Privacy still said there is no persistent password manager and pointed to
Bitwarden or KeePass, and Passwords said autofill was "phase 2". Both
contradicted the encrypted vault and the address-bar fill button Theseus
has had for a while.
2026-10-04 04:10:50 +02:00
Local Dev
561cb122ea Vault PIN: tie it to the TPM and never store it unsealed
Theseus's own quick-unlock PIN had the same limit as Aegis's: once the
DPAPI seal is opened (as the user, or from a disk image plus the
Windows password) the 6-digit PIN falls to an offline search. The PIN
is now also the authorization value of a Platform Crypto Provider TPM
key whose secret is mixed into the wrapping key, so the chip's lockout
bounds guessing; lib/tpm-pin.cjs is the same module Aegis uses.

set() now refuses when there is no real OS keystore (Linux basic_text
included) instead of writing the blob in the clear, an unsealed record
from an older build is deleted, and Settings says what the PIN actually
protects against on this machine.
2026-10-04 03:42:02 +02:00
Silent Mode
5f02973597 Theseus: Settings language dropdown — supported first, in their own optgroup
The dropdown mixed supported and unsupported languages in one list ranked
by global speakers, so the six languages the translator actually handles
today (en, es, fr, de, el, ru) were scattered between greyed rows. The
user had to scan every entry to find the ones they could pick. The
dropdown is now partitioned into two <optgroup>s — "Supported today"
first with the enabled languages, "Translator coming later (Accept-
Language only)" below with the greyed rows — so the top of the list
is actionable and the roadmap is still visible underneath.
2026-10-03 23:27:05 +02:00
Local Dev
71f0c96e93 Theseus: reopen the previous tabs even when history is cleared on quit
"Open previous windows and tabs" and "Clear history on quit" both default
to on, and the quit clear deleted session.json along with the history, so
every launch started from the start page and the restore setting did
nothing. The open tabs are what the user asked to reopen, not history:
while restore is on, the quit clear keeps them and still drops back/forward
and address-bar history. With restore off, the tab list is deleted as
before.
2026-10-03 23:01:56 +02:00
Local Dev
4f745406d8 Settings > Ariadne's Thread: Refresh/Install/Update live in the Status row
They had a row of their own with nothing else in it, which read as an
empty card with a stray button. Same layout as the Plug-ins list row now:
buttons beside the on/off switch.
2026-10-03 22:43:30 +02:00
Local Dev
278da658ce Merge extensions-on-first-use: add-ons start when first used, not at launch
The left-edge panels landed meanwhile, so a panel record now carries both
its side and replace-by-id; a manifest-declared panel may say side:left too,
or a dormant add-on would show its panel on the wrong edge until it starts.
2026-10-03 21:07:44 +02:00
Local Dev
de7735feb3 Theseus: quick-unlock PIN for the vault, shared with extensions
The vault re-locks on every restart and only the master password opened
it, so every extension that needs it (Aegis, now Pithos) either asked for
the master password itself or grew its own PIN. Theseus now owns one:

- Settings > Passwords sets, changes or removes a 6-digit PIN. The PIN
  wraps the master password (PBKDF2-SHA256, 600k iterations, AES-256-GCM)
  and the result is sealed with the OS keystore (safeStorage: DPAPI /
  Keychain / libsecret), so a copied vault-pin.json cannot be brute-forced
  elsewhere. Every unlock still ends at the master password.
- Three wrong PINs in a row require the master password. The strike count
  lives in the same file, so a restart does not reset it; a successful
  master-password unlock does. A PIN whose password no longer opens the
  vault (password changed) is dropped.
- unlock.html is Theseus's own prompt, over the whole window: PIN pad, or
  the master password. Extensions call api.vault.requestUnlock({ reason })
  (vault-derive capability) and get { ok } back; what the user typed never
  reaches them. Settings' locked screen offers "Unlock with PIN" through
  the same prompt.
2026-10-03 20:33:26 +02:00
Silent Mode
3536cd89bd Theseus: one language chip, auto-translate, picker owns up to what works
Two chips carried the same word in two shapes — a globe (Accept-Language)
and a translate chip (chip lights when page lang differs) — both labelled
"RU" at the same time for a Russian user. The chip for translation is
gone. The globe menu now covers both: a "Translate this page from X to Y"
item appears at the top when the loaded page is in another supported
language, flipping to "Show original" while a translation is on screen.
The chip's own code still shows the user's language (EN, RU, …); its
tooltip switches to "Translated to <X>. Menu: Show original." when a
translation is up, so the one chip reads the whole state.

With "Translate automatically" on, Theseus translates in place on
did-finish-load the first time it sees a supported source + target
mismatch for the active tab — no chip-click needed. A `_tr.autoTried`
latch keeps it to one attempt per document (a failing backend doesn't
retry on every reflow), and the latch resets on did-start-navigation so
the next page gets a fresh shot. The setting copy in Settings › Language
now says "Translate automatically" instead of "Offer to translate", so
the switch's label matches the behaviour.

The picker (both in Settings and in the globe menu) still lists every
language in WEBSITE_LANGUAGE_QUICK, but entries whose base code isn't
on the translator backend (en, es, fr, de, el, ru today) are shown
greyed out with "— translator coming later", and "Other… (Accept-Language
only, no translation)" is explicit about what free-form tags buy you.
The menu is a roadmap, not a lie: a user picking one of the greyed
entries sets Accept-Language and nothing else surprises them.
2026-10-03 19:37:04 +02:00
Silent Mode
c61fef08dc Theseus: Updates description — honest about the startup retry 2026-10-03 19:09:47 +02:00
Silent Mode
88044952e3 Theseus: Settings grows its own Language page
Website language, offer-to-translate and the translator peers list used
to sit as three sub-sections inside General, and Privacy › Anti-
fingerprinting duplicated the language picker on top of them — three
places to edit the one languageMode/languageValue setting. Settings
grows its own Language entry in the sidebar now; everything about
language — the preferred-language picker, the auto-offer toggle, the
peer list, the API key — lives there, and the Privacy duplicate is
gone. The chip in the URL bar still edits the same setting, so the
toolbar surface is unchanged.

The copy for the picker ("Your language") now says what the setting
actually drives: it's sent as Accept-Language and it's the translator's
target. Legacy "hide"/"spoof" language modes migrate to Automatic on
first open of Settings (the picker only has Automatic / a tag / Other),
so a profile that still carries one of those from an older release
lands on a valid state the first time Settings opens.

Greek (el-GR) was already in the picker; this release's silentmode.st/
libre backend added `el` to --load-only so the translator now has real
en↔el support — the picker and the backend are in step.
2026-10-03 19:05:25 +02:00
Local Dev
7254ffe6f4 Theseus: read BNS names from Ariadne's indexer; its own is the standby
Migration step 3 (DESIGN-bns-indexer-service.md). Ariadne's Thread now owns
BNS indexing on the machine, so Theseus no longer runs a second electrum
indexer beside it.

bns-indexer.js keeps its process and its messages to main.js, but inside
it is now an index host on the shared source chain:
- Ariadne's indexer over its pipe, trusted only after ariadne-helper.exe
  has checked the server process on that connection (found through
  Ariadne's uninstall key), then pushes;
- the local copies: Ariadne's files for both scopes, Theseus's own raw
  copy, the bundled one. The richest wins.
- Theseus's own index copy, written from the pipe data.

The shared core runs as Theseus's own indexer only while Ariadne is
unhealthy. That means: no pipe 4 s after launch, a pipe that fails the
check, a pipe that went silent, or an index not confirmed for 10 min while
Ariadne is not paused. The own indexer warm-starts from Ariadne's
snapshot, so there is no download and no cold sync. It hands back after
90 s of health, so a flapping service does not start and stop it. Economy
is not a failure and never triggers a takeover. With no checkable Ariadne
(portable, not installed, older than the pipe) the own indexer starts at
once, as in 0.3.70. The one thing Theseus does on Ariadne's side is run
the indexer's task at launch when "Launch at start" is off.

main.js passes the shared module paths (packaged as .mjs, which is why the
shared modules no longer import each other) and keeps the host's status.
The Ariadne panel takes its state from the indexer task when one exists,
and the sub-page says where Theseus's names come from.
2026-10-03 17:06:24 +02:00
Local Dev
4cc2d53582 Theseus: start extensions on first use, Startup switches in Settings
Every enabled add-on used to be activated synchronously in initAddons(),
during app.whenReady and before the window exists. That is the largest
launch cost left (boot tracer, 2026-10-03). An add-on can now say
"activation": "on-demand" in addon.json. It is then listed at launch but
not started. Its declared surfaces stay live: "panels" (new: sidebar
panels declared up front), toolbar-menu, context-menu-items and the
page-inject bridge, whose source is read on the first matching page.
activate() runs on first real use: a panel opened, a menu or context
item picked, a message from its panel, tab or page bridge, a Settings
addon-invoke, a wiz:// link (for Aegis). All of those go through
AddonHost.dispatch(), which starts the add-on and waits for it, so no
call is dropped. Concurrent callers share one activation, and
activations run one at a time.

Startup stays the default: the host cannot tell what an older add-on
does in activate(). request-filter add-ons and add-ons that declare no
surface are forced to startup. If activate() returns a promise, calls
wait for it (at most 5 s). api.startAtLaunch(bool) lets an on-demand
add-on ask to be started at launch again (for live relay sessions).

Converted: notepad, screenshot, translate, docx-editor, pdf-editor, vpn
(none has launch-time work: no file association, no auto-connect, and
add-on file tabs are not part of the saved session). Shield and Cookie
Pop-ups stay startup: Shield owns the request filter and must see the
first request; Cookie Pop-ups costs ~6 ms and acts unasked on every
page. Aegis stays startup and untouched: another session owns it. See
NOTE-aegis-on-demand.md (next commit).

Settings › Performance › Startup:
- "Start extensions when first used" (default on). Off = all at launch;
  switching it off starts the waiting add-ons immediately.
- "Start the wallet at launch". Shown disabled with a hint until the
  installed Aegis manifest allows on-demand. It applies with no Settings
  change once Aegis opts in.
- "Preload common menus" gates prewarmOverlays().
- "Use lightest" preset.
New keys are plain SETTINGS_DEFAULTS through the existing settings-set.
No new IPC channels.

Measured: boot-trace, fresh profile, --seconds 20 so the 30 s add-on OTA
poll can't swap Aegis mid-series; warm runs 2-3 of two paired series.
- Add-on activation at launch: 121-154 ms -> 104-174 ms. The six
  converted add-ons went from 16-20 ms to 0. The rest is Shield (83-148
  ms, noisy) and Aegis (15 ms in this tree's 0.9.0).
- Toolbar painted: 1278-1584 ms -> 1282-1481 ms (within noise).
- With "Preload common menus" off: 0 overlays prewarmed, 8 processes
  instead of 11, about 50-70 MB less at 15 s.

The bundled add-on versions are not bumped. Existing profiles keep their
old addon.json, and so stay on startup activation, until those add-ons
ship with a higher version (seedBundledAddons only reseeds a strictly
newer bundle).
2026-10-03 16:05:32 +02:00
Silent Mode
0b36532922 Theseus: translator peers list — fall back when a mirror is down
The chip ran against a single endpoint, which is the fastest way to go
dark: libretranslate.com's public tier moved behind an API key in late
2026, and most of the historical public mirrors (libretranslate.de,
argosopentech, lt.vern.cc, translate.terraprint.co) either 502 at any
given time, serve a parked page, or started requiring a key of their
own. One URL in settings meant one of those going down meant the chip
stopped working.

Settings.translateEndpoints is now an ordered list. The translator tries
each peer in order and returns the first non-error answer; a dead peer
is logged and skipped. Order is preserved — the first entry is the
primary. Shipped defaults put Silent Mode's own instances
(translate.silentmode.st, the BNS name translate.x) at the top and keep
libretranslate.com as the last-resort entry; neither Silent Mode URL
answers today, but a user's chip starts working as soon as either goes
live without a browser release.

Settings › General › Translate pages grew a list editor (same shape as
the quick-links one): PRIMARY tag on row 0, add a peer, remove any row;
bns:// URLs are accepted so a BNS translator doesn't have to be fronted
by an https host. The single-URL `translateEndpoint` setting carried
over from the earlier draft is migrated on load — a custom URL goes to
the front of the list, the historical default is dropped.
2026-10-03 15:19:09 +02:00
Silent Mode
f3c8998ecc Theseus: in-page translator (LibreTranslate client)
The Website-language setting only tells servers what the user prefers via
Accept-Language — many static sites (including names on BCDN) serve one
language and ignore it, so e.g. hello.bch loads in English for every user,
in every language. This adds a translator that converts the page's visible
text in place, so a Lithuanian user reads hello.bch in Lithuanian without
asking the server for anything.

The URL-bar grows a translate chip next to the website-language globe. The
chip lights up when the page's declared `<html lang>` differs from the
user's preferred language. Click it once to translate in place; click again
to revert — originals are kept in a renderer-local state slot and swapped
back without a reload. Right-click opens the chip menu (change target /
translator settings).

The engine lives behind a swappable adapter in main — this ships with the
LibreTranslate backend (POST /translate with {q, source, target, format}).
The endpoint defaults to the LibreTranslate public tier but is settable in
Settings › General › Translate pages, so a user with a self-hosted
LibreTranslate (or Silent Mode's own translate.silentmode.st once it is
up) swaps it there without a code change. On-device Bergamot (the WASM
engine Firefox Translations uses) will plug into the same adapter in a
later release — same contract (array of texts in, array of translations
out), the chip and revert path are already engine-agnostic.

The fetch goes through session.defaultSession.fetch so Tor and add-on
proxy rules apply uniformly, chunks the batch at ~3.8 KB per POST so a
large page spreads across several requests, times each one out at 45 s,
and reports a failure to the chip's tooltip so a dead endpoint reads as
such and not as a silent no-op. The injected walker skips SCRIPT / STYLE
/ CODE / PRE / NOSCRIPT / TEXTAREA and contentEditable subtrees, keeps a
reference to each text node and the original text, and reverts by
restoring from that pair.
2026-10-03 15:01:40 +02:00
Local Dev
adcea19f8a Theseus: background tabs stop unless kept running; popups close on click-away
A tab you switch away from is frozen (page lifecycle "frozen": no JS,
timers, network callbacks or media) one second later and thawed the moment
it is shown again. Right-click a tab → "Keep running in background" exempts
it (music, calls, dashboards); the strip marks it ▶. Dormant restored tabs
and tabs waiting on a page dialog are never frozen. Settings › Performance ›
"Stop tabs in the background" (on by default) turns it off. Freezing uses
the per-tab debugger applyFingerprint already keeps attached; Chromium only
freezes hidden pages.

The downloads, site-info and engine-picker popups close when focus moves
elsewhere in the window or to another app; the toolbar click that caused
that does not reopen them. Focus only moves into a popup while the window
is active — focusing it from the background blurs the window and closed the
popup it had just opened.

Also fixes a bug in the lazy overlays: isLoading() is still true while
did-finish-load is delivered, so a first show waited out the 4 s timeout
before appearing. Finished loads are now recorded explicitly.
2026-10-03 14:14:47 +02:00
Local Dev
08beadcf8f Theseus: close the Settings-tab vault leak and the add-on update signer bypass
A preload belongs to the WebContents, not the page: a website loaded into
the Settings tab kept window.cfg and could read every vault password, flip
settings and install extensions without consent. Settings and add-on tabs
now never load web content, and the channels behind settings-preload check
their sender. `navigate` no longer accepts calls from web pages.

Add-on updates trusted any publisherSig, whatever name it carried, even for
bundled add-ons. The trust root is now the installed addon.json (publisher,
or the operator key when there is none); versions must be plain dotted
numbers; a community install can't take over a bundled or foreign id.

Also:
- autofill matches and fills against the live URL, not a stale prov.host
- bns:// forwards the raw request path (..%2F escaped the name's bucket)
- clipboard-read denied, openExternal asks; forged collision choices ignored
- web pages can't window.open file:/chrome:/theseus:; data:/blob: no longer
  go to the search engine; the quick-links panel loses home-preload
- clear-history-on-quit is awaited and removes history.json too
- update helper takes its paths from the environment (non-ASCII profiles)
- electrum poll has a deadline; misses wait at most 2.5 s
- p records go through Tor; add-on proxy credentials are actually used
- whole-folder require-cache bust on add-on version change; failed
  activate() no longer leaks its request filter
- approvals released when the window closes; web-app ids stay on-origin
2026-10-03 09:50:10 +02:00
Silent Mode
08ecd093d2 Theseus 0.3.67: Opera-style quick-links strip on the left edge
New thin vertical column (44 px) on the left side of every page, Opera-style.
Click an icon to open its web app in a new tab; if a tab is already open on
that host, we focus it instead of stacking another one. Hidden in HTML
fullscreen so a video still fills the window; toggle via Settings › General ›
Quick links › Show the strip.

Defaults ship X, Telegram and WhatsApp. The Settings › General › Quick links
section lists the current entries with a Remove button each and a Title + URL
+ Add row that auto-prefixes https:// and auto-fills the title from the
hostname when empty. Edits write the whole settings.quickLinks array; the
strip view and the window layout react through settings-set, so no restart is
needed.

Settings › General › Updates panel also now runs standalone (no longer gated
by anything in the shared cfg.get().then() init), so a thrown exception in an
unrelated feature can't leave it stuck on "Loading…" any more — the version
line reads immediately and Check for Updates stays functional.
2026-10-02 23:49:39 +02:00
Silent Mode
cb4d900c1a Theseus 0.3.66: Updates panel is now truly independent of the shared Settings init
0.3.65 wrapped the Updates panel code in try/catch but still left it inside the
big C.get().then((s)=>…) block. If anything earlier in that block throws on a
specific profile — a feature's addEventListener on a missing element, a settings
read that rejects, anything — the panel's code never runs and the user sees
"Loading…" forever regardless of the try. Users reported this still happening on
0.3.65.

Updates panel now runs standalone right after the shared constants, as its own
immediately-invoked function, with no dependency on cfg.get() or any other
Settings init. Everything it needs (appVersion/recheckUpdate IPCs and two DOM
elements) is already available at script time. It will show "You're on vX.Y.Z"
or a specific error, never a stuck placeholder.
2026-10-02 23:04:59 +02:00
Silent Mode
a02deffe61 Theseus 0.3.65: Updates panel can't silently fatal into "Loading…" any more
Settings › General › Updates used to stay on "Loading…" forever if anything
earlier in the shared C.get().then((s)=>…) init threw, or if the version IPC
rejected — the .catch(()=>{}) on the version fetch swallowed it. Users then
had no in-app way to run "Check for updates", because the button sits in the
same panel.

Panel init now wraps in its own try, surfaces the actual error on the status
line (missing IPC / fetch rejection / init failure), and keeps the Check for
Updates button functional even when the earlier version read fails. Toolbar
chip is unaffected either way — the auto-updater runs independently.
2026-10-02 22:45:36 +02:00
Silent Mode
3c35b2605b Theseus 0.3.63: lazy tab restore, Privacy language simplified, chip reloads the page
Session restore now paints the full strip from the saved titles + favicons and
loads only the ACTIVE tab's page; every other restored tab lives as a dormant
WebContentsView and navigates for the first time when the user clicks it. For
a 20-tab user that drops cold start from 20 renderer loads racing chrome.html
to one, so launch is roughly flat whatever the tab count — fixes the "not
responding" freeze on a session with many restored tabs. session.json is now
v3 ({v:3, tabs:[{url,title,favicon}], active}); v1/v2 session files still
parse (their tabs restore lazy without a cached title, which arrives on first
activation). Reload on a dormant tab materialises it.

Privacy › Anti-fingerprinting › Language is now two modes — Automatic (system
language) and Manual — matching the General › Website language row and the
URL-bar globe chip. The old Spoof-choose top-10 and Hide-en-US modes are gone
from the UI; legacy saved values auto-migrate to Automatic on first open. The
Manual list is the same 24 languages the General row uses, kept in one place
(WEB_LANG_LIST), so all three surfaces stay in sync.

Changing the language via the globe chip or either settings row now reloads
the active tab — the server picked the response body from Accept-Language on
the original request, so an already-rendered page can't adopt the new language
on its own. A reload is what a user clicking a one-click language switch
expects.

The Location row's country dropdown now stacks under the mode dropdown on its
own line when Manual is picked, so an open menu above it can't visually cover
it (the row's flex-row max-60% layout could wrap it where another dropdown's
overlay sat).

Also: the settings-update broadcast now reaches every open settings tab, not
only the chrome — so changing the chip updates both the General Website-
language row and the Privacy Anti-fingerprinting Language row live without a
Settings refresh.
2026-10-02 21:51:28 +02:00
Silent Mode
f4514946bd Theseus 0.3.62: picker says just "English", not "American English"
Intl.DisplayNames.of("en-US") returns "American English", which spells out a
distinction the picker doesn't make — one row per language, with English the
UK original. Pass the base code to Intl so the chip tooltip, the "Automatic
(…)" label and the Settings hint all read as the plain language name
(English, Russian, Portuguese, Chinese) regardless of which regional variant
the OS or the saved setting happens to be.
2026-10-01 22:14:38 +02:00
Local Dev
93828a0172 fix(theseus): Presearch is frozen in the engine catalog
presearch.com has redirected every request, searches included, into a
dead host since 2026-09-28, so a user who picked it gets Cloudflare's
origin error instead of results. Rather than deleting the entry, a catalog
engine can now carry a frozen reason: it stays listed in Settings, greyed,
with the reason as its tooltip and an Unavailable badge where the switch
was; it is never enabled, never in the picker, never accepted as the
default from any path, and a profile that had it as default falls back at
startup. Turning it off still works, and deleting the field brings the
engine back exactly as the user had it.
2026-10-01 01:24:10 +02:00
Silent Mode
deae55647c Theseus 0.3.61: language picker — merge regional variants, sort by speakers
Sits on top of the earlier 0.3.61 commit (bundled into the same shipped build):

Spanish and Portuguese collapse to their originals — es-ES and pt-PT — and the
Mexican / Brazilian variants come off the picker (same 2-letter chip, roughly
the same text). Ordering is now global-speakers ranking with European
languages first, so the languages a European desktop is most likely to want
sit at the top: English, Español, Français, Português, Русский, Deutsch,
Italiano, Türkçe, Polski, Nederlands, Ελληνικά, Čeština, Svenska, Suomi —
then the non-European tier led by 中文, हिन्दी, العربية and so on.
2026-10-01 00:59:23 +02:00
Local Dev
596ea09fc7 feat(theseus/ariadne): settings panel — policy + per-source toggles + status report
Ariadne 0.1.13 exposed /api/status and per-source enable flags in
policy.json. Theseus's Plug-ins > Ariadne's Thread sub-page now wires those
into a full UI, no daemon restart, no UAC.

Added to the plugins-ariadne sub-page (after Status, before Remove):

  Collision policy   -- radio group (BCNR-first / ICANN-first) writes
                        C:\ProgramData\Ariadne\policy.json.policy; hot-reloaded
                        by the daemon within 5 s.
  Sources            -- 3-column grid, one row per source (snapshotHttps,
                        electrumWss, perQueryLookup, diskCache, localApi):
                        enable checkbox + last-state summary
                        (last success / last error / hit-miss counters /
                        disk-cache size+mtime). Toggle writes
                        policy.json.sources.<name>.enabled and re-polls after
                        the 5-s hot-reload tick so the state text catches up.
  Status report      -- <pre> JSON dump of GET http://127.0.0.1/api/status
                        with Copy report + Refresh report buttons. This is
                        the paste-me-into-support artefact for any diagnosis.

IPC wiring:
  main.js
    ariadne-get-status  -> GET http://127.0.0.1/api/status  ({ok, status|error})
    ariadne-get-policy  -> read C:\ProgramData\Ariadne\policy.json (or {})
    ariadne-set-policy  -> merge {policy}, write back (validates enum)
    ariadne-set-source  -> merge {sources.<name>.enabled}, write back
                          (validates against the known 5 names)
  settings-preload.js
    ariadneGetStatus, ariadneGetPolicy, ariadneSetPolicy, ariadneSetSource

All four handlers write policy.json as the local user; no UAC. Works because
install.ps1 grants BUILTIN\Users Modify on the file (0.1.7+).

Sub-page auto-refreshes state every time it opens (listens on the existing
'section' custom event dispatched by showSection).

Not building/shipping Theseus here -- this rides the next Theseus release.
Panel gracefully handles: daemon down (shows 'Daemon unreachable' with a
pointer to the Status toggle), localApi disabled (daemon returns 503, panel
shows the error), missing policy.json (all sources default to true).
2026-10-01 00:51:50 +02:00
Silent Mode
a3fb8917c3 Theseus 0.3.61: Privacy tidied — country dropdown, VPN row honest, language names in the picker
Privacy › Location is three modes now: Show real, Hide, Manual. Manual reveals a
50-country dropdown whose pick becomes the coordinates navigator.geolocation
returns to pages — country-capital granularity, no regions or free-form cities.
Old profiles on the retired "Spoof (region)" auto-migrate to Manual + the
region's representative country on first open, so nothing breaks.

VPN row in Privacy stops opening the wrong add-on: the sidebar now no-ops on a
specific panelId that isn't registered (used to silently substitute panels[0],
which surfaced Aegis whenever the VPN add-on was disabled), and the row hides
itself when vpn:main isn't in the sidebar panel list.

Language picker (globe chip menu + Settings › General › Website language) drops
the BCP-47 tag from every visible label — the tag surfaces only as the 2-letter
chip in the URL bar once picked. "English" is the UK original; the US variant
row is retired (same 2-letter chip, ~same text). Ukrainian dropped from the
quick list too. "Automatic" reads as the OS language name (Intl.DisplayNames)
instead of a raw en-US style tag.
2026-10-01 00:48:19 +02:00
Silent Mode
df2b1f57ff Theseus 0.3.60: pick your browsing language from the URL bar; per plug-in settings
A globe chip next to the URL-bar star shows the language sites see you in
(Accept-Language + navigator.language) — "AUTO" while following the OS locale,
the two-letter code once you pin one. Click opens a 23-language menu; the same
setting has a friendly row at the top of Settings › General. Both write to the
existing languageMode/languageValue and stay in sync with the Anti-fingerprinting
Language row through a settings-update broadcast (settings.html and chrome.html
both react live).

Settings › Plug-ins is now two compact rows — one per plug-in — with the on/off
toggle on the right and the update controls beside it. Clicking a plug-in's title
opens its own sub-page (plugins/ariadne, plugins/aegis) with the full description
and the Uninstall button, so the main list stays scannable and dangerous actions
stop travelling with the everyday ones. The Ariadne toggle and its sub-page
mirror the same scheduled-task state.
2026-09-30 02:16:11 +02:00
Local Dev
13e4c6997c Licenses: MPL-2.0 for Theseus, Ariadne and Hephaestus; Apache-2.0 for Argus; CC BY 4.0 for the documents
The public repos carried no license, so nobody could legally copy or build
on the code, and the whitepaper's "free software" had nothing behind it.
Theseus and its companions take the Mozilla Public License 2.0, the
file-level copyleft Firefox and Brave use, which is compatible with every
component they bundle. The resolver and gateway libraries take Apache-2.0
so that other implementations of the registry can reuse them without
copyleft in the way. The protocol documents and the whitepaper are CC BY 4.0.

A third-party notices file lists what the browser ships and fetches, with
the source offer the GPL sing-box binary the VPN add-on downloads requires;
the matching source archive is now published beside the binaries. The
names and marks are reserved in TRADEMARKS.md, separate from the code
license, so a fork must ship under its own name. Settings › General says
the license and links the three files; the whitepaper says the same.
2026-09-29 00:18:00 +02:00
Local Dev
668914354f fix(theseus): the default-engine control in Settings shows the engine icons
It was a native <select>, which can only show text, so each option carried
an emoji in front of the name; after the engine icons moved into the build
that was the one place still showing emojis. The control is now drawn by
Settings with the same icons as the rows below, grouped like the toolbar
picker, with arrow-key and Escape handling. Choosing a default there also
repaints the toolbar at once: the generic setting write never told it.
2026-09-28 20:07:51 +02:00
Local Dev
d016453f73 feat(theseus): search-engine icons ship in the build; custom engines cache theirs on disk
Every engine icon was an <img> pointing at Google's favicon service, fetched
again each time the picker, the toolbar or Settings rendered. Offline the
whole list collapsed to the emoji fallbacks, and each open told Google
which engines the user has configured. The catalog's icons now live in
engine-icons/<id>.png inside the app; a custom engine's icon is fetched
once (its own /favicon.ico first, the favicon service as fallback), cached
under the profile, and removed with the engine. Settings no longer falls
through to DuckDuckGo's icon service either. Phind ships no icon: its site
serves none through the bot wall.
2026-09-28 19:47:21 +02:00
Local Dev
27819684d5 feat(theseus): DNS over HTTPS and Global Privacy Control
DNS over HTTPS through Chromium's secure DNS (app.configureHostResolver),
under Privacy › Network: Default protection (encrypted via the chosen
provider, plain if that fails — the default), Increased protection
(always the provider, never plain) or Off, with Quad9, Cloudflare,
Mullvad, AdGuard or a custom resolver URL. Any DoH mode also turns on
Chromium's built-in resolver, as Chrome does. Silent Mode names never
touch DNS, and Tor resolves remotely through the SOCKS proxy, so
neither path goes around it.

Global Privacy Control, on by default, under Tracking protection: the
Sec-GPC header on every request (added in the one request-header hook
beside the client hints) and navigator.globalPrivacyControl in pages.
2026-09-27 22:10:06 +02:00
Local Dev
f1b1de5a9e feat(theseus): Settings pages have addresses and sub-pages; Privacy regrouped
Navigation base, the way Firefox does about:preferences#privacy: the
address bar follows the Settings page (theseus://settings/privacy) and
the hash mirrors it, so every page has a link; a page can have
sub-pages (theseus://settings/privacy/exceptions) with a breadcrumb and
a back arrow; open-settings and theseus:// links accept the two-level
slug.

Privacy now reads top-down: a "Theseus is on guard" card (Shield and
its running total, cookie pop-ups answered, Tor state, version), then
Tracking protection with the Shield and Cookie Pop-ups cards moved here
from Performance and a Manage exceptions sub-page listing the sites
each add-on was told to leave alone (remove to protect again), then
Device access, Anti-fingerprinting, Network (Tor switch and the VPN
panel) and Browsing data. Performance is about resources again.
2026-09-27 22:01:28 +02:00
Local Dev
2230d4200c feat(theseus): protections live in Settings › Performance; quiet dock for Shield and Cookie Pop-ups
Shield and Cookie Pop-ups are settings more than tools, so their
switches, the cookie mode, the counters and "Update rules" now sit in
Settings › Performance under a Protections heading, driven through the
add-ons' own message handlers (Settings-only IPC). Each card opens the
add-on's panel for the per-site details, and each panel links back to
Settings. The two add-ons start hidden from the toolbar's extension
row (manifest dock:"hidden", honoured once so a user who shows them
keeps them); "Show hidden" on the row brings them back.

theseus://settings and theseus://settings/<section> are now addresses,
so any page or note can link to a Settings page.

Also: a Settings or add-on tab that the user navigates elsewhere stops
counting as that tab, otherwise "open Settings" kept focusing a tab
that no longer showed Settings.
2026-09-27 20:37:30 +02:00
Local Dev
b2cfcd6dbf fix(theseus): extension updates re-check, announce and install in place
Updates published after launch never showed: the add-on channel was
checked once, 30 s after boot, and staged copies only applied on the
next launch with nothing telling the user. On 2026-09-22 Aegis 0.8.3
and VPN 0.1.3 landed minutes after the app's only check and stayed
invisible through manual scans made earlier and a restart made before
they were published.

Now: the check repeats every 4 hours; every check (boot, timer, manual,
add-on-driven) reports what is staged to the chrome, which shows a chip
for staged extensions; clicking it, or the "Update to vX" button that
appears on the extension's row and detail in Settings, promotes the
staged folder over the installed one and rebuilds the add-on host, so
the new version runs without a restart. Plug-ins (Aegis) are excluded
from the chip and the hot swap — a wallet updates from its own panel
and applies on the next launch.

Also from the same review: the new-tab button follows the last tab and
parks after the scroll arrow only when the strip overflows; Tor sits
left of the Aegis chip; plug-ins no longer appear in Settings ›
Extensions (they have Plug-ins); the extension detail view has a
labelled Back button, a close button and a Check now button; the
promotion helper returns what it promoted and accepts a filter.
2026-09-22 21:25:33 +02:00
Local Dev
1505f766c2 feat(theseus/settings): Extensions as compact rows with a detail view
The Extensions list was a stack of tall cards — description, author,
capabilities and buttons on every one — so seven add-ons filled the
page before the user found the toggle. Rows are now one line each,
Firefox-style: icon, name, built-in badge, version, a short update
status, the on/off switch and a ⋯ menu, grouped Enabled / Disabled /
Failed to load. Clicking a row opens the detail view in place: back
arrow, description, update status, author, version, type, folder with
Show folder, and a Permissions block that explains each declared
capability in plain words. The ⋯ menu (and right-click) offers Turn
on/off, Details, Show folder and, for non-bundled extensions, Remove —
a new addons-remove IPC that deletes the folder under the extensions
directory, refuses bundled add-ons (they would only be reseeded), and
clears the dock prefs it left behind.
2026-09-22 20:43:50 +02:00
Local Dev
9fbba50999 theseus: drop Community section from Settings > Extensions
Discovery of new community extensions belongs on theseus.x/extensions,
which has room to describe each one and lists them by publisher. Settings
just shows what's installed on this machine — one card per extension,
whether bundled or community. The Install button on the web page invokes
the same addons-install-community IPC, and Theseus refreshes the
installed list after each install, so the two lists were mirroring the
same state with a docx-editor card appearing on both sides.

Removes the Community h2, its list container, the Refresh catalog
button, loadCommunity() and its wiring — kept only the trust warning as
a note directly under the installed list.
2026-09-21 03:28:04 +02:00
Local Dev
cb7ffafcce theseus + site: fix duplicate extension cards + real-icon rendering
- 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.
2026-09-21 02:43:10 +02:00
Local Dev
bd6aab02fe feat(theseus): profile at %APPDATA%\Theseus, extensions under extensions\
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.
2026-09-21 01:55:25 +02:00
Local Dev
2b52fcef2f settings: the extensions link reads just "Extensions" 2026-09-20 15:33:33 +02:00
Local Dev
8ff8bc51ff feat: community extensions — publish with a BCDN name, install from Settings, theseus.x catalog
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).
2026-09-20 15:26:30 +02:00
Local Dev
68735ad490 fix(theseus): address bar follows the page; tab strip stops blinking; extensions page
- 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).
2026-09-20 14:39:07 +02:00
Local Dev
f46e9112b7 chore(theseus): 0.3.47 — plug-in category + panel-driven addon self-update, aegis 0.6.31
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".
2026-09-14 02:30:51 +02:00
Local Dev
992c02ea89 feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect
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.
2026-09-09 10:33:21 +02:00
Local Dev
124325673f feat(theseus/settings): per-extension update info inline on the card
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.
2026-09-09 02:43:28 +02:00
Local Dev
a65dc0a153 feat(theseus/devtools): 3-mode dock — bottom / sidebar / two-sidebars
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.
2026-09-09 00:51:05 +02:00
Local Dev
71bd3004e0 feat(theseus/settings): Plug-ins section — Ariadne's Thread + Aegis moved out of General
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)
2026-09-08 23:00:41 +02:00
Local Dev
83e2a83bf8 fix(theseus/settings/light): map every dark-navy card to a BCH-palette surface
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.
2026-09-08 18:49:53 +02:00