Commit graph

83 commits

Author SHA1 Message Date
Local Dev
fa65ce5bab 0.3.34: greyer chrome bar, tighter bookmarks + drag-reorder, legible Tor/Theseus chips in light mode
Chrome strip: light-mode --bg/--surface/--surface2/--active retuned to a
soft neutral grey. Flat #ffffff tabs disappeared into light Windows
title bars and the top strip read as one bright rectangle; now the
tab row sits on #e6e8ec, inactive tabs are #f2f4f7, active tab is
white so the current one still pops.

Bookmarks: chips down to 130px max-width, 11px text, 12px favicon,
tighter padding, 22px row (was 26). Fits ~40% more saved pages in
the same width. Chips are now draggable — dragstart/dragover/drop
mirror the tab strip's reorder pattern, dropping before/after by
which half of the target chip the pointer is on. IPC bookmark-move
splices the array; a missing entry or self-drop is a no-op.

Tor + Theseus (⛓ logo) chips in light mode: white-on-#253A49 was
below legible at 12-13px on real light Windows themes. Both now use
#eef1f5 with #253A49 ink and a subtle border — same footprint, dark
letters read cleanly. .tor.connecting/.on keep amber/purple hue but
in light-appropriate fills with dark text. .upchip picks up the
same treatment for consistency.
2026-09-08 20:34:21 +02:00
Local Dev
3e3b2c2b8b fix(theseus/updater): verify manifest SHA-256 before arming install
The in-app updater fetched the setup .exe via
session.defaultSession.downloadURL and marked updateDownloadState="ready"
on any DownloadItem `done` with state === "completed", then handed
that path to install-update-now to spawn. No hash check against the
manifest — the same manifest that already carries a SHA-256 per file
and that the Ariadne addon updater verifies at ariadneDownloadInstaller
in this same file.

Consequence: a mid-stream truncation the runtime swallowed as
"completed" (a wrong Content-Length, a CDN cache truncation, an
interrupted TLS session, a corrupted mirror) armed install of a
half-file. install-update-now then ran the corrupt setup silently,
NSIS integrity check failed, uninstaller wiped the app first, and
Theseus was gone with nothing to click.

Now the completion handler streams the saved file through
crypto.createHash("sha256"), compares against updateAvailable.setupHash
from the manifest (already captured in checkForUpdate), and refuses to
arm install on mismatch — deletes the corrupt file and marks the
download failed so the retry loop can pick a fresh one up.

Companion fix to 0.3.32's --updated /S --force-run flags. Both
symptoms landed users in the same "browser vanished" state; 0.3.32
covered the spawn-side, this covers the download-side.
2026-09-08 19:52:15 +02:00
Local Dev
0f1411094e fix(theseus/updater): --updated flag so silent install actually reinstalls
Users reported Theseus being uninstalled by the auto-updater and never
coming back. Reproduced on 0.3.31:

The 'Install & restart' handler was spawning the NSIS setup with
just [\"/S\"]. Our config is oneClick:false + perMachine:false +
allowToChangeInstallationDirectory:true, so NSIS in silent mode:

  1. runs the old uninstaller (wipes install dir + registry path)
  2. hits the install phase, which in wizard mode expects UI to pick
     the install directory
  3. silent mode has no UI, registry path is gone → exits without
     installing anything

Result: browser gone, nothing left, no error surfaced to the user.

Fix: pass --updated alongside /S. electron-builder's NSIS template
treats --updated as 'this is an auto-update, reuse the existing
install directory from registry, don't rerun the uninstaller'.
--force-run relaunches the app after install.

Users stuck on 0.3.31 need to manually install 0.3.32 once (from
dl.silentmode.st) — the auto-updater in 0.3.31 will not recover on
its own. Their %APPDATA%\theseus-navigator profile is untouched by
the uninstall so vault, addons and settings survive the reinstall.
2026-09-08 19:34:06 +02:00
Local Dev
dc58d0426a fix(theseus/addons): captureTab skips addon-owned tabs
Reported: the Screenshot dropdown produces a blank white PNG "every time".
Verified end-to-end via CDP: the FIRST capture on a real page (silentmode.st)
worked (221 KB, real pixels). Every capture after that came back as a
uniform-white 1920×899 PNG (~24 KB, all bytes 255/255/255/255 confirmed by
canvas getImageData in the editor tab).

Root cause: api.captureTab reads activeTab(), which after the first successful
run is the editor.html tab the add-on just opened. CDP Page.captureScreenshot
on the editor's own tab happily snapshots its still-blank canvas, and the
addon writes that white PNG into scratch → opens a new editor showing it →
next re-capture snapshots THAT editor → all-white feedback loop.

Fix: track the last active tab that isn't an add-on-owned page (or the
Settings tab) in setActive(), and have captureTab fall back to it when the
current active tab has an addonId or settings flag. Last resort: the first
non-addon non-settings tab in the list. If none exists, throw a clear "open
a page you'd like to shoot first" error instead of returning white pixels.

No new capability; no manifest change; addons keep calling
api.captureTab({mode}) exactly as before. Ships in the next Theseus release.
2026-09-08 19:14:29 +02:00
Local Dev
22be28ed17 fix(theseus/aegis): retire bchwallet on every launch + branded dock/list icon
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.
2026-09-08 18:42:00 +02:00
Local Dev
2c7b82ad60 refactor(theseus/aegis): rename bundle bchwallet→aegis + retire standalone siawallet
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.
2026-09-08 18:17:44 +02:00
Local Dev
8055d39a9d feat(theseus/addons): per-add-on diagnostic report from checkAndStageUpdates
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.
2026-09-08 18:14:07 +02:00
Local Dev
b5f1cf468b feat(theseus/settings): Aegis update card + DevTools open in tab sidebar
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.
2026-09-08 18:09:55 +02:00
Local Dev
1886b76fb2 fix(theseus/settings): real search-engine favicons instead of emoji fallback
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.
2026-09-08 13:12:55 +02:00
Local Dev
1d0e25c4e0 feat(theseus/settings): show current Theseus version by default in General
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.
2026-09-08 12:36:21 +02:00
Local Dev
d59e969702 feat(theseus/find): Ctrl+F opens an in-page find bar
Standard browser Ctrl+F support:

- main.js: Ctrl+F caught in the shared before-input-event handler (same
  place as F12 / reload shortcuts) fires 'find-open' to chrome.
  Two new IPC handlers proxy to activeTab().view.webContents:
    find-in-page(query, {forward, findNext, matchCase})
    find-stop
  Each tab's webContents listens for 'found-in-page' and forwards
  {activeMatchOrdinal, matches, finalUpdate} back to chrome via
  'find-result' — only when it's the active tab so the bar doesn't
  update from a background tab's stale match count.

- preload.js: exposes onFindOpen / findInPage / findStop / onFindResult.

- chrome.html: adds a .findbar strip below the bookmarks bar (part of
  chrome-view height like .tordisc / .bcnrbar, so syncHeight picks it
  up). Input + "N of M" counter + prev/next/close buttons. Enter jumps
  next, Shift+Enter previous, Esc closes. Typing runs findNext:false
  (fresh search); pressing Enter runs findNext:true (walk matches).
  No-match state paints the input border red.
2026-09-08 07:53:48 +02:00
Local Dev
638aa4d326 feat(theseus/addons): CDP capture + editor Discard + manual update controls
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).
2026-09-08 02:27:36 +02:00
Local Dev
c992b2c366 Ariadne 0.1.1 + Theseus fetches it from silentmode.st (no more bundling)
Ariadne 0.1.1
- install.ps1 primary TLD source: HTTPS $Indexer/api/tlds -> advertised[]
  (electrum fetchTldList / legacy fetchBcnrTlds / ariadne.config.json /
   bootstrap kept as fallbacks in that order). Silent installs no longer
   stall when WSS is blocked.
- Persistent transcript at C:\ProgramData\Ariadne\install.log; separate
  install-error.log on any uncaught exception. Top-level trap logs the full
  stack, then exit 1 so Inno/NSIS silent-install callers see a real failure.
- .iss: SetupLogging=yes; DeinitializeSetup() copies Inno's log to
  C:\ProgramData\Ariadne\inno-setup.log on every exit path.
- Version bumped to 0.1.1. sha256=be1fa8841919b321841d90f8147abe1b1d7604fe7fff6c2e271dc47c4c24a2b6

Theseus decoupled from Ariadne
- Removed nsis/installer.nsh (custom Ariadne chain-install page).
- Removed extraResources entry for AriadneResolver-Setup-*.exe.
- Removed build.nsis.include from package.json.
- Settings > System-wide resolver panel now fetches the .exe from
  https://silentmode.st/releases-manifest.json, streams the download while
  hashing, refuses to spawn on SHA-256 mismatch. 30-min in-memory manifest
  cache so opening Settings doesn't spam the endpoint. Offline gracefully
  degrades to installedVersion-only / canUpdate:false.
- "bundledVersion" IPC field kept for renderer compatibility; it now
  carries "latest advertised by the site's manifest".

Deployed to silentmode.st:
- dl.silentmode.st/AriadneResolver-Setup-0.1.1.exe (25 MB)
- silentmode.st/releases-manifest.json (schema/2, updated 2026-09-08)
- silentmode.st/releases/ + silentmode.st/tools/ show 0.1.1

Local Dev <noreply@localhost>
2026-09-08 01:27:51 +02:00
Local Dev
f8c58538d0 fix: use BCH-primary #0AC18E in light mode + strip Electron token from UA
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.
2026-09-08 01:04:27 +02:00
Local Dev
5cd60f1df3 feat(theseus/settings): Check for updates button in Settings > General
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.
2026-09-08 00:43:26 +02:00
Local Dev
93a9ffcbb3 feat(theseus/settings): Ariadne — Install / Update / Uninstall alongside Turn on / off
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.
2026-09-07 23:03:57 +02:00
Local Dev
01253e882c fix(theseus/tabs): kill the tab-switch flash
Two independent causes of the flash the user reported when clicking
between tabs (and when opening Settings, which is just another tab):

1) Every tab view was created without an explicit backgroundColor.
   Electron's default is transparent, which means the first frame after
   setVisible(true) shows whatever is underneath the view — black, or
   the just-hidden previous tab — until the page paints. Set a solid
   ground that tracks the system theme (#0b0e14 dark / #ffffff light)
   so the first-paint gap is invisible.

2) setActive iterated tabs and toggled visibility in list order. If
   the currently-active tab came before the new active in the array,
   the loop hid the active one first and showed the new one later,
   leaving one frame where no tab was visible. Reverse: show the new
   target FIRST, then hide the rest. Compositor always has at least
   one tab view up during the switch.
2026-09-07 22:15:47 +02:00
Local Dev
cffb956a4c feat(theseus/addons): signed add-on update endpoint, à la Firefox XPI
Decouples bundled-add-on updates from Theseus releases. An add-on
whose addon.json declares an updateURL can be republished at any time
without shipping a new Theseus installer; existing installs pick it up
on the next boot's +30 s background check.

Client flow (main-process only, no UI touchpoints in this commit):

    initAddons()
    ├── promoteStagedUpdates()   # promote signed stage if newer
    ├── seedBundledAddons()      # bundle wins over on-disk if newer
    └── AddonHost.discoverAndActivate()
    30 s later:
    └── checkAndStageUpdates()   # fetch, verify, download, stage

Signature: Ed25519 over
"silentmode.addon-update-v1|<id>|<version>|<tarball-sha256>",
verified against a hardcoded set of operator pubkeys living in
addon-update-pubkeys.js. Domain-separated so the operator key can't
be tricked into signing a message with a different purpose. Empty
pubkey array is the shipping default — checkAndStageUpdates() then
short-circuits and no outbound requests are made, which is the safe
posture until the operator ceremonies a key in.

Payload: gzipped tar, extracted with the system tar (present on
Win10 1803+, macOS, Linux). Path traversal defended by tar's default
refusal of `..` entries; the extracted manifest's id + version are
re-checked against the signed values before staging.

Staged updates go to <userData>/addons-updates-staged/<id>-<version>/.
Promotion into <userData>/addons/<id>/ reuses seedBundledAddons's
backup dance: existing folder moves to
<userData>/addons-backups/<id>-<oldver>-<timestamp>/ so any local
edits survive.

New files:
- addon-updater.js — client
- addon-update-pubkeys.js — hardcoded pubkeys (empty; edit + rebuild to rotate)
- scripts/generate-update-keypair.mjs — one-time keygen
- scripts/sign-addon-update.mjs — operator packager+signer
- docs/ADDON-UPDATES.md — operator brief + threat model

Wired into main.js at boot; screenshot add-on's addon.json advertises
the reference updateURL for when the endpoint goes live.
2026-09-07 21:58:30 +02:00
Local Dev
c2be569ac1 fix(theseus/addons): reseed bundled add-ons when their version bumps
seedBundledAddons() only copied a bundled add-on when the target folder
was missing, so an updated bundled add-on never landed on any machine
that had ever run Theseus before — the 0.3.14 shipped screenshot editor
would sit in resources/ and be ignored by every dev machine with an
older screenshot/ folder from a previous test.

Compare the bundled addon.json version to the user's on-disk version.
On mismatch, rename the user copy to
<userData>/addons-backups/<id>-<oldver>-<stamp>/ and cp the fresh
bundle in. Backups live outside addonsDir so AddonHost's folder scan
doesn't pick them up as duplicate add-ons under the same manifest id.

Bump screenshot 0.2.0 -> 0.2.1 so the first build carrying this fix
actually reseeds the shipped-0.3.14 editor on existing dev copies.

Users who genuinely fork a bundled add-on should bump their local
version to something different from the bundled one — that keeps them
pinned. Users who edit files without bumping accept upstream updates,
with the timestamped backup as safety net.
2026-09-07 20:53:21 +02:00
Local Dev
6193d336db feat(theseus/addons): native toolbar-menu popup + capturePage retry
Two follow-ups from the screenshot editor rework (task_b9608dc6):

1) toolbar-menu popup goes native. The DOM popover in chrome.html was
   getting clipped by chrome.html's own WebContentsView height and then
   covered by the tab view below it. Route through main.js's
   Menu.popup() so the menu escapes the chrome-view layering entirely.
   Preload exposes toolbarMenuPopup(addonId, rect) + subscribes to
   toolbar-menu-closed so chrome can drop the button's "active" tint.

2) capturePage() intermittently returns a 0x0 image on Windows right
   after a navigation (view hasn't painted a frame yet). Retry up to
   six times with 150 ms between attempts; throw a specific error if
   still empty so the addon can surface a real message instead of
   silently producing a blank PNG.

Also lands an [addons] openAddonTab log line so the editor tab opening
is easy to trace in main's log.
2026-09-07 01:52:49 +02:00
Local Dev
909b93ee23 feat(theseus/toolbar): drag-handles + pin right-side items on URL/search resize
Two fixes bundled:

1) Right-side items (download, extension dock, Theseus button) no longer
   slide leftward when the URL bar is capped. Added margin-right: auto
   to .urlwrap under every capped state (data-urlsize=medium/compact
   and the new data-urlwidth override) so the leftover flex space sits
   AFTER the URL bar, keeping the right group pinned to the right edge.
   Verified: logo's gap from bar's right edge stays at 10px across
   default / urlBarSize=compact / urlBarWidthPx=500 / reset.

2) Drag handles for live resize. A 6-px col-resize strip sits on the
   trailing edge of .urlwrap and the leading edge of .searchbox; a
   pointerdown/move/up dance updates the width live via a CSS custom
   property and persists to settings on release. Two new keys:
   - urlBarWidthPx (0-1800, 0 = follow size preset)
   - searchBoxWidthPx (0-800, 0 = follow size preset)
   When either is non-zero, the corresponding CSS override wins over
   the discrete size preset. Preload gets setSetting so the drag can
   push the persistent value from chrome.

Visual affordance: handles are transparent by default, gain a faint
acid tint on hover and while dragging.
2026-09-07 01:07:23 +02:00
Local Dev
15694195d6 feat(theseus/screenshot): full-tab editor + toolbar-menu + open-tab capabilities
Reworks the screenshot addon into the flow the user asked for: the
dock icon opens a small dropdown menu (Visible viewport / Full page /
Region…) instead of the sidebar picker, and each capture opens a
full browser tab hosting an editor.

Two new addon-host capabilities land alongside:
- toolbar-menu: the addon declares an icon + item list in its manifest;
  the chrome dock renders a button that, on click, opens a small menu
  and dispatches the selection to the addon via addon-menu-select IPC.
- open-tab: api.openTab(path) opens a browser tab whose URL is the
  addon's local file. Origin-gated per addon; the editor uses a
  dedicated addon-tab-preload for its main → renderer bridge.

Editor page (editor.html/js/css):
- Crop, arrow, rectangle, circle, freehand pen, text, blur
- Colour swatches (red / yellow / acid / white / black), 3 stroke widths
- Undo/redo command stack, zoom controls
- Save PNG (goes through the download pipeline, chip picks it up)
- Copy to clipboard via ClipboardItem
2026-09-07 00:56:57 +02:00
Local Dev
57d71a0996 Ship Theseus 0.3.13 7d88e4c4 (Ariadne toggle + 'p' record + Screenshot addon)
Setup    7d88e4c46b02448e40d6075d10f2c6688c6d60c5a9ec41b9cbcb7684f131d6e1
Portable 5a4bcc6abb21c23729d79dd600142df4f171cc3d6bf71716dae1c802ac10e48f

Bundled since 0.3.12:

1514793 - Settings > Registries gets an on/off toggle for Ariadne's
Thread (system-wide BCDN resolver for non-Theseus browsers). Query is
silent Get-ScheduledTask; toggle spawns elevated PowerShell (UAC once
per action). Three states: running / stopped / not-installed.

b16f0a1 - New BCDN 'p' record type in Argus record-picker + Theseus
serving. Reverse-proxies an upstream URL under a BCDN name, keeping
the BCDN name in the address bar; uses upstream's own DNS + public
CA + Host header (unlike 'ip' which pins IP + on-chain TLS fingerprint).
Placed after 'ip' in the apex chain, suppressed under subdomain
inheritance so a 'p' name doesn't silently proxy every subdomain.

cfec253 - Argus registrar gains buildTldRegistrationTx + TLD_BEACON +
normalizeTld exports for minting per-TLD certificates per the TLD-
registry design.

bbfc05c - Bundled Screenshot add-on: capture-tab capability + sidebar
launcher for visible / full page / region modes; saves to Downloads.
Follow-up task_b9608dc6 will rework this into a full-tab editor.

Deployed. Verified LIVE 0.3.13.
2026-09-07 00:36:08 +02:00
Local Dev
10f01644c1 feat(theseus/settings): Ariadne's Thread on/off toggle for system-wide BCDN resolution
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.
2026-09-07 00:31:16 +02:00
Local Dev
0cf6ca10b4 feat(bns): 'p' reverse-proxy record type — mirror an upstream URL under a BNS name
New record kind alongside h / s3 / ip / u: `p` reverse-proxies the
request to a full upstream URL while keeping the BNS host in the
address bar. Unlike `ip` (which pins the upstream to a raw IP + on-chain
TLS fingerprint), `p` uses the upstream's own DNS + public CA cert and
sends `Host:` of the upstream so vhost-based origins answer correctly.

Argus (record-picker.js):
- Placed AFTER `ip` in the apex chain: a name carrying both keeps its
  existing pinned-IP behavior; names with only `p` get honored instead
  of falling through to `u` and 302-ing away.
- Not applied to subdomain inheritance — `p` is single-URL by intent,
  and inheriting through a subdomain would silently mangle the target.

Theseus (main.js serveBns):
- Fetches the upstream (path prefix from the `p` URL is preserved) and
  returns the response body/status/content-type verbatim. Provenance
  label "mirror" appears in the source badge.

Tests: record-picker.test.mjs covers `p`-only, `p`+ip precedence, and
the subdomain-suppression rule.
2026-09-07 00:18:07 +02:00
Local Dev
998775af42 feat(theseus/toolbar): user-resizable address bar + search box
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.
2026-09-06 17:14:57 +02:00
Local Dev
1c34fc2426 feat(theseus/keys): F12 / Ctrl+Shift+I opens tab DevTools (detached)
The packaged build stripped the native app menu, which took Chromium's
default DevTools accelerators with it. Wire the two everyone expects —
F12 and Ctrl+Shift+I — in the same before-input-event handler that
already owns reload / sidebar shortcuts. Always target the active tab
regardless of which view received the keystroke (chrome, overlay, tab)
so debugging is consistent with every other browser. Detach mode keeps
the tools out of the tab strip.
2026-09-06 16:50:50 +02:00
Local Dev
40391798e0 feat(theseus/bchwallet): per-site payment allowance for remembered sends
The dapp send approval gains an "Afterwards" dropdown: ask every time, or
allow up to 0.001 / 0.01 / 0.1 BCH more without asking. The allowance is
stored as permissions[origin].sendTx {capSats, usedSats}; sends within the
remainder go through silently and draw it down, a larger request re-prompts
(showing what is left) and the choice made there replaces the allowance.
No unlimited option. Settings > Connected sites shows the remaining budget
and Revoke clears it. Message signing still asks every time.

Host: approvalModal accepts `select` {id, label, options}; a chosen value
comes back as "+<id>=<value>" and is validated against the offered options.
2026-09-06 12:43:17 +02:00
Local Dev
7b3b5f7be8 feat(theseus/ctxmenu): search-for-selection in page right-click menu
Adds a "Search for '<selection>'" item to the page context menu when
the user right-clicks with text selected. Uses SEARCH() so it honours
whichever engine the user has picked (Startpage default, or their own
via the picker), opens in a new foreground tab so the current page
isn't lost, and truncates the label at 40 chars so a long selection
doesn't stretch the menu. Ampersands in the label are doubled so
Chromium doesn't eat them as accelerator markers.
2026-09-06 12:24:41 +02:00
Local Dev
67939b1493 feat(theseus/bchwallet): window.bitcoincash dapp bridge with per-origin permissions
wallet-inject.js runs in the isolated world of https://*.x pages and exposes
window.bitcoincash { isTheseus, version, network, getAddress, signAndSend,
signMessage }. Every call is routed page -> addon-page-msg -> activate()
handler -> approval overlay showing the requesting origin:
- getAddress: approval with an "always allow" checkbox; grants persist in
  api.storage.permissions and are listed/revocable under Settings.
- signAndSend / signMessage: approval on every call, never remembered.
  signMessage returns a BIP-137 recoverable signature (verified offline).
- one pending approval per origin; page-facing errors never echo balance.
Host fix: the inject IPC assigned event.returnValue twice, so pages always
got an empty script list.
2026-09-06 02:56:34 +02:00
Local Dev
de576935c1 feat(theseus/bchwallet): receive + history — vault-derived keys, cashaddr, QR, electrum
Wallet core on mainnet:
- keys from api.vault.derive("bchwallet/mainnet/0") -> BIP32 m/44'/145'/0'
  (@scure/bip32), never persisted; wiped on deactivate.
- lib/cashaddr.js (encode/decode + legacy Base58Check, spec vectors pass),
  lib/keys.js (hash160, p2pkh, electrum scripthash, ECDSA DER + BIP-137
  recoverable signing), lib/tx.js (serialization, SIGHASH_ALL|FORKID
  digest, coin selection, fee estimate), lib/electrum.js (Fulcrum WSS
  client with failover + subscriptions), lib/wallet.js (gap-limit scan,
  balance, UTXOs, 25-tx history with per-tx deltas, cached public txs).
- qr.js: dependency-free QR encoder (byte mode, v1-10, EC M/L; verified
  against jsQR).
- panel: balance header, Receive (QR, copy, next unused address, explorer),
  History (deltas, confirmations, explorer links), Settings (derivation
  path, electrum server list, xpub / approval-gated xprv reveal). Locked
  and not-set-up vault states explained in-panel.
- host: api.import for ESM-only deps, api.openTab for explorer links; an
  add-on whose activate() throws is no longer listed twice.
2026-09-06 02:46:41 +02:00
Local Dev
ffeda26345 feat(theseus/addons): vault-derive, page-inject and approval-modal capabilities
Three opt-in capabilities for add-ons, plus the plumbing they need:

- vault-derive: api.vault.derive("<id>/<path>") resolves once the password
  vault is unlocked with a 32-byte HKDF child of the vault root under
  "silentmode/addons/<path>". Path must start with the add-on id.
- page-inject: manifest "page-inject" {preload, origins}; a session-wide
  preload asks main (sync, against the committed URL) which add-on bridges
  apply and runs them in the isolated world with a scoped `theseus` object.
- approval-modal: api.approvalModal({title, body, origin, rows, actions,
  checkbox}) shows a consent overlay over the tab area (approval.html);
  resolves to the picked action id, "cancel", or "<id>+<checkbox>".
- api.onMessage/emit + window.silentmode.invoke/on for panel <-> activate()
  messaging; page bridges use addon-page-msg, gated by tab + origin match.
- api.require so add-ons can share Theseus's dependency tree.
2026-09-06 02:33:26 +02:00
Local Dev
5450813fe2 dev(theseus): THESEUS_USER_DATA env points a run at a throwaway profile 2026-09-06 02:21:16 +02:00
Local Dev
61ebe44dac fix(theseus/bookmarks): favicon + working edit-title (window.prompt is blocked)
Two bugs the user hit:

1) Edit title silently did nothing. window.prompt is disabled in
   Electron BrowserView contexts, so prompt() returned null and the
   round-trip remove-then-re-add never fired. Built an in-chrome
   modal (.promptmask/.promptbox) that resolves to the trimmed
   string on OK/Enter or null on Cancel/Escape/mask-click, and
   swapped the ctx-menu Edit action to use it. Added a real
   bookmark-update IPC (title + optional favicon merge) so the edit
   no longer round-trips through remove/add — same URL, just the
   title changes.

2) Bookmarks had no favicon. The bookmark data model was {title,url}
   only, and the row template had no icon slot. Extended:
     - Renderer tracks current.favicon from onTabs's active tab.
     - Star click and ctx "Add current page" pass favicon along.
     - main persists a favicon field (capped 2KB) and emits it.
     - Row template shows the favicon (14×14) with an onerror
       fallback so a broken data:/URL doesn't leave a broken glyph.
     - Backfill: when a currently-open URL matches a stale bookmark
       without favicon, the render pass patches it once via
       updateBookmark. Stops itself the next render.

Older bookmarks.json files stay valid — no schema migration needed;
missing favicon just renders the empty placeholder slot.
2026-09-04 22:12:03 +02:00
Local Dev
7aae2b2f3b feat(theseus/error): offline banner + BCNR did-you-mean on typos
Two problems bundled into the branded error page:

1) "fetch failed" on no network — the tab kept falling through to
   name-not-registered (and other "we asked and got nothing" verdicts)
   because a totally offline browser can't reach the beacon to know.
   Add a first-class `offline` kind that runs before every other
   verdict: navigator.onLine === false wins; the page says "You appear
   to be offline" and keeps the address so the user can Retry after
   reconnecting.

2) Typos on BCNR names (games.x when they meant game.x) — the page
   only offered Search / Register. Now the page asks main for the top
   near-matches from the warm sharedIndex (Levenshtein ≤ 2, same TLD)
   over an origin-gated `error-bns-similar` IPC. Up to three matches
   render as chip-links; clicking one retries at that host, preserving
   the original path. Works offline too — the index is local.

Origin-gate on the IPC uses the existing isErrorPageSender check, and
error-preload.js exposes only the invoke — no arbitrary index access.
2026-09-04 20:21:30 +02:00
Local Dev
c5dd03adc3 Ship Theseus 0.3.4 3d6d3d6a (address-picker delete X, Enter picks highlight, URL bar override)
Setup    3d6d3d6aeacf482284707f50d129315d07a593e09b5d68af852abe5aa0ed4c92
Portable 6c8c5ef212bfccb377e17404e83a18cf2f09771d0eb3e0ec86a85587efdcfc9e

Three address-bar suggestion fixes.

Per-row ✕ delete on hover. Clicks on the X call address-forget instead
of address-pick; the row disappears optimistically in the picker and
main drops the entry from history + persists. Sender-URL gated to the
picker's own file:// origin.

Enter with a highlighted suggestion now navigates to THAT url. The
URL input handler tracks a pickerHasCursor flag that flips true on
ArrowDown/ArrowUp and false on any input; Enter with cursor forwards
to the picker's own submit path via addressCursor("enter") — before
this fix, Enter always ran goURL against the typed letters, which
submitted them as a web search instead of opening the selected url.

Address bar reliably shows the picked URL. onAddressPicked now arms
an overrideUrlBarUntil = now+1500ms flag; the onTabs handler treats
that window as "force write the url", bypassing the focus-guard that
was leaving the bar blank when blur() hadn't landed yet.

Deployed: scp + sia-upload of both trees, verified LIVE 0.3.4.
2026-09-02 04:28:54 +02:00
Local Dev
3bf41f92ae Ship Theseus 0.3.3 e016d665 (fix tab menu clip + always-on bookmarks + real tab groups)
Setup    e016d66543976b7a4df5ac16033319d59cb0f418e54d7e838ef16de274525a83
Portable b9881a62c39ecfe22644611f5aa16a48f32f97f9ec5751b5888ccbd870338c3e

Three 0.3.2 misses fixed:

Tab context menu was clipped by the chrome view's fixed height — the
menu rendered with position:fixed inside a WebContentsView whose
bottom edge stops at CHROME_H, so items below the tab strip weren't
visible. New growChromeForMenu() measures every open menu's bounding
box and calls T.setChromeHeight(needed) so main.js grows the chrome
view to fit; closeAllMenus() calls syncHeight() to restore.

Bookmarks bar was hidden on every tab except Home, so Add-to-
Bookmarks had no on-screen feedback. Now always visible.

Tab groups now actually cluster. Assigning a color to a tab moves it
adjacent to its group-mates in the tabs array (main-side splice, no
render-only trick). The chrome renderer inserts a colored group chip
before the first tab of each group; clicking the chip toggles a
per-group collapsed state (in-memory, resets on relaunch). Collapsed
groups hide their member tabs and the chip shows the member count.
Expanded groups keep the per-tab top color stripe from 0.3.2 so the
cluster reads as one visual block.

Deployed: scp + sia-upload, verified LIVE 0.3.3 on dl.silentmode.st.
2026-08-31 21:29:14 +02:00
Local Dev
5ea4515085 Ship Theseus 0.3.2 09331b2f (tab context menu + branded installer)
Setup    09331b2fd9ccf136e2183b7cd85354cfd56e2ed50260b7aadeed63c7ea450251
Portable 21752d0fc85fb39ec1e65192920461e9ae395a22d9a68abd27f12e638d0fdd07

Right-click a tab: floating context menu with Reload, Duplicate, Group
(submenu: None / Red / Orange / Yellow / Green / Cyan / Blue / Purple),
Add to Bookmarks, Mute (also Unmute; 🔇 shows next to the title when
muted), Close. Menus close on outside click or Escape.

Group state is per-tab. A grouped tab shows a colored dot before the
title and a matching 2-px accent stripe on the top edge, so a cluster
of same-group tabs reads visually. Palette is drawn from existing
provenance colors (err/warn/acid/srv/sia/blue).

Backend IPCs are all tab-scoped (not "active tab"): tab-reload,
tab-duplicate, tab-mute (toggle or explicit boolean), tab-group,
tab-bookmark. emitTabs payload gains muted, group, and url so the
menu can read current state.

Installer wizard branding: 164×314 sidebar BMP with the compass mark
centered + "Theseus / NAVIGATOR" wordmark under it, plus a 150×57
top-strip header with a mini compass on the right. Sharp can't write
BMP directly (only png/webp/etc), so nsis/make-icons.mjs renders raw
RGB via sharp and wraps it in a hand-rolled 24-bit uncompressed BMP
header. Uninstaller reuses the same sidebar.

Silent-install fix: nsis/installer.nsh's AriadnePageCreate now checks
IfSilent BEFORE touching nsDialogs::Create. In /S mode the flag is
zeroed and the function returns cleanly, so the installer no longer
hangs waiting for a page it will never draw. This is why 0.3.2 needed
two builds — the first hung on /S install; the fixed hash is the one
that ships.

Deployed: scp + sia-upload of both trees. Verified VPS hash matches
local 09331b2f. Fresh /S install to D:\Program Files\Theseus Navigator\
placed 0.3.2 with the correct HKCU Uninstall registry entry.
2026-08-31 19:30:30 +02:00
Local Dev
19ffde3bfa Ship Theseus 0.3.1 fe59105d (one-click updates: silent prefetch + install-and-restart)
Setup    fe59105d2e99a41b7000caeb86601a8e1675846d193e92204034669f5b368d60
Portable 1b6eda55b53894cf9889548116c7b6100888fb160edc84cc1592bb79f9d95b53

The update flow no longer asks the user to click Download. When
checkForUpdate detects a newer version, autoDownloadUpdate() kicks off
session.defaultSession.downloadURL against the setup URL immediately.
will-download recognises the update URL and routes the file to a
fixed %TEMP% path (bypassing the visible downloads panel entirely),
streams updateDownloadReceived/Total into the chip via
emitUpdateAvailable, and flips updateDownloadState to "ready" when
the transfer finishes.

Chip states:
  idle         first render before the fetch starts — clickable to
               trigger the manual download (kept as a fallback).
  downloading  "↓ 42% — 0.3.2" — no click, just progress.
  ready        "✓ Install 0.3.2 & restart" — one click.
  failed       fall back to the pre-0.3.1 explicit-download click.

install-update-now IPC: spawns the cached setup with /S (detached,
stdio ignored), then app.quit() 400ms later so the installer can
overwrite the running exe. Our nsis/installer.nsh detects an existing
Ariadne install via the HKLM registry and skips its Ariadne prompt on
upgrades, so the /S run is fully unattended.

The one-click flow eliminates two long-standing sources of confusion:
  - "Download opens a different browser" — Theseus's default session
    fetches the installer itself, not a URL handoff to shell.
  - "Update requires multiple wizard clicks" — /S skips them.

Extensions aren't touched by this. The framework lives in
addons-host.js + sidebar-preload.js; add-ons themselves live in
%APPDATA%\Theseus Navigator\addons\<id>\ and are a separate layer.
New extensions ship by drop-a-folder, no browser release required.

Deployed: scp + sia-upload, verified 200 + 0.3.1 in the manifest.
2026-08-31 18:47:50 +02:00
Local Dev
5d248541cf Ship Theseus 0.3.0 bb9d8dec (home cards decoupled from browser release)
Setup    bb9d8dec124b8e2a976642ed77051b04edd1d03185581f99db845ef168919836
Portable 1e2d02aea13656574c374440b0c41374290f1bbf85ac6313cec075292cfc34f8

Home cards decoupled from the installer. Theseus now fetches
https://dl.silentmode.st/home-cards.json at boot and every 6h, caches
into <userData>/home-cards-remote.json, and applies it as the default
tier. Priority in loadHomeCards() is:
  1. <userData>/home-cards.json  (user edits — unchanged, always win)
  2. <userData>/home-cards-remote.json  (last successful fetch)
  3. DEFAULT_HOME_CARDS  (code fallback for offline first-boot)
On a successful refresh, main pushes the new list to every home tab
via the home-cards IPC — but only when the user has NO local edits.
The user's edit mode remains authoritative.

Content updates now happen by editing site/home-cards.json and running
scp + sia-upload. No browser build required.

Prometheus.X copy fix: "Decentralized App Marketplace" (was
"marketplace for BCDN-hosted apps" — the marketplace isn't BCDN-
specific). Applied to both DEFAULT_HOME_CARDS (fallback) and
site/home-cards.json (canonical live copy).

Deployed:
- scp installers + manifest + tools/ + releases/ + home-cards.json
  to /opt/silent-mode/dl/ and /opt/silent-mode/site/
- sia-upload of both site trees
- verified home-cards.json served 200, manifest 0.3.0 live

Existing installs of 0.2.5 will surface 0.3.0 in the update chip and
also pull the new home-cards.json — so the "Decentralized App
Marketplace" wording lands on ANY current install (0.3.0+) the moment
this commit deploys, without waiting for anyone to install 0.3.0.

Wait — 0.3.0 is what CARRIES the fetch logic. So the auto-pull benefit
only kicks in from 0.3.0 onward. Users on 0.2.5 or earlier need to
install 0.3.0 once; from then on every home-card edit reaches them
without a new browser release.
2026-08-31 18:38:09 +02:00
Local Dev
62d32573cf Ship Theseus 0.2.5 50be20d6 (home page brand pass: .X acid + role badges)
Setup    50be20d6e3811445817e35c560dce1a4bbcc62004d6b0480848a211a654319ea
Portable 021afe629c61f2f49e95f936261649bdcff58c8b79bd0d903a8d10e6009740b0

Home page card grid gets a brand pass. Each title's TLD suffix (.X,
.bch, ...) is now styled in acid (--acid) with a soft text-shadow
glow. styledTitle() splits the domain on the last dot so capital
labels like "Theseus.X" render as ink + acid.

Card copy tightened per name:
  hello.bch         A small page on the blockchain itself.
  siatest.bch       A page with no server, backed by Sia.
  SilentMode.X      Infrastructure development for a decentralized web.
  Theseus.X         The Web Navigator - this browser's own address.
  Sirius.X          Register and manage BCDN names.
  Hephaestus.X      The forge - Silent Mode's code host.
  Prometheus.X      An app store - a marketplace for BCDN-hosted apps.
  Helios.X          Search engine for the decentralized web (in design).
  Hermes.X          Messaging - end-to-end encrypted over Nostr.

Badges: dropped the generic ".x" tag; each .X card carries its role
label instead. Palette assigns each role a distinct hue drawn from
the existing provenance colors, so the grid reads as families without
adding new brand values:
  Infrastructure / Registrar  acid
  Navigator                   blue
  Code host                   sia purple
  App store                   srv teal
  Search                      warn amber
  Messaging                   err coral

Descriptions brighten from --mut to --ink on card hover so the ink
color reads as the "acid touch" the user asked for without adding
literal green blocks to every card.

Deployed: scp installers + manifest + tools/ + releases/ pages to VPS,
sia-upload of both trees, verified HEAD 200 + manifest 0.2.5.
2026-08-31 18:03:02 +02:00
Local Dev
454255e963 Ship Theseus 0.2.4 7fd323a8 (home cards refresh + engine-picker sync + proxy auth support)
Setup    7fd323a87bd32b780e147de18e16ecd82f89960bbd8e9a619c5d25d374597cd2
Portable 3166e64cf56badd7b26c4c793dc79bbed6f9d6c48fbd467d97f845385b91b1dd

Home cards: DEFAULT_HOME_CARDS replaced with the .x sibling grid the
user asked for -- hello.bch, siatest.bch (the "types of BCDN" pair),
then silentmode.x / theseus.x / sirius.x / hephaestus.x /
prometheus.x / helios.x / hermes.x. Existing installs with a saved
home-cards.json keep their edits (defaults only seed fresh profiles).

Search engine picker sync: user reported the toolbar dropdown listed
engines as active that Settings > Search showed differently. Root
cause: settings.searchEngine could be pointing at an id not in the
currently-enabled set (stale settings.json after DEFAULT_ENABLED
changes across versions). loadSettings now normalizes on boot -- if
searchEngine isn't enabled, fall back to enabled[0]; and
installedEngines gets unioned with enabledEngines so the two lists
can't disagree in ways that make toolbar and Settings render
different rows.

Proxy auth support in the framework: setSessionProxy accepts
`{ proxyRules, auth: { username, password } }` or an inline
`socks5://user:pass@host:port` URL. When creds are present, the
handler strips them from the URL, installs a session#login listener
on the default session that answers with them, then calls setProxy.
Chromium's SOCKS5 client doesn't consume proxy auth (known Chromium
limitation), but HTTP proxies work; SOCKS-based extensions need to
gate by IP allowlist at their server. Log line masks the password.

Update chip note: the "download opens in a different browser" was
0.2.0-era behavior. 0.2.1 rewired it to session.downloadURL. Anyone
still seeing it needs to install 0.2.1+ once.

Deployed: scp + sia-upload both trees, verified HEAD 200 + manifest
0.2.4 live.
2026-08-31 17:24:39 +02:00
Local Dev
49dde73c6b Ship Theseus 0.2.3 3abaeff7 (new brand icon + multi-panel picker + Startpage default)
Setup    3abaeff73afcecc9e4f05f749765e168a97a377890211dbae4345654c84ce2f1
Portable 71017563d6ba107cb25e24be240abcb78e34491d8f7aa42e095421edbc482a85

Brand: the red-N compass from theseus.x is now the taskbar / titlebar /
File Explorer icon everywhere. Source SVG lives at
site-theseus-x/assets/favicon.svg so brand + browser icon stay in sync.
nsis/make-icons.mjs renders it to build/icon.png (512x512) and
build/icon.ico (multi-resolution: 16/24/32/48/64/128/256). Wired into
package.json: build.win.icon, build.nsis.installerIcon + uninstaller
+ header; the ico ships as an extraResource so main.js's BrowserWindow
also uses it at runtime. Icons live in build/ which is gitignored -
run `node nsis/make-icons.mjs` when the SVG changes.

Sidebar: multi-panel picker strip. When 2+ extensions register sidebar
panels, sidebar-preload.js injects a 32-px tab strip at the top of
every panel's document. Click a tab -> ipcRenderer sidebar-open ->
loadFile switch. Preload also injects box-sizing:border-box + a
33-px body padding so height:100% panels don't overflow. Solo-panel
case is unchanged (strip only appears when panels.length >= 2).

Search: Startpage is the new default. Both the default id and the
enabled-list ordering put it first. DDG stays enabled by default too.

Deploy: scp installers + manifest + tools/ + releases/ to VPS,
sia-upload of both trees, verified HEAD 200 and manifest 0.2.3.

Icons regenerated from theseus.x's favicon.svg; the .svg itself
shipped in the 0.1.x window when the theseus.x site went live.
2026-08-31 16:32:16 +02:00
Local Dev
ee0548fec9 Ship Theseus 0.2.2 972f6209 (Extensions rename + draggable sidebar + session-proxy)
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.
2026-08-31 16:00:34 +02:00
Local Dev
1a6b631d98 Ship Theseus 0.2.1 67e3cda1 (in-app update download + Ctrl+B sidebar)
Setup    67e3cda1b76a6dba966d91a43e78fdb52378f860c0f75a7775d11b96e1b4f5e3
Portable 415c804f75df6fab5072650fbdc3cffa4124ed02f8ff53a91281ebd2dc4775c3

Update chip: the Download button used to hand the URL to the system
browser (shell.openExternal) - user opens Theseus, gets prompted to
update, clicks Download, watches ANOTHER browser wake up and start
the transfer. That's the "why did a different browser open?" report.
Fix: session.defaultSession.downloadURL(url) - triggers the same
will-download handler our own downloads panel already listens on, so
the file lands in the user's Downloads folder AND appears in
Theseus's downloads chip with progress + Show-in-folder. Rejected
protocols unchanged; only dl.silentmode.st and silentmode.st URLs
survive the allowlist.

Ctrl+B toggles the add-on sidebar (matches VS Code). Verified end-
to-end against a fresh <userData>: bundled Notepad was seeded,
Ctrl+B opened its panel, typed text autosaved to
<userData>/addons-data/notepad.json, restart re-mounted and the
text loaded back before the append could clobber it.

Deployed: scp installers + manifest + tools/ + releases/ to VPS,
sia-upload of both trees, verified HEAD 200 and manifest 0.2.1.
2026-08-31 15:35:47 +02:00
Local Dev
0117986657 Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.

Files:
- addons-host.js         Loader + api.registerSidebarPanel() + per-
                         addon storage on <userData>/addons-data/.
                         Kept at the CommonJS-scoped top level (lib/
                         is ESM-scoped via its own package.json).
- sidebar-preload.js     Runs in every sidebar panel. Exposes
                         window.silentmode.storage.{get,set,all} +
                         onVisibility. Main-side handlers derive the
                         add-on id from the sender file:// URL, so a
                         panel can only touch its own store.
- bundled-addons/notepad/  Reference add-on: addon.json, index.js,
                         note.html. Autosaving textarea with char /
                         word count.

main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
  (SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
  views by the sidebar width when visible. First registered panel
  wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
  AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
  sidebar-state, addons-list / addons-set-enabled / addons-reveal /
  addons-open-dir / addons-reload, and origin-gated
  addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
  the loader honours them without a restart (discoverAndActivate
  runs again on toggle).

chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.

settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.

package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.

Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.

Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
Local Dev
e43e009f73 Theseus 0.1.3: branded error page for load failures (BUILT, NOT DEPLOYED)
Setup    f2afc14efc63008cbb9dad44176e94146386db4c0afda4459f1d4eb929172b6d
Portable 5d08b1415526934db8de780949a610896064fe9567aa0e5e1702ebabd7eb7df2

Chromium's default 'This site can't be reached' replaced with a Theseus-
themed error page. did-fail-load on every tab's webContents (main frame
only, non-ignorable code) routes the tab to error.html with the
attempt URL, host, error code, and description as query params. The
page keeps t.url pointing at the failed URL so the address bar shows
what the user typed and they can edit + retry - refreshTabUrl's
existing file:// skip means the error page's own path never leaks
back into the bar.

Five kinds, chosen by pickErrorKind(code, host):
  name-not-registered  BCNR-eligible host + ERR_NAME_NOT_RESOLVED.
                       Says "no BCDN record on chain, no clearnet host
                       either." Offers Register on Sirius + Search +
                       Retry + Home.
  name-unreachable     ERR_NAME_NOT_RESOLVED on a non-BCNR host. DNS
                       failed - offers Retry + Search + Register +
                       Home.
  unreachable          CONN_REFUSED/RESET/TIMED_OUT/CLOSED/NETWORK_CHANGED.
                       Offers Retry + Tor guide + Home.
  tls                  ERR_CERT_* range (-200..-299). Offers Retry +
                       Home.
  generic              Everything else.

home-preload.js gains `window.errorpage` alongside `window.home`. Both
APIs are sender-URL-gated in main - a random page seeing the shape
can't invoke them (isErrorPageSender / isHomePageSender). The external-
open handler additionally allowlists Silent Mode domains only.

package.json build.files gets error.html + error-preload.js so
electron-builder actually bundles them (GOTCHAS rule: an unlisted
runtime-loaded file silently opens blank).

Ship pages (releases-manifest.json, tools/index.html, releases/index.html,
site-theseus-x/index.html) updated to 0.1.3 with the new hashes.

DEPLOY STATUS - blocked on VPS SSH: my IP was hit with a full-port ban
mid-turn (likely fail2ban from the burst of scp during the 0.1.0-0.1.2
iterations). Site pages/manifest/installers are committed locally but
NOT yet on dl.silentmode.st or the Sia mirror. Live still reads 0.1.2.
User needs to unban 195.184.247.106 on their end, or wait for the ban
to expire, before the ship pages match reality.
2026-08-31 13:38:05 +02:00
Local Dev
6f4531cda0 Theseus: don't intercept same-origin navigations (unbreaks Startpage POST)
Startpage's in-page search form POSTs to /do/search. Our will-navigate
rewriter was calling navigateTab(id, url) which loads via loadURL(url)
- always a GET, no body. So the second search from within Startpage
always landed on /do/search with no query and no results.

Google's search form GETs, so 0.0.5's query-preservation fix was
enough for it. DDG uses history.pushState and never fires
will-navigate at all. Startpage POSTs, so it needs a different fix:
just don't intercept.

Only intercept CROSS-ORIGIN navigations for BCNR re-lookup. A form
submit or subpage link on the current site (same hostname) belongs to
Chromium natively - the site is already loaded from clearnet, its
subsequent navigation stays on clearnet. Preserves POST bodies for
every site, not just Startpage: any login form, comment submit, or
checkout flow that stayed on-origin was silently broken by the same
bug.
2026-08-31 05:13:20 +02:00
Local Dev
10cddcacca Theseus: address-picker URL bar + reload keys (F5 / Ctrl+R / Ctrl+Shift+R)
Address-picker fix:
Picking a suggestion from the address dropdown loaded the URL but
left the address bar showing the 3-4 letters the user had typed.
Root cause: onTabs's focus guard
  if (document.activeElement !== $("url")) $("url").value = d.url
skipped its write while the URL input still held DOM focus, and
clicking a sibling WebContentsView doesn't always deliver the blur
to the chrome renderer in time. Fix: address-pick sends an explicit
address-picked IPC to the chrome, which force-blurs and writes the
full picked URL before the tabs event arrives.

Reload keys:
None of the standard reload accelerators worked (Menu.setApplicationMenu(null)
drops Chromium's default menu accelerators, and hard-reload was never
wired at any layer). Now:
  F5 / Ctrl+R         -> soft reload
  Ctrl+F5 / Ctrl+Shift+R -> hard reload (reloadIgnoringCache)
  Shift-click on the toolbar reload button -> hard reload
Same before-input-event hook that carries Ctrl+Shift+M; always targets
the active tab regardless of which view got the key, and skips the
Settings tab. Toolbar tooltip updated to advertise Shift-click.
2026-08-31 02:50:57 +02:00
Local Dev
755e97b5bb Ship Theseus 0.0.8: window.bcnr dApp API + eTLD+1 permission origins
Merges a parallel session's work with the multi-source BNS story from 0.0.7.

The dApp side (parallel session)
--------------------------------
* bcnr-preload.js — installs `window.bcnr` on every page via contextBridge.
  Read-only surface: resolveName(name), isRegistered(name), getBcnrTlds(),
  getRecordVersion(name), plus getPermissionOrigin() for diagnostics. All
  Promises; a missing name returns null (not throw). No signing, no wallet
  unlock — that surface is designed but deliberately out of scope for 0.0.8
  (see TheseusNavigator/DESIGN-integrated-wallet.md).
* bcnr-origin.js — pure function that computes the eTLD+1 permission origin
  for a URL. ICANN suffixes via `psl` (same PSL Chromium uses, handles
  .co.uk / .github.io / etc); BNS names key off the on-chain TLD list so
  foo.wallet becomes a public suffix as soon as `wallet` appears there.
  Match browser cookie / MetaMask semantics: a grant on pay.merchant.com
  covers account.merchant.com but not evil.com.
* dev/bcnr-selftest.js, dev/origin-selftest.mjs — self-tests, no I/O.
* main.js wires bcnr-preload.js into session.defaultSession.setPreloads() so
  it runs BEFORE per-WebContentsView preloads; adds bcnr:* IPC handlers.
* preload.js + chrome.html — small hooks so the shell picks up window.bcnr
  the same way regular content does.
* package.json — psl dep, bcnr-preload.js/bcnr-origin.js in `files`.

Also included
-------------
* AriadneResolver/mobile/.../UpdateCheck.java — in-app update-check for the
  Android app; already active in the shipped 0.11 APK (build.ps1 -Recurse
  picked it up), formalising the source now.
* TheseusNavigator/snapshots/bns-name-snapshot.json — refreshed bundled
  starter (73 beacon txs, root c37b8596…c54e414ba).
* Site pages + manifest updated to point at 0.0.8.

  TheseusNavigator-Setup-0.0.8.exe    95.4 MB
    21939743eafdfe8742a6b7c4b987bd2782384d7bc41289cb80a7e08019dc9f02
  TheseusNavigator-0.0.8-portable.exe 92.7 MB
    2aa429fe39dc0fa4ac040fc6d6eb31b0f890c8a83175c49fcb50f052c480d39d
2026-08-31 01:38:55 +02:00