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.
Setup f9d0654572e110f6895951dc22ec9fb4549b3a8fb14714770579264b62051259
Portable 7ff7a1501bc7d7407ec0a4974aa26652d6d7ecabcc796584254a8bb463d43b09
Two bundled fixes since 0.3.15:
4f498a1 - Extension dock no longer stacks into a column when dragged
narrow. #extbuttons + .extdock get flex-wrap:nowrap + overflow:hidden;
a second data-extcollapse signal fires when .bar's contents overflow
(alongside the width-based level 3), collapsing the row into the
single 🛡 puzzle button. Hysteresis (cached natural width + 8px slack)
keeps the ResizeObserver from oscillating across the boundary.
e160dac - toolbar-menu popup goes native (Menu.popup from main) so it
escapes the chrome-view height clipping. capturePage retries transient
0x0 results up to 6 times so the screenshot addon doesn't silently
produce a blank PNG right after a navigation.
Deployed. Verified LIVE 0.3.16.
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.
The extension row was stacking into a column when the user dragged the
URL bar wide enough to squeeze the dock's slot. Two things broken:
1) #extbuttons could wrap: added flex-wrap:nowrap + overflow:hidden so
the buttons never break to a new line. Same for .extdock's own
flex container (nowrap + min-width:0 so it can flex-shrink to zero).
2) Collapse was width-based on .bar only — a window wide enough for
level 0 wouldn't collapse the dock even when the URL-bar override
left extdock with 0 px. Added a second signal:
data-extcollapse="1" fires when .bar's contents overflow their slot
(bar.scrollWidth > bar.clientWidth). Applies alongside the level-3
collapse; either path shows .extmore instead of the row.
Hysteresis to avoid RO loop: while EXPANDED, cache the row's natural
scrollWidth. While COLLAPSED, re-expand only when summing every other
.bar child leaves at least (naturalWidth + 8 px slack) of room. Both
directions verified across URL widths 400/800/1200/1500/reset — the
row expands / collapses at the right thresholds with no oscillation.
Setup 81b6a8d2a64d20641c4cc448d35627fb3c492ea45adb24fa5c41a9f63663dd2d
Portable 47a22d86a08305536b4d80f4072ad38179a4935f303ac31bcd05dba36d5f4d40
One bundled commit since 0.3.14:
6dae15e - Drag handles on the trailing edge of the URL bar + leading
edge of the search box let the user resize live; widths persist as
urlBarWidthPx / searchBoxWidthPx and override the discrete size preset.
Also fixes the sliding bug the user just flagged: capping the URL bar
via Settings > Appearance was letting the download / extension /
Theseus buttons pack next to the URL bar, sliding leftward. Adding
margin-right: auto to the capped .urlwrap absorbs the slack so the
right group stays pinned to the right edge. Verified via CDP: logo's
rightGap stays at 10px across default / compact / urlBarWidthPx=500 /
reset.
Deployed. Verified LIVE 0.3.15.
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.
Setup 238f0b81afd93d7cef80b9403edef1dc3e1d4c1bf29e29a498115e1185ce1db8
Portable d8877e696ad6425aa6d56cb796048702cfb37b40ff539a8c1af0d08d0a31870c
Three bundled fixes since 0.3.13:
- (Aegis multi-chain wallet, cherry-picked from claude/sleepy-maxwell-251ee6
worktree) — bchwallet add-on turns into a chain-agnostic wallet
manager with BCH + Tron mainnet + Tron Nile testnet; addon id stays
"bchwallet" so vault-derive paths + legacy BCH funds are untouched.
Dapp bridge on any https page exposes window.tronWeb / window.tronLink
matching TronLink chain ids so Tron dapps just work.
- 3d3dfe5 — Screenshot addon reworked: dock icon opens a small
toolbar-menu (Visible / Full page / Region…) instead of a sidebar
panel; captures open in a full browser tab with an editor
(crop / arrow / rect / circle / freehand / text / blur / undo / save /
copy). Two new addon-host capabilities land: toolbar-menu + open-tab.
- dd31b88 — Light-mode --acid went from #4d7300 → #3a5c00 (~7:1 on
white), and the missed addon panels (bchwallet, siawallet,
screenshot editor.css) got their overrides so nothing resolves to
the bright #d6ff3d anymore.
Note on Nile testing: the code paths verified via a running instance —
dock shows 🛡 Wallet, chain picker "🟨 BCH — main ▾" is present. Live
Nile send requires a vault + testnet TRX from nileex.io/join/getJoinPage.
Deployed. Verified LIVE 0.3.14.
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
Previous #4d7300 (0.3.10) was still too light against actual white
backgrounds — several tint fills (rgba(214,255,61,X)) and unpatched
addon panels were making the effective color feel bright green. Two
fixes bundled:
1) Bump --acid in every top-level page's light-media block from
#4d7300 to #3a5c00 — same hue, ~7:1 contrast on #ffffff (was ~5.5:1).
2) Add the missing light-media --acid override to the addon panels
that were still resolving to #d6ff3d: bchwallet/panel.html,
siawallet/panel.html, and screenshot/editor.css (was #b4e024, now
#3a5c00 to match).
Dark mode unchanged. Tint fills (rgba backgrounds at low alpha) still
stay as-is — at 8–15% opacity the specific hue barely matters and the
darker foreground now dominates.
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.
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.
New capture-tab capability on the addon-host, and the screenshot add-on
uses it to expose three modes in a sidebar launcher panel:
- Visible viewport: Electron's WebContents.capturePage() on the active tab
- Full scrollable page: temp-resize the tab view to document.scrollHeight,
capturePage, restore
- Region: preload overlays a translucent selection div, tracks mousedown /
move / up, sends the rect back; main takes the visible capture and
crops via nativeImage.crop({x,y,width,height})
Saves land in the user's Downloads folder via session.downloadURL — same
pipeline as any file download, so the download chip picks them up.
Filename: theseus-screenshot-<host>-<ISO date>.png. JPEG option for
smaller files.
A follow-up task (task_b9608dc6) reworks this to open captures in a
full-tab editor with crop / draw / annotate / undo / copy-to-clipboard
instead of the current bare launcher.
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.
Setup a28b4d2be705069f35fa21c5c3e008627661b6173d1eb3014d001eb81c72761e
Portable 5dab9a17818d263706704ab867b6ab9743c2606eb60e2391d0e00a78604cd26b
Two bundled fixes since 0.3.11:
e7dfa46 - At responsive level 3 the collapsed extension button was a
generic 🧩 puzzle piece. Swap it for the first registered extension's
own icon (📝 / ₿ / Ⓢ / whatever ships first). Puzzle piece stays as the
empty-state fallback when no addons are registered. Click still opens
the same dropdown of all installed extensions.
95cc2c7 - Remove the redundant 32-px extension picker strip that
sidebar-preload.js was injecting at the top of every add-on panel.
The toolbar extension dock is the canonical switcher now; doubling
that inside the sidebar just wasted vertical space and made narrow
panels cramped. Every panel reclaims its top 33px.
Deployed. Verified LIVE 0.3.12.
Every add-on panel was carrying a 32-px tab bar at the top listing every
registered extension. That existed before the toolbar extension dock
landed — now that the dock (per-extension buttons + puzzle dropdown at
narrow widths) is the canonical switcher, doubling that inside the
sidebar just wasted vertical space and made narrow panels feel cramped.
Drops installPickerStrip() from sidebar-preload.js and the invocation
from the DOMContentLoaded handler. The body-padding style that made
room for the strip goes with it, so panels reclaim their top 33px.
Resize grip on the left edge stays — unrelated feature.
No panel HTML depends on the strip's padding — the wallets, notepad,
relay, siawallet, and screenshot addon all start their own body flow
from 0. Nothing else to change.
At responsive level 3 the dock folds into a single button; that button
was a generic 🧩 puzzle piece. Swap it for the first registered
extension's own icon (📝, ₿, Ⓢ, whatever ships first in the panels
list), with the puzzle piece staying as the empty-state fallback if
no addons are registered yet. Clicking still opens the same dropdown
of all installed extensions, so no functional change — just the
button reads as "the extension you're most likely to want" instead of
a generic marker.
Setup b08530a69d47c2525a2238bd35542d141c4cf2be09f61f7b6d9bb08166f606cf
Portable b67387c85f6d37df2d2c2055a1773954a2e9019d3d567943d8ed5a39e9ee53a9
One bundled fix since 0.3.10:
beca0fa - A ResizeObserver on .bar sets data-responsive to one of four
levels based on width. CSS reacts: search box auto-hides at level 1,
Theseus button collapses to just the gear icon at level 2, and the
extension dock folds into a single 🧩 puzzle button opening a dropdown
of installed extensions at level 3. User's explicit Address bar /
Search box size settings still win at wide widths.
Deployed. Verified LIVE 0.3.11.
A ResizeObserver on .bar sets data-responsive to one of four levels
based on width, and CSS reacts:
0 (>=1050px) wide — everything visible (default)
1 ( >=820px) tight — auto-hide the search box, URL min-width drops
2 ( >=620px) narrow — Theseus button collapses to just the gear
3 ( <) xnarrow — extension dock collapses to a single 🧩 button
that opens a dropdown listing every registered
panel (icon + name), click to open the sidebar
Puzzle dropdown reuses the same open/close pattern as the existing
bookmarks menu — click outside to dismiss, click a row to open (or
close if it's already the active panel). User's explicit
Settings > Address bar size / Search box size still win when the
toolbar is wide enough for them; responsive collapse only forces
extra hides at the tighter widths.
Verified on a running instance across 5 widths (1200/900/700/500/400):
each breakpoint flips exactly the elements it should.
Setup d9801fe6a1ea7d54132db36b2c78311cd5c77912618687fb9cf6476ce75a97f9
Portable ff6ea386752fb3ceeb9d5f385258a6f01f7266c2509f4670419ce6e44e24d442
One bundled fix since 0.3.9:
d1f3347 - The brand acid green (#d6ff3d) was ~1.3:1 contrast on
#ffffff / #f6f8fb, so it went nearly invisible any time the user
flipped Settings > Theme to Light. Every chrome page (chrome, home,
settings, error, approval, messages) now overrides --acid to #4d7300
in its prefers-color-scheme: light block — same hue family, ~5.5:1
on white. Also added the missing --acid: #d6ff3d declaration to
chrome.html's :root (was relying on var(--acid, #d6ff3d) fallbacks,
so the light override couldn't bind). Verified live via CDP.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.10.
Two background tasks in progress (screenshot extension, Aegis
multiwallet + Tron/Nile) will land in a later release.
#d6ff3d on a #ffffff / #f6f8fb background sits at ~1.3:1 contrast — the
acid green went almost invisible any time the user flipped Settings >
Theme to light. Override --acid to #4d7300 in every chrome page's
prefers-color-scheme: light block. Same hue family, ~5.5:1 contrast
on white, still reads as the same brand color.
Also: chrome.html was missing --acid: #d6ff3d in :root entirely (every
site used var(--acid, #d6ff3d) fallbacks). Adding the real declaration
means the light override can actually take effect.
Files touched: chrome.html, home.html, settings.html, error.html,
approval.html, messages.html. downloads.html is a dark-only overlay
(hardcoded), collision.html already had a proper light-mode --bcdn.
Tint fills (rgba(214,255,61,X) at low alpha) stay as-is — the specific
hue barely matters through 8% opacity.
Verified live via CDP: getComputedStyle(--acid) returned #d6ff3d in
dark, #4d7300 after cfg.set('theme','light').
Setup cc676e62057b42c1e1221f0e9b69a55806bab90259bbbb4e6eb64701ab3d420a
Portable 1ab811f7edba74220b452763ee0f3329ba91d84c834d3e38be6426de0b04f1c7
Two changes since 0.3.8:
f1d117e - .extbtn was display:grid, which is block-level, so multiple
extension buttons inside #extbuttons stacked vertically. Switch to
inline-grid — same icon-centering, no forced line break between
siblings. Verified on a running instance: all 4 dock buttons at the
same y-coordinate.
(this commit) - Remove the static Aegis ₿ placeholder button and its
handler. Redundant now that the real bchwallet addon ships in the box
and registers its own dock entry. Also flip the dock-hidden logic to
disappear entirely on a fresh install with no add-ons registered, so
the toolbar doesn't carry an empty slot for users who never install
one.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.9.
.extbtn had display:grid, which is block-level by default. Multiple
buttons inside #extbuttons stacked vertically because each was a block.
Switch to inline-grid — same centering behaviour for the icon inside
the button, no forced line break between siblings. Verified on a
running instance: all 4 buttons render at the same y-coordinate with
strictly increasing x.
Second bundled wallet, same shape as bchwallet:
- keys: api.vault.derive("siawallet/mainnet/0") as the seed for walletd's
KeyFromSeed(seed, index) (blake2b(seed||index) -> ed25519); addresses are
standard unlock hashes, so a future walletd seed import yields the same
addresses. Seed and keys live in memory only.
- lib/sia.js: Sia binary encoder, StandardUnlockHash, address checksum,
v2 InputSigHash ("sia/sig/input|" + replay byte 2 + transaction
semantics), transaction weight, walletd JSON. Address hashing and the
sighash were verified against real mainnet v2 transactions (signatures
from block 591853 verify under this implementation).
- lib/walletd.js: address-scoped walletd HTTP client (tip, fee, balance,
outputs with proofs, events, broadcast). The node URL is a user setting
with no default; hosted providers embed the access key in the path, so
only the origin is ever displayed or logged.
- lib/wallet.js: gap-limit discovery via events, mature/immature balance,
history deltas from v1/v2/foundation/miner events, largest-first
selection with change to the current address, fee = walletd rate x
weight x 1-3 multiplier, broadcast with the outputs' basis. A signed tx
built here was accepted structurally by a live walletd (rejected only
for the stub key not owning the parent).
- panel: Receive (QR), Send, History, Settings (node URL, derivation info,
seed reveal behind approval, connected sites); gates for locked vault,
no vault, no node URL.
- window.siacoin dapp bridge: getAddress (rememberable), signAndSend with
100/1,000/10,000 SC allowances, signMessage (ed25519 over blake2b-256 of
the message) — same approval and permission rules as the BCH wallet.
Setup b2b7819e7a5f1ae1aa7ac79dd8e150f7c11f238eb3ab98a53fae51186dd4dc5b
Portable c971931f380245e44064720b132859917039488c12e0f654a2eeb406cf465d37
Bundled fixes since 0.3.7:
b649398 - User-resizable address bar + search box. Settings > Appearance >
Toolbar now offers urlBarSize (wide/medium/compact) and searchBoxSize
(hidden/compact/normal/wide). Applied as data-attrs on .bar so the flex
basis of .urlwrap and the width of .searchbox swap live. Motivation:
the per-extension dock (0.3.7) needs room to grow as users install more.
Settings-set broadcasts settings-update to chrome so resizing is instant
without a relaunch.
ab87576 - F12 / Ctrl+Shift+I opens Chromium DevTools on the active tab
in a detached window. Wired in the same before-input-event handler that
owns reload / sidebar shortcuts. Always targets the active tab
regardless of which view received the keystroke.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.8.
VPS archive step 7: 0.3.6 pair moved to Sia dl-archive/, only 0.3.7 +
0.3.8 remain on /opt/silent-mode/dl/ (per shipping-mirrors memory).
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.
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.
Setup 37fd8db5388cc2486b48d282bf38e1d5b18a40de5b7319950edcc412f3ddb683
Portable befab4c21ef78d7c92eb7d399e163d3e53e202a0bda9fe95f8d8db916db2e242
Bundled fixes since 0.3.6:
1026b08 - Per-extension toolbar dock: replaces the single sidebarbtn with
one button per registered addon sidebar-panel. Notepad (📝) and Silent
Mode Relay (🌐) appear automatically from their manifest icons; a static
Aegis Wallet placeholder (₿) marks the upcoming built-in BCH wallet.
Click a live button → open the sidebar on that panel, click active →
collapse. Preload adds openSidebar(panelId) / closeSidebar() wrappers.
622aaee - Right-click "Search for '<selection>'" in the page context
menu. Uses SEARCH() so it honours the current default engine, opens in
a new foreground tab, and truncates the label at 40 chars.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.7.
Ops note: silentmode.st /var/log had grown to 6GB (journal + rotated
syslog), which broke the first portable upload. Vacuumed journal to
500M cap; ~5.9G free after cleanup. Old Theseus builds not touched.
Replaces the single sidebarbtn with a dock that renders one button per
registered addon sidebar-panel. Each button shows the panel's icon
(the emoji from its manifest) and opens the sidebar on that panel.
Clicking the currently-active button collapses the sidebar; clicking a
different one swaps the visible panel. Notepad (📝) and Silent Mode
Relay (🌐) appear automatically because they already register panels.
Also lands a static Aegis Wallet placeholder button (₿) next to the
live ones, styled as .soon so it reads as coming-soon. It'll come out
once the addon registers a real panel.
Preload adds openSidebar(panelId) and closeSidebar() wrappers around
the existing sidebar-open / sidebar-close IPC handlers.
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.
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.
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.
Send tab: recipient (cashaddr or legacy, testnet rejected), amount with
BCH/sat toggle and Max, 1-5 sat/B fee slider, live fee/total preview via
planSend. Sending goes plan -> approval-modal (To/Amount/Fee/Total) ->
ECDSA DER + SIGHASH_ALL|FORKID -> blockchain.transaction.broadcast, then
shows the txid with an explorer link. Confirmed coins are spent before
unconfirmed; dust change folds into the fee. Verified end to end against a
fake Fulcrum: broadcast tx re-parsed, sighash recomputed, signature checks.
Manifest declaring sidebar-panel, vault-derive, page-inject (https://*.x)
and approval-modal; registers the Wallet sidebar panel. Shows up in
Settings > Extensions and opens from the sidebar.
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.
Setup e2a30a3b280448f8eb6e08efd21e208022b2a37cef14a72076b3f32b7a2fc529
Portable eca7cf3347a958307b8e2ba4f6266c81f8ec691b0c98ed6e24fdd70e65723b1e
Bundled fixes since 0.3.5:
d3c3298 - Chrome-view gap root cause: body no longer inherits the chrome
view's height, so document.body.scrollHeight now measures actual content
and syncHeight can shrink chrome back after a menu close. The growing
blank strip above the page is gone (was previously ratcheting to the
260px clamp on every menu open).
1b4a3c3 - Bookmarks: favicons on every row (backfilled on revisit for
older entries) + Edit title actually works via an in-page modal (Electron
BrowserViews disable window.prompt, which is why the previous menu item
silently did nothing).
952bf5f - Tab tooltip on hover shows title + URL, the way Chrome/Firefox
do it. Uses the url field already present in the per-tab payload.
f591475 - Error page gets a first-class "You appear to be offline" kind
that fires when navigator.onLine is false — no more false "not registered"
verdicts when you're just disconnected. Plus a "did you mean" row that
suggests near-match BCNR names (Levenshtein ≤ 2, same TLD) from the warm
shared index — no network required, works offline. Click a suggestion
to retry with the original path preserved.
b9d70e4 - 750ms self-heal on chrome-view height as belt-and-suspenders
for any future overlay path that bypasses closeAllMenus.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.6.
Every .tab now carries a native title="<title>\n<url>" so hovering the
tab shows the page's own title and the URL (the way Chrome/Firefox do
it). Uses the url already present in the per-tab payload emitTabs sends;
no extra IPC.
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.
The 750ms self-heal (b9d70e4) was a bandage. Real root cause: body had
height:100vh, so document.body.scrollHeight always equalled the chrome
view's current height (which IS the viewport). Once a menu grew chrome
via growChromeForMenu, syncHeight measured the inflated body and re-sent
the same big number — CHROME_H never shrank back.
Fix: drop height:100vh so body sizes to its content. scrollHeight becomes
the natural chrome height, and menu-close paths correctly report a value
smaller than the current CHROME_H. The self-heal stays in as insurance
for any future overlay that bypasses closeAllMenus.
Bar layout unchanged — every bar has an intrinsic height (.tabs 34px,
.bookmarks 26px, .bar auto), so body flow reflects them naturally.
overflow:hidden stays to clip any pathological overflow.
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.
The onTabs / onNav re-sync catches the common menu-close path, but a menu
that gets orphaned by a drag interruption, a focus flip, or a submenu
race can leave the chrome view taller than the natural body-scrollHeight
until the next tabs/nav event fires. Symptom: a huge blank strip between
the address bar and the page that grows and doesn't shrink until the tab
changes.
Fix: a 750ms interval that re-runs syncHeight whenever no .ctxmenu /
.grouppop is present. setChromeHeight in main.js only re-layouts when
the value actually changes, so this is a cheap no-op in the steady state.
Holding the ship: will bundle with the next batch per the slower-cadence
policy. Users on 0.3.5 keep the existing gap fix; this one lands with
the next release.
Setup 935d637ae19ea821f7e89b0f9a802b4e774b6d1ae3254a70f3e5f17f89424177
Portable bb6bea253d6f32f86e8fdd152cf4c969cf311c991329d5e94fce31215ecf7f00
Chrome-view height gap: any tabs event that fires while no menu/popover
is open now normalises the chrome-view height via syncHeight(). A leaked
menu-close path could previously leave the strip inflated; the next
tabs update guarantees it shrinks back to the natural body-scrollHeight.
Tab-group drag-and-drop: group chips are now valid drop targets in the
same drag session as tab reorder. Dragging any tab onto a chip and
dropping assigns that tab to the chip's group (which also auto-clusters
it via the existing tab-group handler in main). Chip highlights acid
green while a valid drop hovers.
Collapsed group vertical popover: click a collapsed group chip and a
floating panel opens below it, listing every tab in the group. Each
row shows the favicon + title + a ✕ to close that tab. Clicking a row
switches to the tab AND expands the group so the newly-active tab
appears in the strip (tabGroupToggle). Popover closes on outside click
or Escape; the outside-click filter also ignores clicks inside .gchip
so opening the popover doesn't immediately close it.
growChromeForMenu() and closeAllMenus() now include .grouppop in their
overlay queries so the popover contributes to chrome-view sizing and
gets cleaned up alongside the ctxmenus.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.5.
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.
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.
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.
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.