Two reasons the Plug-ins card looked dead ("only a Refresh button"):
1. The state check ran Get-ScheduledTask, whose module import took 8–10 s
cold, and only then fetched the release manifest. Every button is hidden
during "checking…", so for 10–15 s the card showed nothing but Refresh.
Task state now comes from the Task Scheduler COM object (numeric, locale-
independent — schtasks.exe prints localized words on non-English
Windows) and the manifest fetch runs in parallel: ~2 s.
2. The Inno installer's AppId is written as {{…}}, which Inno registers as
{…}}_is1 (doubled closing brace). Theseus looked for the single-brace
key, never found it, and so never knew the installed version — no Update
button, no Uninstall button. The entry is now found by DisplayName.
Owners can now publish a signed _records.json (A/AAAA/MX/TXT/CNAME/NS)
beside their Sia content; the gateway verifies it against the current NFT
holder and serves it as GET /api/dns/<name>. Every BCDN resolution now
starts a background fetch of that answer (3 s cap, 30 s cache, seq rollback
guard) and attaches it to the entry as entry.dns. Navigation never waits
for it — on-chain h/s3/ip/p/u stay authoritative — except when a name has
no content record at all and a signed A is the only way to reach it. Only
registered names are looked up, so ICANN hosts never reach the gateway.
Exposed as window.bcnr.dnsRecords(name) for add-ons (TXT verification, MX
for mail bridges), on resolveName() as .dns, and as a "Signed DNS" row in
the site-info popover.
df181d9 and b2c6f62 were staged hunk-by-hunk from a working tree that also
carried unrelated uncommitted edits, and the context-free hunks landed a
few lines off: the local-file check ran after the search rewrite (so paths
still went to the search engine in the committed file), the loadBns header
sat inside loadLocalFile's comment, and the refreshTabUrl comment was split
by the auth block. Content is unchanged; only placement is corrected.
Sites behind Basic/Digest auth (silentmode.st/guardian/admin) rendered the
server's 401 page because nothing listened for Electron's login event,
which cancels every challenge by default. A modal sign-in prompt now asks
for the credentials and answers the challenge; Cancel leaves the 401 page.
Concurrent challenges for the same host and realm share one prompt while it
is open, and a rejected answer re-prompts instead of replaying the same
credentials until Chromium gives up with ERR_TOO_MANY_RETRIES.
Also: THESEUS_NO_UPDATE_CHECK skips the release check, for throwaway dev
instances — the one-click install chip they show targets the real install.
A typed or pasted path such as D:\Dev\x\page.html has no dotted host, so
the URL-vs-search heuristic handed it to the search engine. Paths (drive,
UNC, file://, and absolute/~ on POSIX) now load as file:// URLs before the
heuristic runs. Local-file tabs keep their file:// URL in the address bar
(normally suppressed because our own home/error pages are file://), show a
"Local file" badge, and hide the registry button since no name resolution
is involved. A missing file lands on the error page with a matching badge.
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".
seedBundledAddons reseeded whenever the user copy's version differed from
the bundled one. promoteStagedUpdates runs just before it, so a signed
over-the-air update that had just been promoted (e.g. Aegis 0.6.14 over
the bundled 0.6.2) was backed up and replaced by the older bundle on the
same boot — every OTA add-on update silently reverted at the next launch.
Reseed now only when the bundle is newer, using the same version compare
the promoter uses.
A bns:// fetch that fails after the name resolved (relay unreachable, DNS
stalling, the site's own server down) used to answer with the bare text
"Theseus error: fetch failed", which reads as a broken browser. The
handler now returns a styled page that names the host, the upstream it
tried (navigate.st, the p-record origin or the ip record), the error and
its cause code, explains the likely reason per cause (unreachable vs DNS),
and offers a retry.
A 0.3.44 → 0.3.45 auto-update on 2026-09-11 left the install without
app.asar and ffmpeg.dll ("ffmpeg.dll not found" at launch). The setup was
hash-verified; the old-version uninstaller had moved the whole old install
into its temp folder when both NSIS processes died ~8 s after the spawn,
and the install step never wrote a file. The killer was not identified, so
every overlap with the app's own lifetime is removed instead:
- install-update-now no longer spawns the setup; it records the path and
quits. will-quit writes <userData>\update-helper.cmd and starts it as a
detached cmd.exe (verified to outlive the app; not a child of ours).
- The helper waits for our PID to be gone (child powershell Wait-Process),
gives Chromium's children a grace period, runs the setup directly, and
runs it once more if resources\app.asar is missing afterwards — the
installer is idempotent, so a second pass repairs a torn install. The
helper deletes itself.
- Zone.Identifier is stripped from the verified download so nothing that
starts it through the shell raises a mark-of-the-web prompt.
Console-less cmd.exe traps discovered and designed around (see the module):
child console programs' redirected stdout is empty (no tasklist|find
probing), `start /wait` on a .cmd hangs, a detached powershell.exe
started straight from Node does nothing, `timeout` needs a console.
Scenario tests: setup starts only after the process exits, once with
app.asar present, twice without, helper gone afterwards.
A standalone page window on the same session (cookies, bns:// protocol,
session-wide bcnr preload) with Theseus's fingerprint + WebRTC policy and
no toolbar. Loads BCNR-first like a tab: a dotted host with a BCNR record
goes over bns://, otherwise clearnet; collision names follow the configured
policy without the "Open with…" interstitial. Cross-host navigations inside
the window stay BCNR-first; popups go to the main window's tabs. Its own
context menu offers open-in-tab / open-in-window / copy link and
back/forward/reload. Add-on page bridges (wallet inject) are tab-scoped and
don't run in these windows. openLinkWindow is exported for the test harness.
Verified in the dev app: coinspectrum.x opened as bns://coinspectrum.x with
the page title; navigate.st stayed https.
The publishing steps pointed at scp to /opt/silent-mode/site/addons/, which
never existed; the screenshot channel (and now aegis) is served from
s3://bns/theseus/extensions/<id>/ through navigate.st/bns/theseus.x/….
Also: sign from a git-archive copy so uncommitted edits don't ship.
Theseus (Settings › Plug-ins › Ariadne's Thread):
- The elevated start/stop script was embedded in a double-quoted outer
PowerShell string, so `$t` was interpolated away before the elevated
shell saw it. It received `foreach ( in …)`, failed to parse, and the
outer shell still exited 0 — "Turn on/off" reported success while doing
nothing, in every shipped build. The script now goes across as
-EncodedCommand. Off = Stop + Disable (the daemon task has an
at-startup trigger, so a plain stop came back on reboot); on = Enable +
Start. Exit 2 = daemon task missing, surfaced as a clear error.
- Version/update check now reads dl.silentmode.st's releases manifest,
the same one the Theseus updater uses. The silentmode.st copy lagged a
day behind (still listing Theseus 0.3.31), so a new Ariadne release
published to dl would not have been offered.
- Install/update/uninstall now propagate the installer's exit code
(-PassThru; exit $p.ExitCode) instead of always reading as success.
Resolver package (needs a new installer build to reach users):
- uninstall.ps1 removed only the ".bch" NRPT rule; install.ps1 adds one
per advertised TLD. Sweep every "BNS .<tld> resolver" rule.
Verified: daemon resolves BNS names and passes ICANN A/AAAA through when
run unprivileged on port 15353; the encoded-command construction runs
intact and propagates exit codes 0/2 in an unelevated reproduction.
- Per-tab page zoom on Chrome's ladder (25–500 %) via setZoomFactor, so
Chromium keys it per host: every tab on a site shares the level and it
persists across navigations and restarts. Ctrl +/=/numpad+ in,
Ctrl -/numpad- out, Ctrl 0 reset, Ctrl+wheel via zoom-changed. A
percentage chip appears in the address bar when a tab isn't at 100 %;
clicking it resets. Settings and add-on tabs never zoom.
- Address bar / search bar drag ratio floor lowered from 30 % to 20 %,
so the split runs 80/20 to 20/80 (pixel floors still apply).
- The collapsed extension-dock button and its dropdown printed a data:
URI icon as text ("data:image/svg+xml…"). One addonIconHtml() renderer
now serves the dock buttons, the collapsed button and the dropdown.
Rolling every user report from the 0.6.3 rollout into one bundle:
Sounds — the Web-Audio synth palette matches the metaphor now:
- Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click
chained to a film-advance whir (band-passed noise sweeping 900→400 Hz).
- Copy: printer "chika-chika-chika" — three descending percussive noise
bursts pinned by short sine ticks. Reads as a print-head sweep.
- Discard: paper crumple — three overlapping band-limited noise beds
with per-sample random-amplitude crackle, descending centre freq. No
more descending sine "boop".
- Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click.
- Both the panel and editor share the design so nothing sounds different
depending on which surface fired it.
UI polish:
- Discard button now carries a trash-can icon so it's obviously not the
same as the close-sidebar X (they both used to be plain X's).
- Toolbar drawing tools centre themselves via a new .tool-cluster
wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions
stay right-anchored via margin-left:auto on their own tgroup. Fixes
the maximized-sidebar case where the drawing groups all crowded the
left with a big empty gap before Copy/Save on the right.
- Filename moves out of the topbar into a dedicated footer strip under
the canvas board, alongside a new "Open in folder" button. The topbar
is now flex-wrap:nowrap and holds only fixed-width window controls,
so a long filename can never push discard / sound / max / close onto
a second row (the filename ellipsises instead).
- "Open in folder" invokes a new "openFolder" addon message that calls
Electron's shell.showItemInFolder() to open the OS file explorer with
the specific scratch PNG highlighted (falls back to shell.openPath()
on the scratch dir when no capture is named).
Version bump so the OTA update endpoint picks it up on the next tick.
Four issues from the user's report on 0.6.2:
- Recent captures had a global "clear all" but no way to drop a single
screenshot. Each tile now grows a small × button (visible on hover;
drops in behind the thumbnail preview so it never obstructs the
content). Clicking the × invokes clearRecent({name}) and removes both
the ring entry and the scratch PNG on disk. Bubble-guarded so the ×
click doesn't also trigger the tile's "load into preview" handler.
- Select tool button removed — clicking it did nothing visible, so users
read it as broken. The internal "select" mode still exists as the
no-tool state; you get back to it now by clicking the same drawing
tool a second time (toggle-off) or hitting Escape. The active-drawing-
tool button flips its border when armed.
- Text tool made unmistakable: input paints with a 2 px acid border, a
glowing acid halo, dark background, and the visible ink colour on the
text itself. Focus attempt is three-layered (sync, rAF, timer) to
outrun any Chromium build that drops the mid-pointer-event focus. Non-
Enter/Escape keys get stopPropagation so a stray document listener
can't steal the focus mid-typing.
- Panel header's sound / max / close cluster kept nudging inward when
the status text was empty. The parent's `justify-content: space-
between` distributed the row unevenly. Force-anchor the cluster with
`#btn-sound { margin-left: auto }` so the three window-control icons
hug the right edge regardless of what fills the middle.
Bundled but not shipped separately — parent session signs and pushes.
Layout reorganisation from user's diagram:
- Copy + Save move out of the topbar into the toolbar as their own
right-anchored tgroup (margin-left:auto). On wide sidebars they sit at
the end of the drawing-tool row; when the sidebar is narrow, the
actions cluster wraps as its own row on the right instead of nudging
the drawing tools around. Toolbar switches from justify-content:center
to flex-start so the leading tool groups pack left and the actions
group can find the right edge cleanly.
- Topbar right cluster is now Discard / Sound / Maximize / Close — Copy
and Save are gone from the topbar entirely so the right edge reads
as controls-only, not action-mixed.
- Discard button (X icon, danger red on hover) throws away the current
capture — silentmode.invoke("clearRecent", {name}) removes it from the
ring and unlinks the scratch file — then navigates back to the panel.
Distinct from Back, which is non-destructive.
- Close button already existed from 0.6.1 but stays in the same
right-edge position for continuity.
Bundled but not shipped separately — parent session signs and pushes.
- Link-status pill: it measured its own width inside a view already
capped at 100 px, so it could never grow and long hrefs were cut short.
An off-screen twin now reports the natural width; main caps it to the
tab area (never under the sidebar) and the pill ellipsises past that.
- Address bar at narrow widths: the URL input's intrinsic minimum width
pushed the registry chips and the star out past the bar. #url now has
min-width: 0 and the trailing controls are fixed-size flex items.
- The BCDN/ICANN segmented chips are replaced by one Ariadne's Thread
icon (spiral + tail) at the end of the bar: acid when served from BCDN,
blue for ICANN, caret when the name exists on both. Click opens a
native menu (registry-menu-popup): switch registry, remember per name /
per TLD, forget choices, collision policy, and a jump to the Plug-ins
settings section. Reuses the existing switch / remember / policy paths
(collision-switch body extracted to switchRegistry, open-settings to
openSettingsTab). preload's openSettings now forwards a section slug.
Users saw a blank window with a white strip across the top for seconds
on launch. Root cause: every part of startup, including session restore,
waited for chrome.html's did-finish-load. That event also waits for the
page's subresources, and the bookmarks bar loads its favicons over
bns:// — a BNS lookup plus a network fetch each — so a slow link held the
whole boot. On top of that, seven hidden overlay renderers, every restored
tab, the BNS index build and three network fetches all started in the
same tick and stalled the main thread ~1 s while the toolbar tried to
paint.
- Continue boot at chrome.html's dom-ready (toolbar scripts have run, IPC
listeners exist) instead of did-finish-load; 8 s fallback timer.
- Window and chrome view get the toolbar's --bg for the active theme so
the pre-paint frame is never white.
- Overlay pages (site info, engine picker, downloads, suggestions,
password fill, link status, approval) load 250 ms after the toolbar or
on first use; the approval modal awaits its page so a dapp request
can't hang.
- Session restore is staggered: active tab first, then one background
tab per 150 ms slotted into its saved strip position. Session file v2
records the active index; v1 arrays still load (active = last, as the
old loop effectively did).
- AddonHost gains api.whenUiReady(); Aegis 0.6.2 defers its heavy
dependency loading (noble precompute, bitcoinjs, libauth, WizardConnect)
behind it.
- BNS snapshot warm-up still starts right after createWindow (bookmark
favicons need it); Sia refresh, update check and home-card fetch move
to the post-paint phase.
Measured on a clone of the real profile with nine restored tabs: toolbar
usable at ~0.7 s instead of ~1.5 s, main-thread stall during toolbar load
down from ~1.1 s to ~0.2 s.
Four user reports from the 0.6.0 rollout:
- Text tool never committed. openTextInput placed the box correctly but
a couple of Chromium quirks stopped a normal type-Enter cycle:
focus() called synchronously right after appendChild lost the race
in some builds, and the input's own mousedown / click was bubbling
through to #base and re-firing openTextInput on every subsequent
keystroke click-through, so what looked like "nothing happens" was
actually "a new empty box spawned on top of the last one every time".
Now: focus after requestAnimationFrame, contain pointerdown / mousedown
/ click inside the input so they don't bubble to the canvas, track
the font size on the state so commit uses the same one openTextInput
measured against, and preventDefault on the base pointerdown so
Chromium doesn't reset focus back to <body>.
- Toolbar wrapped one dot at a time when the sidebar was narrow (a
lonely thin/medium/thick width would jump to a second row while the
swatches stayed above it). Toolbar items are now wrapped in
`<div class="tgroup">` per category — tools / swatches / widths /
undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit
and lands cleanly under the previous one. `gap: 10px / row-gap: 6px`
keeps the visual grouping obvious.
- No way to close the sidebar without hunting for the dock icon. Added
an X button in the top-right of both the sidebar panel and the
editor toolbar. Both wire through a new `silentmode.sidebar.close()`
preload method that calls the existing `sidebar-close` IPC.
- Tightened the pointerdown text branch so preventDefault + explicit
focus-after-frame make the click-through races impossible.
Bundled but not shipped separately — parent session signs and pushes.
The resize grip lived along the sidebar's left edge as a 5-px transparent
hover target — you couldn't see it existed until the pointer landed on it.
Users on both light and dark backgrounds reported the seam between the
tab area and the sidebar as invisible.
Paint a 2-px semi-opaque mid-gray line (`rgba(140,150,170,.55)`) at rest so
the boundary is legible on every panel background; hover ramps to acid
green, active-drag ramps brighter. The visible band is narrower than
before (2 px vs 5 px) so it reads as a subtle divider rather than
competing chrome; the pointer-catch zone stays wide via an invisible
outline extension, so drag-to-resize still catches slack.
scripts/push-split.sh pushes one monorepo subdirectory to its matching
split repo on Hephaestus via an ephemeral git-subtree-split branch, so
history is preserved on the forge side. The remote name is derived from
the directory (Navigator/Resolver suffix stripped) or passed explicitly.
TheseusNavigator/test-installer.wsb is a Windows Sandbox profile that
maps dist-public read-only and launches the setup exe on logon, for
clean-machine installer checks.
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.
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
Cloudflare Bot Fight Mode / Turnstile flag 'UA claims Chrome but client
hints don't confirm it' as bot. Electron's default sec-ch-ua reads
'Chromium';v='130', 'Not(A:Brand';v='99' — no 'Google Chrome' brand
(that's closed-source Google branding open Chromium doesn't carry).
Combined with a UA that's already stripped of the Electron token
(stockChromeUA), the mismatch itself is the fingerprint. This is what
whybitcoincash.com and other CF-fronted sites tripped on: server
returned 503 to Theseus while returning 200 to any curl variant.
Brave, Vivaldi and Opera solved this the same way — ship their own
sec-ch-ua that INCLUDES Chrome-family brands so CF's allow-list catches
them. New applyClientHintsSpoof() registers a session-wide
onBeforeSendHeaders that rewrites the sec-ch-ua family on every
outbound request:
sec-ch-ua: 'Google Chrome';v=<major>, 'Chromium';v=<major>, 'Not?A_Brand';v='99'
sec-ch-ua-full-version-list: same trio with real Chromium version
sec-ch-ua-mobile: '?0'
sec-ch-ua-platform: actual OS name (Windows / macOS / Linux)
Major comes from process.versions.chrome so the story stays internally
consistent — nothing to fingerprint from a Chrome/version mismatch.
Runs alongside applyEmbedCookieShim which uses onHeadersReceived; the
two hooks are separate so no listener collision.
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.
Two follow-ups from user reports on the 0.5.x screenshot rollout:
- When the screenshot editor sidebar is maximized (fills the window) and
the user hits New Tab / Settings / any address-bar nav that opens a
different tab, the incoming tab was left invisible behind the sidebar.
setActive now auto-restores the sidebar to its pre-max width whenever
it detects a tab switch — the user can hit the maximize button again
on the way back. Pure additive change, no other setActive semantics
touched.
- The signed-add-on update pipeline failed the 0.5.0 tarball extract on
a Windows 10 install with the built-in bsdtar: `tar --force-local -x
-z -f …` — bsdtar doesn't recognise --force-local and errors out
before it opens the archive. Try the extraction WITHOUT the flag
first (safe with the posix-slash paths we already pass on every tar
we care about — bsdtar, GNU tar, MSYS2 tar) and fall back to WITH
--force-local only if the first invocation exits non-zero (MSYS2
path where a bare `C:/…` gets parsed as a `host:` prefix). Original
error message is surfaced on total failure so we can still tell what
went wrong.
Two things the shipped 0.5.0 got wrong:
- `.empty { display: flex }` overrode the plain `[hidden]` attribute the
init flow sets after the image draws, so the "Loading capture…" pill
stayed visible on top of the finished capture. Global rule
`[hidden] { display: none !important }` takes it out.
- Tool bar was left-aligned; older editor iterations grouped the drawing
tools / swatches / widths / undo-redo in the centre of the bar, which
read better in a narrow sidebar. Adds `.toolbar { justify-content: center }`;
the topbar's back / max / sound / name / save / copy stay edge-anchored.
Version bump so the OTA update endpoint picks it up on the next tick.
Toolbar drag handle now clamps both bars to at least 30 % of the
.urlsearch budget (URL: 30 %–70 %, search fills the rest). The
existing absolute mins (URL 220 px, search bumped from 140 → 180 px
so the 'Search' placeholder always fits) still apply — the tighter of
absolute vs 30 %-of-container wins at any width. .urlsearch also gets
margin-right: 10 px so the search bar has visible breathing room from
the trailing dock (Downloads / extensions / ⛓ Theseus).
Bookmark brand-case now uses a canonical map for multi-word Silent Mode
names so all-caps sources come out correctly cased: SILENTMODE.X →
SilentMode.X, silentmode.x → SilentMode.X, coinspectrum.x →
CoinSpectrum.X. Single-word brands (Theseus, Sirius, Deviant, Aegis,
Ariadne, Argus, Hermes, Prometheus, Hephaestus, Helios, Atlas,
Katalogos, Game, Poutakidis, Syskypo) are in the same map for
consistency. Unknown names fall back to Title-case (foo.x → Foo.X)
— the ALL-CAPS preserve rule is gone, so GAME.X → Game.X now,
matching the user's ask.
Bookmark chip labels now normalise the .x TLD family to <Name>.X on
render:
theseus.x -> Theseus.X
deviant.x -> Deviant.X
Sirius.x -> Sirius.X
foo-bar.x -> Foo-bar.X
Names that were already ALL-CAPS keep their form so the visual weight
carries through:
GAME.X -> GAME.X (unchanged)
SILENTMODE.X -> SILENTMODE.X (unchanged)
Non-.x titles are untouched (CoinSpectrum, navigate.st, etc.). The
transformation runs after the descriptor trim, so titles like
'theseus.x — the browser…' also come out 'Theseus.X'.
Amends DESIGN-integrated-wallet.md §0 ("one account per profile in v1") with
a formal path to supporting external key imports (BIP39 seeds and raw WIFs)
alongside the primary HD purpose-subtree. Motivated by the user's 15-wallet
Deviant chipnet keystore now encrypted under the same master-password model
that Theseus is designed around.
Key discipline:
- SEPARATE storage file (wallet-imports.enc), same PBKDF2/AES-256-GCM crypto,
same master password, distinct KDF salt. Imports cannot compromise the
primary seed and vice versa; corruption of one file does not damage the
other.
- The imports schema mirrors Deviant's chipnet-keystore/1 shape (cashaddr /
label / category / source / path / seed / wif) so import is a 1:1 field
copy, not a translation.
- Every import stores the raw seed + BIP44 path (or the raw WIF) and derives
addresses DIRECTLY via HDKey.fromMasterSeed, bypassing the vault's
purposes/wallet HKDF subtree. This is essential: without the bypass,
pasting a mnemonic re-hashes the seed through HKDF and produces DIFFERENT
addresses than the source wallet (see memory
bchwallet-vault-root-derivation).
- No bcnr.importWallet() ever; imports are settings-page only.
- Sign modal grows an account picker with category chips; per-origin
"recently used" pre-selection with red-flag on category change.
Also introduces:
- bcnr.requestAccount({ account?, category? }) — backwards compatible
- bcnr.getAccounts({ category? })
- Stale-import detection (source-file cashaddr comparison on unlock)
- Sequencing: M.1 (generic import) → M.2 (import from Deviant keystore) →
M.3 (API extensions) → M.4 (stale detection). M.1 is load-bearing;
everything else composes.
Companion to Option A which shipped separately as the cross-repo fall-through
in Argus/src/lib/wallet.js.
Two visible fixes from the same 2026-09-09 screenshot:
Right-click a tab was building a DOM menu and then growing the chrome
view height so it would fit under the tabstrip. That opened a
visible gap between the toolbar and the tab body while the menu was
up. Now the tab context menu goes through a new IPC
"tab-context-menu-popup" (main.js) that pops an OS-native Menu at
the click point, floating above every WebContentsView — no layout
change, no gap. Preload exposes tabContextMenuPopup(id, {x, y});
chrome.html's tab contextmenu handler now calls it directly and the
DOM openTabContextMenu / openGroupSubmenu / growChromeForMenu path
is bypassed for tabs. (The bookmark bar's own ctxmenu still uses
the DOM path — its short 2-3-row menus don't grow chrome enough
to be visible.)
Bookmark chips were too small (130px max-width, 11px text, 22px row).
Now every chip is a fixed 150px × 28px cell so the row reads as a
uniform grid, 12.5px text, 14px favicon. Labels drop the descriptor:
"GAME.X — Bitcoin Cash game platform" renders as "GAME.X". The
trimmer splits on the first em-dash / en-dash / hyphen that's
surrounded by whitespace, so single-word titles and hyphenated
compound names ("Foo-Bar" with no spaces) come through intact. Full
title still shows on hover.
install-update-now was spawning setup with ['/S'] alone since the
0.3.31 rewrite. That installs correctly (E2E-proven multiple times
this week) but leaves the user without a running browser after the
install completes — the setup exits, and the user has to click the
Start-menu shortcut to get Theseus back.
--force-run is electron-builder's NSIS convention for 'start the app
when the install finishes'; it makes the whole update feel like a
seamless in-place restart. --updated stays out (was proven not to
affect the install itself on our config).
Reported by user 2026-09-09 after 0.3.37 → 0.3.39 auto-update ran
cleanly but silently, with no post-install relaunch.
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
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.
Two changes shipped together (main.js touched by both this session and a
parallel session in different regions):
Scrollbars — from the "empty white space should be grey, thumb should
be Bitcoin Cash green" ask:
* new SCROLLBAR_CSS constant + styleScrollbars(wc) helper injects the
theme on every dom-ready
* thumb #0AC18E (BCH primary), track rgba(120,130,150,0.18) subtle
neutral grey so it works on both dark and light surfaces without
hardcoding either; 6px radius, 2px inset via background-clip:padding-box
* modern scrollbar-color on <html> for Chromium 121+; ::-webkit- rules
with !important as the fallback / override for sites that theme
their own scrollbars — scrollbar-width intentionally left alone so
a page that hides scrollbars entirely keeps that behaviour
* hooked into every wc we own: createTab, chrome, popover, enginePicker,
downloadsPop, addressPicker, pwFillPop, linkStatus, sidebar (so every
add-on panel like Aegis picks it up), approvalPop
* fires once immediately if the wc is already past dom-ready when we
attach — fixed views load fast during startup, we'd otherwise miss
captureTab full-page — from the parallel session's screenshot work:
* before Page.captureScreenshot with captureBeyondViewport we now
override Emulation.setDeviceMetricsOverride to the window's full
content width so an open sidebar (or other on-screen chrome that
narrowed the tab view) doesn't clip the shot — capture comes back at
the page's natural full width, not the visible width
* attach the debugger for the call if it isn't attached, detach on
return; clear the metrics override in finally so the tab returns to
its normal layout regardless of success
The two independent drag handles (right edge of URL bar, left edge of
search bar) are replaced with ONE handle sitting between them, wrapped
in a new .urlsearch flex container that owns the URL-bar-plus-search-bar
budget between the nav buttons on the left and the trailing dock on the
right.
Zero-sum semantics: dragging the handle right grows the URL bar and
shrinks the search bar; dragging left inverts. Nothing else in the
toolbar moves — nav buttons, Downloads, extension dock and the
⛓ Theseus logo all sit outside .urlsearch's flex parent.
When the window shrinks below the responsive breakpoint the search
bar (and the drag handle) hide as before, and the URL bar grows to
fill the .urlsearch budget on its own (added flex: 1 1 auto in the
data-responsive rules for .urlwrap).
Verified static rendering: url=700px → search=157px, url=300px →
search=539px, url=default → 60/40 split, narrow window → search
hidden and url fills.
Old settings (urlBarSize / searchBoxSize / searchBoxWidthPx) are
ignored by chrome.html but kept in main's SETTINGS_DEFAULTS so an
older chrome.html could still read them on a downgrade. Only
urlBarWidthPx is used going forward.
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)
Replace the hand-drawn approximations with the official SVGs from
github.com/spothq/cryptocurrency-icons — the permissive-licensed set most
exchanges, block explorers, and other wallets standardised on. Users see
the same BCH / BTC / DGB / SC / TRX / ETH / SOL marks in Aegis they
already recognise from Coinmarketcap, Coingecko, Trezor, MetaMask, etc.
- BCH: green disc with the tilted Bitcoin-Cash B
- BTC: orange disc with the classic Bitcoin B glyph
- DGB: blue disc with the DigiByte D + swash
- SC: brand-green disc with Siacoin's stylised S
- TRX: red disc with the geometric Tron triangle-net
- ETH: purple disc with the two-triangle Ethereum rhombus
- SOL: mint disc with the three-slash Solana mark
All SVGs are inlined in panel.js — no network fetches at panel load.
Bumped addon 0.4.3 → 0.4.4 so seedBundledAddons reseeds the new panel
on next launch.
The toolbar dock still showed the 🛡 emoji even after chrome.html learned
to render data-URI icons — because registerSidebarPanel({icon}) is the
per-panel icon that overrides manifest.icon, and Aegis was passing "🛡"
verbatim. Dropping the override lets addons-host's `icon = manifest.icon`
default kick in, so the dock button pulls the branded aegis.x/brand
shield the manifest now advertises.
Version bumped 0.4.1 → 0.4.3 to force seedBundledAddons to reseed the
new index.js on next launch.
Version bump only, no code changes. Published to prove the just-shipped
0.3.34 install can auto-update itself using the reverted-to ["/S"]
spawn code.
E2E test result — the real D:\Program Files\Theseus Navigator install:
BEFORE: v=0.3.34, hash e59d6da2..., mtime 09/08 22:02:46
AFTER: v=0.3.35, hash 420b7320..., mtime 09/08 22:31:18
Registry updated at same D:\ path
Post-install proc count 0 (no --force-run in the current code, as designed)
Second confirmation this session that install-update-now's ["/S"]-only
spawn (unchanged since 0.3.1) does the right thing against a real
install at a path with spaces.
Installer: 4a7bd82f30a00a6795670d0a48cf590832fd077d84726966d8fbea04fc23be1b
Portable: 138c03ad21cb6fb2a6a7f89cc726adee618622c11eb58177857fa24afa955e47
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
Same 0.3.31 version, new binary. Rebuilds the shipped 0.3.31 with the
salvageable content from the reverted 0.3.32-0.3.34 track:
chrome.html
- light-mode chrome strip: --bg #e6e8ec, inactive tab #f2f4f7,
active tab #ffffff. Fixes the "tabs disappear into the light
Windows title bar" report.
- bookmark chips shrunk: 130px max-width, 11px text, 12px favicon,
22px row (was 26). ~40% more chips fit in the same width.
- bookmark chips draggable with the tab-strip's left/right-half
drop convention; new .dropbefore/.dropafter accent.
- light-mode .tor + .logo + .upchip chips: from illegible white-
on-#253A49 (at 12-13px) to #eef1f5 with #253A49 ink. Both readable
now. .tor.connecting/.on keep amber/purple hue in light fills.
main.js
- will-download update handler now streams the saved setup .exe
through crypto.createHash("sha256"), compares to the manifest's
updateAvailable.setupHash before marking ready. Rejects and
deletes the file on mismatch or on empty manifest hash. Test C
in the previous session proved this catches truncated payloads
Electron reports as "completed" (a real class of failure the
Ariadne addon updater has always guarded against here).
- new bookmark-move IPC: splices the list, no-ops on self-drop
or missing entry.
preload.js
- moveBookmark(fromUrl, targetUrl, place) exposed for chrome.
Deliberately NOT changed: install-update-now still spawns setup with
["/S"] alone. The 0.3.32 --updated /S --force-run change was proven
in the previous session's real-install E2E to not address the actual
"browser vanished on D:\Program Files install" symptom — every flag
combination (/S alone, --updated /S --force-run, /S /currentuser,
/S /D=<install>) exits 0 without upgrading anything on that specific
install path. That's a separate open bug; not touched here.
Version stays 0.3.31 — this is a binary rewrite of 0.3.31, not a new
release. Existing 0.3.31 installs won't see an update chip (version
compare returns false), which is intentional given the auto-update
path is still broken for non-default install locations.
Bundled revert of six commits:
9bacdcb Ship 0.3.34
aef7864 0.3.34 UI (grey chrome, DnD bookmarks, chip colors)
c2da8bb Ship 0.3.33
9b92224 fix(updater) verify SHA-256
4e932e0 Ship 0.3.32
4e695c4 fix(updater) --updated flag
Reason: the real-install E2E test (turn N-1) showed silent update
against the user's custom D:\Program Files\Theseus Navigator location
returns exit 0 without upgrading anything — every flag combination
tried (/S alone, --updated /S --force-run, /S /currentuser,
/S /D=<install>) exits successfully and no files change. The 0.3.32
flag fix and 0.3.33 hash-verify fix were defensive improvements but
did not address the actual "browser vanished" symptom, and 0.3.34
inherits the same broken silent-update path so its UI improvements
never reach anyone still on 0.3.33+ with a non-default install path.
Rolling manifest back to 0.3.31 so the update chip stops appearing on
installs that can't complete the update. Users on the default
%LOCALAPPDATA%\Programs\theseus-navigator location are unaffected —
0.3.31 is still their canonical shipped version.
package.json reset to 0.3.31 explicitly (git revert stopped one bump
short on the merged diff).
Screenshot 0.3.0 (8ca5ac3) preserved — unrelated parallel work.
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.