Manifest, releases page, download pages and theseus.x fallback text all point at
0.3.61 with the new hashes. Silentmode.st serves static; silentmode.bch mirror
follows via the 5-min Sia sync.
Every subsystem is now individually switchable and reports its own state.
GET http://127.0.0.1/api/status returns:
- daemon: version, startedAt, uptimeSec, policy, permittedTlds, ports,
upstream, gateway
- index: size, nameCacheSize, warmStart {source, at, size}
- sources.snapshotHttps: enabled, urls, lastAttempt/Success/Status/Error,
winnerUrl, notModified count, refreshes count, ETags
- sources.electrumWss: enabled, lastAttempt/Success/Error, failStreak, refreshes
- sources.perQueryLookup: enabled, gateway, lastAttempt, hits/misses/errors
- sources.diskCache: enabled, path, exists, size, mtime
- sources.localApi: enabled, endpoints[]
- policyFile: path, exists
policy.json schema extended with a sources.<name>.enabled block, hot-reloaded
by the existing loadPolicyOverride() on the same 5-second tick as the
collision policy. Each refresh/lookup path guards on its flag, so an operator
can isolate any subsystem without a restart:
- snapshotHttps off -> daemon skips dl.silentmode.st/Sia polling
- electrumWss off -> daemon skips chipnet WSS refresh
- perQueryLookup off -> DNS handler misses fall straight through to forward
- diskCache off -> no write to and no read from bns-name-snapshot.json
- localApi off -> /api/* return 503 with a hint pointing at policy.json
Verified locally on port 18080: /api/status returned the full JSON with a
snapshot warm-start from dl.silentmode.st recorded in warmStart, ETag captured
in sources.snapshotHttps.etags, disk cache path/size/mtime populated.
Deployed dl.silentmode.st + Sia. sha256 a16d10759efede4412f4fdb52d59424bf56bbb258bb002ab930f4a5a5aef8a41.
The matching Theseus panel UI (source table with the toggles + a Copy-status
button that fetches /api/status) belongs in the Theseus release; not touched
here to avoid colliding with settings-preload.js work in flight from a
parallel session.
Manifest and download pages point at the new build. Silentmode.st serves
static; silentmode.bch mirror follows via the 5-min Sia sync; theseus.x's
fallback text and hashes match the 0.3.60 binaries so the page reads correctly
before the client-side manifest fetch overwrites them.
- .iss pre-install hook now also kills orphan node.exe from the Ariadne
install dir (foreground diagnostic runs, crashed processes not cleaned
up by the scheduled task), path-scoped so no other app is affected.
Fixes upgrades failing with "DeleteFile failed; code 5 -- Access is
denied" on runtime/node.exe -- seen on 0.1.11 install by a user who had
run the daemon manually per an earlier diagnostic.
- bnsd.js Express gateway now serves local BNS indexer endpoints:
/api/tlds -> {source, tlds: string[], count}
/api/name/:n -> full BNS entry (same shape as navigate.st/api/name/)
/api/registry -> {source, count, names[]} full index dump
CORS wide-open (matches operator behavior). Any app on the machine can
now query BNS locally with no navigate.st round-trip. Answers off the
daemMons in-memory index which already merges snapshot warm-start +
ETag polling + per-query indexer fallback + electrum WSS.
Verified locally: hello.bch resolves via /api/name; unknown gives 404;
/api/tlds returns the CA-constrained set (bch p2p bit deviant nav test
bcnr on this boxs current CA); /api/registry returns 61 names.
Deployed: dl.silentmode.st + Sia. sha256 b041652a330fefb4561da7ca14fa84ddf3b6085fda6eb62831cb08ea56ec602d.
The daemon now uses the operator's live BNS indexer as the primary source of
truth, not chipnet electrum. Two changes to the resolver pipeline:
1) Background refresh is HTTPS-first with ETag. Every 30 s the daemon polls
dl.silentmode.st/bns-name-index.json (and s3.silentmode.st:8600 as an
independent Sia mirror) sending If-None-Match; only reparses the array
when the operator republished, does zero work otherwise. Electrum WSS
stays as a same-tick secondary that runs when HTTPS turned nothing up,
so a daemon on a network that can reach chipnet but not the operator VPS
still converges. Both broken? Keep the last-good index from warm-start.
2) Per-query indexer fallback in the DNS handler. When the in-memory index
misses, the handler calls navigate.st/api/name/<host> directly with a
5-second timeout and a 30-second LRU cache. Names registered AFTER the
last snapshot poll but BEFORE the next one now answer immediately from
the live indexer instead of falling straight through to a forward and
returning NXDOMAIN. Cache is cleared on any successful full refresh so
a de-registered name doesn't stay resolvable.
Net effect: on the class of failure that motivated this (chipnet electrum
timing out on blockchain.scripthash.get_history, or port 50004 filtered by
the network), the daemon just works. The operator's HTTPS indexer -- which
every other client in the ecosystem (Theseus mobile, Aegis, sirius.x, the
gateway itself) already reads from -- is now Ariadne's Thread's primary
too. Trust model unchanged: the snapshot always came from the operator.
Verified locally with the built binary on port 15353:
bnsd index: warm-started from https://dl.silentmode.st/bns-name-index.json (61 names)
hello.bch -> 127.0.0.1 (snapshot hit)
game.x -> 127.0.0.1 (per-query /api/name fallback -- not in .bch-heavy snapshot)
coinspectrum.x -> 127.0.0.1 (per-query fallback)
ariadne.x -> 127.0.0.1 (per-query fallback)
notregisteredxyz.x -> NXDOMAIN (indexer confirms not registered, forwarded to upstream)
Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.11.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.11.exe
- silentmode.st manifest advertises 0.1.11
- sha256 58ffb06424785973d2cd656e5122061ce10b53a8dcff55605c23b5699552ee75
WSS-blocked networks
Symptom on the other box (2026-09-29): task Running, port 53 bound by our
node, `Resolve-DnsName hello.bch -Server 127.0.0.1` times out. Cause: the
daemon's initial index build was a top-level `await buildIndex()` over
electrum WSS to chipnet (port 50004). Corporate/ISP networks that filter
outbound WSS let the await hang forever; the queries fell through to
`forward()` to upstream 1.1.1.1:53, which the same firewall also blocked,
so both legs died.
0.1.10 borrows Theseus's warmFromSnapshot pattern for the system-wide
daemon. On startup, in parallel with an 8 s deadline per source:
1. HTTPS snapshot from operator VPS https://dl.silentmode.st/bns-name-index.json
2. HTTPS snapshot from Sia mirror https://s3.silentmode.st:8600/bns/name-list.json
3. On-disk cache from a previous run C:\ProgramData\Ariadne\bns-name-snapshot.json
First non-empty result wins; on any HTTPS win the raw array is written to
the disk cache so the NEXT boot is instant even fully offline. dns.bind()
runs the moment warm-start completes, whether or not electrum was
reachable. Electrum WSS refresh continues in the background on the 30 s
timer and wins over the snapshot when it succeeds because it is strictly
newer.
Snapshot format is the pre-resolved array Android's Ariadne reader already
consumes (`[{name, category, records, txid, height, updatedTxid, owner?}]`),
so the operator maintains ONE artifact for both clients. Custom URLs go in
`ariadne.config.json`.snapshotUrls.
Verified on the dev box by running the built binary manually on port 15353:
bnsd index: warm-started from https://dl.silentmode.st/bns-name-index.json (61 names)
bnsd DNS: 127.0.0.1:15353 (policy=bcnr-first, upstream 1.1.1.1)
hello.bch -> 127.0.0.1
Disk cache written: 17,674 bytes.
Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.10.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.10.exe
- silentmode.st manifest advertises 0.1.10
- sha256 29710c4011dfe058a252f94af221c02593e728e89cc7427e3e01eaa530f8e526
Chromium (Chrome + Edge) doesn't treat `foo.x` as a URL because `.x` isn't on
its known-TLD list -- it sends bare input to the default search engine. Only
way to intercept it without a browser extension is to make the default search
engine one that understands BNS.
The daemon now serves that. New route `app.get("/search")` before the catch-all
Express handler:
- `q` matches <label>.<permitted-tld> -> 301 https://q/ (any path preserved)
- q has whitespace, unknown TLD, IP, etc. -> 302 to `searchFallback`
(ariadne.config.json, defaults to https://duckduckgo.com/?q=)
- empty q -> 400
Permitted-TLD check reuses isPermittedHost() from lib/tlds.js -- same set the
CA is constrained to and the SNI callback gates leaf minting against, so
"routable, therefore a URL" stays one decision.
Users set their browser's default search engine to
`http://127.0.0.1/search?q=%s`. Everything decided locally, no navigate.st
round trip. Works offline. Works with the same offline BNS index the DNS side
uses. Fallback engine is per-user via config, not a locked-in default.
Verified on this box after upgrade:
"ariadne.x" -> 301 https://ariadne.x/
"game.x" -> 301 https://game.x/
"hello.bch" -> 301 https://hello.bch/
"ariadne.x/path" -> 301 https://ariadne.x/path
"Weather" -> 302 https://duckduckgo.com/?q=Weather
"bitcoin cash" -> 302 https://duckduckgo.com/?q=bitcoin%20cash
"unknown.zzz" -> 302 https://duckduckgo.com/?q=unknown.zzz
"" -> 400 bnsd search: q parameter is required
Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.9.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.9.exe
- silentmode.st manifest advertises 0.1.9
- sha256 3b973e0ae584113ad776c756b16b895fd0dfb217a02bfae6376c5abb48f9a77c
Before 0.1.8, a BNS name with an on-chain `ip` record made the daemon return
the origin's public IP directly. Browsers then hit the origin's own HTTPS
server -- and if that origin's cert was not browser-trusted (which is the
whole point of the on-chain `tls` fingerprint pin next to the `ip` record),
every browser except Theseus rejected the site with ERR_CERT_AUTHORITY_INVALID
or SEC_ERROR_UNKNOWN_ISSUER. game.x and hephaestus.x both hit this. Theseus
was fine because its in-process resolver validates against the on-chain pin.
0.1.8 answers DNS with 127.0.0.1 whenever the name has BOTH `ip` and `tls`,
terminates the browser's TLS locally with our trusted leaf, and reverse-proxies
to the origin over HTTPS while validating the origin's SHA-256 fingerprint
against the on-chain pin. Same algorithm Theseus uses (main.js:pinnedHttpsGet,
cert.fingerprint256 lower-cased, colons stripped) so a site's `tls` record
means the same thing in-Theseus and system-wide.
Names with just `ip` (no pin) keep direct routing -- unchanged. Only opts in
when the owner has published a fingerprint pin, so nothing regresses for sites
that deliberately wanted direct IP routing.
Two subtle bugs found + fixed while landing this:
1. `agent: false` on the outbound https.request. With Node's default global
agent (keepAlive on), the second request onwards reuses the TLS socket
and `res.socket.getPeerCertificate(false)` comes back stripped of
fingerprint256 -- pin validation then reads "empty" and rejects with 526.
Fresh socket per request costs a handshake but is cheap next to the
pin-verify miss.
2. Removed `content-encoding` from the hop-by-hop strip list. Origin sends
gzip'd bytes; my proxy streams them unchanged. If we strip
content-encoding while forwarding compressed bytes, the browser treats the
gzip magic as text and renders garbage. content-encoding is end-to-end
per RFC 7230 -- keep it.
Verified on this box: game.x, hephaestus.x, theseus.x, sirius.x,
coinspectrum.x, hello.bch all load HTTPS 200 in strict IWR and Chromium; DNS
answers 127.0.0.1 for game.x + hephaestus.x (was their origin IP before),
still their origin IP for names without a tls pin.
Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.8.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.8.exe
- silentmode.st manifest advertises 0.1.8
- sha256 47450ad4555beb04fe3b90ae5f658dc2932061d011ada9a2fb507471e2f0a0cb
Feature. The daemon now hot-reloads C:\ProgramData\Ariadne\policy.json every
5 s (fs.stat mtime check, only opens the file when it actually changed) and
flips its DNS collision policy between bcnr-first and icann-first in place.
Every browser on the machine follows within seconds -- no daemon restart,
no reinstall, no UAC. The next Ariadne panel in Theseus will edit this file
directly; a text editor works too.
install.ps1 seeds the file with the current -Policy default on FIRST install
only (an upgrade never clobbers a user's later flip) and grants BUILTIN\Users
Modify on it so an unprivileged app can rewrite it. Invalid values keep the
last good policy (logged). File deleted -> daemon falls back to the CLI
policy the scheduled task was registered with.
Also fixes an intermittent "config unreadable" on daemon startup when
ariadne.config.json had a UTF-8 BOM -- same class of PS-writes-BOM bug 0.1.6
fixed in the TLD-sync path, this time in bnsd.js's own config loader.
Verified live on this box with a second daemon running on port 15353:
bnsd policy: bcnr-first -> icann-first (from C:\ProgramData\Ariadne\policy.json)
bnsd policy: icann-first -> bcnr-first (from C:\ProgramData\Ariadne\policy.json)
bnsd policy: ... has invalid policy 'cash-first' -- keeping bcnr-first
ACL check: BUILTIN\Users has Modify on policy.json, non-elevated write succeeds.
Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.7.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.7.exe
- silentmode.st manifest advertises 0.1.7
- sha256 742f8005c2dfb47929287c009cddf7738f0d12bd56fb8f8a837df3e9b435f47f
Theseus UI wiring for this file NOT touched -- settings-preload.js has 31
lines of uncommitted work from another session; a Theseus panel selector
belongs in that session's next commit so it doesn't collide.
Symptom: on any machine that had been through a series of installs, one or
more older "BNS Local Root CA" certs from earlier builds could accumulate in
LocalMachine\Root -- an unconstrained root from before 2026-07, or a rotated
one -- and every install added the current constrained root ALONGSIDE the
stale ones. Two certs with the same Subject-DN defeat browser chain-builders:
Chromium/Edge/Firefox pick one candidate and refuse to try the others, so
every .bch site failed with ERR_CERT_AUTHORITY_INVALID / SEC_ERROR_BAD_SIGNATURE
in every browser EXCEPT Theseus (which trusts the on-chain tls fingerprint
directly, not the machine store).
Reproduced on the dev box: two BNS Local Root CAs present (thumbprints
EB5DBE98... constrained + 23538FD7... unconstrained), DNS answered correctly,
strict Invoke-WebRequest to https://coinspectrum.bch/ failed. Removing the
stale one and re-running the chain-build restored HTTPS 200.
Fix (install.ps1):
- Before Add(imported), enumerate the store and $store.Remove() every
"BNS Local Root CA" whose thumbprint differs from the one we're about
to trust. Post-condition: exactly one BNS root remains after install.
Fix (uninstall.ps1):
- Switched from `Get-ChildItem Cert:\LocalMachine\Root` to X509Store direct.
The Cert: PSDrive is not always loaded in Inno's [UninstallRun] -NoProfile
context, so uninstall could silently leave the CA behind on the store side
even while it removed everything else.
Verified end-to-end on this box: 0.1.4 upgrade over 0.1.3 finished cleanly,
BNS Resolver Daemon Running, 127.0.0.1:53 open, exactly one BNS Local Root
CA in the machine store, strict HTTPS to https://coinspectrum.bch/ returned
200 (506,997 bytes) with X509Chain.Build ok=true leaf -> BNS root.
Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.4.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.4.exe
- silentmode.st manifest advertises 0.1.4
- sha256 30ed5e3bb6d6da5325f854c97976410b4fc63067ed52c810a55e9d2e1bdc4e17
Until now only the gateway, Theseus and the desktop reader applied the
TLD co-sign rule; phones and the Windows resolver still admitted every
registration under a cosign or hidden TLD, so the network disagreed
about which names exist. Both shipped builds now carry the rule.
Android 0.19: the JSON helpers in the new TLD-map code used throwing
getters inside methods that do not declare the checked exception, which
javac rejected under the real build (the earlier compile check ran
without -bootclasspath and stopped at the aapt R errors first). Switched
to the non-throwing opt* forms. Release-signed with the existing key.
Resolver 0.1.3: no source change beyond the resolver library already
committed; the installer picks up the source tree, so this is a version
bump and a rebuild.
Manifest, releases page, tools page and theseus page point at the new
files; dl.silentmode.st/latest redirects regenerated.
Direct answers to the three complaints in one release.
"Doesn't load any results even if I type a keyword" — navigateTo used to
always append .bch to any bare label. So "bitcoin" became
https://bitcoin.bch/ and NXDOMAINed. Now: input with a space, or bare
label with no dot, routes to the user's chosen search engine. Input with
a dot (hello.bch, example.com) stays a URL. Matches Chrome/Firefox
omnibox behaviour exactly.
"Has no check for updates in settings" — new Updates section shows the
installed version + signing-cert SHA-256, with a "Check for updates now"
button that manually polls dl.silentmode.st and toasts the result. If a
newer version exists, the standard silent-prefetch → green-chip → tap-to
-install flow (unchanged from 0.15) kicks in.
"Looks poor compared to a regular browser" — Settings gains three more
sections beyond Resolution / Ariadne's Thread / Maintenance:
- Search: segmented picker for DuckDuckGo (default), Startpage, Brave
Search, Google, Kagi. Persists via SharedPreferences.
- Updates: version + cert + manual check.
- Privacy: "Clear browsing data" — cookies (CookieManager), form data,
cache, per-tab history, all in one tap. Bookmarks + tabs preserved.
- About: silentmode.st/theseus + theseus.x links, brand credit.
serveSettingsPage() signature grew to take (indexerOn, searchKey,
currentVersion) so it can render the current selection + version. Added
searchEngineSegment() helper for the picker layout.
Release: ariadne-v0.17.apk sha256
8a655081782ecc17a50a7fcd67c28625ccb0c905cf6b583bdd703a128b2b9b10
Deployed to dl.silentmode.st + silentmode.bch (Sia mirror). Site pages
bumped: silentmode.st/tools + /releases + /theseus + theseus.x.
Users on 0.15 reported "Ariadne can't open many websites" and "start page
is full of dead links". Two independent stale-data issues, both fixed.
Start page — replaces the featured-hero + beacon-fallback grid with the
same nine curated cards navigate.st serves (hello.bch, siatest.bch,
SilentMode.X, Theseus.X, Sirius.X, Hephaestus.X, Prometheus.X, Helios.X,
Hermes.X). No more auto-iteration over every registered name on the
beacon — which is what was surfacing smtest-dc5563.bch, uitest-e20579.p2p,
qrtest-e21970.nav etc. Added product-role badges (infrastructure /
navigator / registrar / code-host / app-store / search / messaging) with
the same acid/blue/purple/green/gold/pink vocabulary Theseus uses.
Bundled snapshot at app/assets/bns-index.json regenerated from the live
chain via the resolver-web.js buildIndex path. Up from 44 entries to 54,
now including all 13 .x names (was 6 — silentmode.x, theseus.x,
hephaestus.x, prometheus.x, helios.x, hermes.x etc. were entirely
missing). So cold-start resolution finds the .x brands without waiting
for a network trip.
Also regenerated dl.silentmode.st/bns-name-index.json — same 54-entry
JSON. That file hadn't been rebuilt since 2026-08-30 and is what every
client's CDN pull path reads on cold start (mobile, extension, resolver).
There's no timer that regenerates it; scratchpad/build-snapshot.mjs is
the reproducible one-shot: node build-snapshot.mjs <cdn-path>.
Sites updated in the same commit (all four surfaces need to advertise the
same version — silentmode.st/tools + /releases + /theseus + theseus.x
were still linking 0.15). APK also mirrored to bns/silentmode/ so
silentmode.bch/ariadne-v0.16.apk serves the same bytes.
Release: ariadne-v0.16.apk sha256
fa58aa7642319faaa6b23f0f0a46c0adad2ce1f3c5957f4d527ac09241b76574
Silent-background prefetch was already the model in 0.12+, but the chip
showed the same "▲ 0.14" text whether the download was running, done, or
failed. Users had no way to know they'd get "Install now" instead of
"Download & install" until they tapped.
Chip is now a small state machine driven off UpdateInstaller.Progress:
- DOWNLOADING — muted amber pill, text "▲ 0.15 · 45%" ticking live from
onProgress. Tap = "Downloading in the background, tap again when green."
- READY — green pill, "▲ Install 0.15". A brief toast fires on state entry
so the user sees the prefetch just completed. Tap = short confirmation
dialog, then straight to the platform package installer.
- RETRY — appears when the prefetch fails. One tap re-runs prefetch and
the chip returns to DOWNLOADING.
The confirmation dialog now spells out that Android replaces Ariadne in
place and reopens on the new version — matches Android's actual behaviour.
saveSession() runs before launchInstall so the newly-launched v0.15
restores tabs cleanly.
Dead code drop: promptToUpdate + downloadThenInstall (the old on-demand
progress-dialog path) — the state machine handles both silent and
tap-first flows.
Release: ariadne-v0.15.apk sha256
35532e596ec43a2da0cf49496640c72972321f5a44fa2e16d259d281a021b425
Deployed to dl.silentmode.st; site/releases-manifest.json bumped.
install.ps1 root-cause fix
- Every `& $node ... 2>&1` had been tripping PS 5.1's "$ErrorActionPreference=Stop +
native-command stderr = terminating error" quirk. Trap-and-log added in 0.1.1 EXPOSED
the bug (0.1.0 died at the exact same line -- silently). 0.1.2 routes both node calls
through a new Invoke-NodeCapture helper that uses Start-Process
-RedirectStandardOutput/Error to file, so PS 5.1 never touches the streams.
Multi-source TLD cascade with on-disk cache
- -Indexer now accepts a comma-separated list; default "https://navigate.st,https://silentmode.st".
- Cascade (first hit wins): indexer[s] HTTPS -> local cache
(C:\ProgramData\Ariadne\tlds-cache.json) -> electrum WSS (fetchTldList / legacy
fetchBcnrTlds) -> bundled ariadne.config.json snapshot -> bootstrap [bch].
- Authoritative fetches (indexer or electrum) refresh the cache. Second install on a
machine that has ever succeeded before is instant + always works offline.
- Per-source deadlines kept tight (5s for indexer HTTPS) so a slow source can't stall
the install.
Upgrade over live install
- .iss CurStepChanged(ssInstall) stops "BNS Resolver Daemon" + "BNS Sia Bridge" via
schtasks + 1.5s pause before file replacement. Fixes "DeleteFile failed; code 5.
Access is denied" on runtime\node.exe when upgrading a machine where the daemon is
currently running.
CA import
- Switched Import-Certificate -CertStoreLocation Cert:\... to direct X509Store('Root',
'LocalMachine').Add(). Cert: PSDrive is not always loaded in -NoProfile elevated
shells, which had broken the standalone unblock scripts.
Verified end-to-end on this box: silent install -> exit 0, clean install.log,
BNS Resolver Daemon running, 127.0.0.1:53 open, 9 NRPT rules (all TLDs including .bch),
coinspectrum.bch resolves system-wide, tlds-cache.json populated.
Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.2.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.2.exe
- silentmode.st + navigate.st/bns/silentmode.bch manifests advertise 0.1.2
- sha256 5a2c9e4f3c5e1a162008641019f5ffc35968751c0440f94d3c5fe67aafb8cc5e
Local Dev <noreply@localhost>
Bookmarks (A1). ⭐ button in the address row toggles the current URL;
long-press opens the list; a Your-bookmarks section appears above Featured
on the start page. Storage in filesDir/bookmarks.json, plain JSON,
most-recent first.
Undo close tab (A2). Floating pill "Tab closed · UNDO" for 5s after any
closeTab; tap UNDO → reopen in a new tab.
Long-press link menu (A3). WebView.HitTestResult drives an Open in new tab
/ Copy link / Share link sheet; no JavaScript injection.
Downloads (A5). WebView.setDownloadListener wired to Android's system
DownloadManager. Files land in the public Downloads dir with the platform
notification. URLUtil.guessFileName picks the name.
Pull-to-refresh (A4). New PullWebView subclasses WebView with hand-rolled
overscroll detection; a gold chip fades in (pull to reload → release to
reload) as the user drags. No AndroidX SwipeRefreshLayout dependency.
Share-target intent (A6). Manifest gains SEND text/plain and VIEW
http/https filters. urlFromIntent() feeds the payload through newTab (which
inherits navigateTo's .bch shortcut and .bch.bch collapse from 0.12).
Address bar polish (B2/B3/B5). Idle display strips https:// + trailing /
(focus regains restore the full URL, select-all). Long-press copies. A
compound-drawable ic_close × appears while focused + non-empty; tap clears.
Branded splash (E1). New SplashTheme + splash_background paint the Ariadne
mark on #0B0F1A during cold start; MainActivity.onCreate swaps to
AriadneTheme before layout.
Build: d8 classes.dex step ran over Windows's 32k command-line limit; now
Push-Locations into build/classes and passes relative paths.
Release: ariadne-v0.14.apk sha256
0deace437ccde7709cd21486c9b7b37b2a57463cead79b75672d974d3e5b11b0
site/releases-manifest.json bumped. Steps in RELEASE-HANDOFF-v0.14.md.
Start page grows a Theseus-style hero: big Ariadne mark, tagline, search
input that navigates the tab. Section header separates hero from the
featured-card grid. New ariadne-search:// scheme handled in
MainActivity.handleChoice routes submissions through navigateTo (so it
inherits the .bch shortcut and .bch.bch collapse from 0.12).
Tab strip auto-hides while there's only one tab — Chrome pattern, reclaims
~44dp of top chrome for the common case. A compact tab-count button now
lives in the address row (always visible, gold when many tabs), tapping it
opens a lightweight tab switcher (title + active-mark + New tab item).
Address bar polish: 15px → 16sp text, focus outline in gold via
StateListDrawable, slightly taller padding (48dp touch target).
Bottom navbar: ⟳ reload → ⌂ home (the actually-missing action); Reload
moves into the overflow ⋮ menu as the first item.
Motion: 150ms alpha crossfade on tab switch; :active card scale on the
start page.
Release: ariadne-v0.13.apk sha256
aab1f1e1aa4654c6d2d21ee09e3067d8344697609900d6ccdbd082af95dc9ddc
site/releases-manifest.json bumped. Steps in RELEASE-HANDOFF-v0.13.md.
Fixes the compounding-suffix bug (cards linked to hello.bch.bch, then
hello.bch.bch.bch on subsequent loads): the bundled BNS index stores names
TLD-qualified (hello.bch), so the card generator no longer appends '.bch'
a second time. Featured hero lookup is now TLD-agnostic (matches by
label). navigateTo defensively collapses repeated TLD suffixes so old
bookmarks and any address-bar text with .bch.bch survive gracefully.
Start page redesigned to match TheseusNavigator/home.html: provenance
badges (on-chain / sia / server / proxy / redirect / registry), editorial
one-liner descriptions on hero cards, acid-accent .tld suffix, richer
typography.
Auto-update: on launch the app polls dl.silentmode.st/releases-manifest.json,
finds the ariadne-android entry, shows a chip when newer, silently prefetches
the APK to filesDir/updates/, verifies SHA-256 against the manifest, then
offers a one-tap Install prompt via a plain-Java UpdateFileProvider (no
AndroidX). Hash mismatch deletes the file and reports the failure — never
spawns the installer on a truncated payload (per hash-verify rule in
CLAUDE.md).
New: UpdateInstaller, UpdateFileProvider. UpdateCheck now returns filename +
sha256 + changelog. AndroidManifest: REQUEST_INSTALL_PACKAGES + provider.
Release: ariadne-v0.12.apk sha256
89c4cc28d068d2f6962a9788bf671c6f017f27fb8502f3beef8312eb7d7924f4
site/releases-manifest.json bumped. Steps in RELEASE-HANDOFF-v0.12.md.
Every outbound request now advertises the Google Chrome brand in the
sec-ch-ua client-hint family alongside Chromium — same technique Brave,
Vivaldi and Opera use to stay off Cloudflare Bot Fight Mode's naughty
list. Fixes whybitcoincash.com and every other CF-fronted site that was
returning 503 to Theseus while curl got 200. Values come from
process.versions.chrome so nothing straddles.
Installer: 6dd510184a3c1488ac93b7fd0194ddd239fe4e31083705afcc3c7bde0f530107
Portable: 346be24804db0d8a00cd76a2c89cd3bd2b9af17e5505b25ec326f30c3ceec6ad
Toolbar drag handle clamps to a 30% share floor for both the address
bar and the search bar; search min-width bumped to 180px so the
'Search' placeholder always fits; 10px margin between the search bar
and the trailing dock.
Bookmark brand-case for .x names now uses a canonical map so all-caps
sources come out correctly: SILENTMODE.X → SilentMode.X,
silentmode.x → SilentMode.X, GAME.X → Game.X, coinspectrum.x →
CoinSpectrum.X.
Installer: 678a598c52cf3e86686241633d99cf49bcbc6509d893a93af4ba3336712e5e23
Portable: 718ce35606cb15e6a3333704f1b4e0999e488048eb2c4f1bbff2896ffcc7124c
theseus.x → Theseus.X, deviant.x → Deviant.X, sirius.x → Sirius.X.
Already-all-caps names (GAME.X, SILENTMODE.X) keep their form.
Non-.x titles untouched. Applied after the descriptor trim from
0.3.41 so a saved 'theseus.x — the browser…' also comes out
'Theseus.X'.
Installer: 65d467c4b065b5726f33fc7fe7c0626d0d4289eecbfadae5d3625bdf1adf2ec3
Portable: 4466de982c29d7f4028be2d8e9fd9956714ff1a27b2aff8f4cc80f03f84c4a7c
Right-click on a tab no longer opens a DOM menu that grows the chrome
view. New IPC 'tab-context-menu-popup' builds an OS-native Menu (same
pattern the add-on dock dropdowns use) and pops it at the click point,
floating above every WebContentsView — no layout change, no gap
between the toolbar and the tab body.
Bookmark chips now a uniform 150 × 28 px cell, 12.5 px text, 14 px
favicon. Chip labels trim descriptors after a whitespace-flanked
em/en-dash/hyphen: 'GAME.X — Bitcoin Cash game platform' renders as
'GAME.X'. Full title stays on hover. 'Foo-Bar' comes through intact.
Installer: 22cff863ee44e6bdc17c612f0b7a37dc855328985b1bc564bd730867326a9892
Portable: ef1931dee9f0bee9d4ac0d4a4c014788da18a267297763d9d2a1c5515156ad03
install-update-now now spawns setup with ['/S', '--force-run'] so
Theseus comes back on its own after the silent install completes.
The 0.3.31 rewrite had dropped --force-run under the (correct)
observation that /S alone installs cleanly on our NSIS config; the
user reported the missing post-install relaunch after the
0.3.37 → 0.3.39 auto-update went smoothly but left them looking for
the Start-menu shortcut. --force-run is electron-builder's convention
for auto-launching the app after silent install and puts the flow
back to feel like a one-click restart.
Installer: 8ebde5f307995a364283234078db9e66bc770dcd8f3c559513be1200ea0e8d56
Portable: d14e4cbd8f2e8202f9842c7b7d8e60aa2eb5fe6c57bec1d4f7ffdeef79592331
Bundled release covering three improvements from this session:
DevTools dock — new Settings > General > Developer tools with
three radio choices: Bottom panel (default, Chrome-style), Right
sidebar, Two sidebars. F12 handler reads settings.devToolsDock on
each open so the choice applies instantly.
Scrollbars — every scroll surface Theseus owns (tabs, chrome,
sidebar, popovers, add-on panels) uses a subtle neutral-grey track
and a BCH-green #0AC18E thumb. Modern scrollbar-color on <html>
plus ::-webkit- overrides for width, radius and hover.
captureTab full-page — before Page.captureScreenshot the code now
runs Emulation.setDeviceMetricsOverride to widen the viewport to
the window's content width, so an open sidebar can't clip the
screenshot. Restores metrics in finally.
Installer: c95019b4d3f2a386816caa5323e9aa2de68919d2419367b926eecd93f97c278c
Portable: ed4eb27cdf539d85cf36373744ef9a41510dc2035541a7bf7445f074c6688579
Two independent drag handles replaced with ONE between the URL bar
and the search bar. Zero-sum: bigger URL bar = smaller search bar,
and vice versa. Fixed items (nav, Downloads, extension dock, ⛓ logo)
stay put — .urlwrap + .bardrag + .searchbox now share a .urlsearch
flex container between them, so the drag never displaces anything
outside it.
Verified via static render in the browser pane:
url=default → 60/40 split (533 / 317 px at 1280 total)
url=700px → search shrinks to 157px
url=300px → search grows to 539px
narrow window (data-responsive=1+) → search bar + drag handle
both display:none, URL bar grows to fill
Installer: 0b44b6175a286b6bf7710000a60f54d28e2c3ce061dc852f366f1011ac786d05
Portable: 3599d3df7a0d8a3c8df1f34ab777068c3ef400ca1b88cfdaff6e41a7964730c3
New sidebar tab between Privacy and Extensions groups the two long
system-scoped cards that used to live at the bottom of General:
- Ariadne's Thread (system-wide resolver, install / update / on-off)
- Aegis (built-in wallet, OTA-updated)
General now stops at the collision-policy block. Plug-ins clusters
components that live alongside Theseus with their own lifecycle
controls, which is what those two already are — the shape was hiding
inside a general-purpose bucket.
Ariadne status re-polls on every tab visit (avoids stale "checking…"
after a background poll finished while another section was open).
Aegis card loads on page-init and its "Check for updates" button
stays user-initiated — we don't want a network request every time
the user opens the tab.
Installer: 48a46e503af63d2c6867d4fce9069aae07433e5eb4560d14687e196a6622c55e
Portable: 0525a5a4b6e7f2fc6f53fe005aafaafe783d20cb6db08a97fea9ae68ef520969
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
Same content as the 0.3.31 rewrite, published under 0.3.34 so existing
0.3.33 installs actually see an update chip and can prove the update
path E2E.
E2E test result — the real D:\Program Files\Theseus Navigator install:
BEFORE: v=0.3.33, hash 312cd15b..., mtime 09/08 19:53:04
AFTER: v=0.3.34, hash e59d6da2..., mtime 09/08 22:02:46
Registry updated to point at same D:\ path
Auto-launched via --force-run (23 procs)
Confirms: the earlier "silent no-op" observations were transient
(most likely stale APP_GUID mutex from an orphaned setup process),
not a systemic bug. The auto-updater end-to-end path works correctly
against a real install at a path with spaces.
Installer: 50e1c97dc80c0a19f94a3489abeea30d220d4619959671eedabca796d36c4477
Portable: a75fa06af0d22c936d0ecf74a60b8d71ddc065b38a9df130be5850d75532165a