Commit graph

236 commits

Author SHA1 Message Date
Local Dev
464d83316b fix(aegis): 0.9.10 — say why a wallet can't pair, and stop imports advertising the wrong key tree
Every chipnet wallet in the picker read "can't pair" with the reason
nowhere on screen: the explanatory note only rendered when NOTHING could
pair, so one working mainnet wallet hid it entirely. The cause now rides
on the row itself ("can't pair (WIF import)") and the note appears
whenever any wallet is blocked. A single private key has no chain code,
so there is no xpub for the handshake to send — the text now says that
and points at the two ways out.

Seed-imported wallets stored the full leaf path (m/44'/1'/0'/0/0) in
accountPath, because that is what derived the one address the strip
shows. WizardConnect was handed that as the BIP44 *account* node and
would derive m/44'/1'/0'/0/0/<branch>/<i>: a tree the user holds no keys
in. Pairing looked healthy and every sign request failed with "no path
for input". Same class as the 0.9.7 chipnet mismatch, one level down.

A dapp drops its pairing code from the DOM once connected, so the
commonest empty scan is a page that is already paired. Sending that user
to "open the Connect dialog" points at a dialog the dapp will not show
again; the message now names the existing pairing instead.
2026-09-27 00:01:39 +02:00
Local Dev
e761500a2f fix(aegis): 0.9.9 — address columns share one grid so they line up
Each .warow was its own grid container, so the `auto` amount column
resolved independently per row: a row holding 0.3066756 got a narrower
column than one holding 0.43789841, and the copy button therefore landed
at a different x on every line. Column widths have to be SHARED to line
up, and nothing was sharing them.

The columns are now declared once on a .walist wrapper and every row
inherits them via `grid-template-columns: subgrid`. Column gaps moved to
the parent, since a subgrid takes its gutters in the subgridded axis from
the grid it inherits and would have ignored them on the row. A
@supports fallback pins the amount column to a fixed 104px for any host
without subgrid, which keeps the copy column straight there too.

Measured across six rows with four different amount lengths, at 260,
320, 400 and 520px wide: copy left edge, amount right edge, ticker left
edge and menu right edge all have 0px spread, with no row overflow and
no clipped amount.

0.9.8 claimed this area was verified, but the check only looked for cell
overlap WITHIN each row and never compared the same column ACROSS rows —
which is exactly the defect it missed.
2026-09-24 23:05:08 +02:00
Local Dev
4754fb948b fix(aegis): 0.9.9 — keep the dapp bridge off Trusted-Types sites
The main-world bridge was pushed into every https page as a text script.
Sites that enforce Trusted Types refuse that and report the attempt to
their CSP endpoint — Google's sign-in pages among them, which then have
every reason to call the browser insecure. No dapp lives on those
origins: a static list of the big enforcing sites is skipped outright,
any other origin that rejects the bridge once is remembered and skipped
from then on, and where Trusted Types exist unenforced a policy keeps
the assignment clean.
2026-09-24 22:50:36 +02:00
Local Dev
4c7d8e3f56 fix(aegis): 0.9.8 — address row columns, resizable QR, inline copy
The row's grid template still described the pre-0.9.2 layout — six
columns including an address cell that no longer exists — while the row
renders five. Every cell therefore sat one column left of where it
belonged: copy landed in the label's space, the amount in the old label
column, and the ⋯ in the amount column instead of the edge. That is the
whole cause of "the copy button collides with the amount" and "the last
edit button is not on the edge".

Now five columns for five cells: icon · label · copy · amount · menu,
8px gaps. The label is the flexible one, left-aligned, so it takes the
slack and truncates rather than squeezing the number. Verified at 520,
400 and 300px: no cell overlap at any width, no row overflow, and the
amount never clips — only long labels give way.

The copy glyph was the 📋 emoji, which has no glyph in this platform's
font stack and rendered as a tofu box that read like a stray character
stuck to the balance. Replaced with an inline SVG in both the row and
the address card. Both confirmation flashes now swap innerHTML rather
than textContent, which would have deleted the SVG and left a blank
square.

Address card: the address and its copy button share one row, so the
button sits at the end of the value it copies. The address clamps to two
lines and truncates beyond that instead of growing the card in a narrow
sidebar.

The QR is always visible and the panel is drag-resizable from a grip
under it (pointer events, arrow keys as a non-mouse path, clamped
90–420px, capped against the panel's own width so a size set on a wide
sidebar cannot overflow a narrow one, persisted). That replaces the
0.9.2 show/hide toggle — a size set once beats a binary, and it frees
the row Copy was sharing with a QR button.

drawQr was setting cv.style.width/height, which would have reset the
panel to its intrinsic size on every redraw — i.e. every time the
address changed. It now sets only the backing store and re-asserts the
user's size after drawing.

"Next unused address" is correct at the adapter level on both mainnet
and chipnet (tested: the index advances and the address changes), so the
reported failure is elsewhere. The handler was discarding the error and
flashing a bare "Failed", which is why there was nothing to diagnose; it
now surfaces the real message.
2026-09-24 22:30:10 +02:00
Local Dev
36012e7c26 fix(aegis): 0.9.7 — chipnet WizardConnect advertised the wrong key tree
A chipnet BCH wallet derives from m/44'/1'/0', but the WizardConnect
registration fell back to a hardcoded m/44'/145'/0' whenever the entry
had no explicit accountPath — which is the normal case for a wallet
created through the UI. Mainnet's default happens to be that same
literal, so only chipnet was affected.

The consequence was worse than a failed pairing. Pairing SUCCEEDED, the
handshake carried xpubs for an unrelated key tree, and the dapp then
derived addresses this wallet does not own:

  wallet's real chipnet address : bchtest:qpezx8qkwpjd4e6pd5aang0ve6fctpjvg5ckp2lwu7
  address from the WC xpub      : bchtest:qzvpe3w9rqnszk6mntnef6zv6v94zmfvpc49qkxml4

So the dapp saw an empty stranger's wallet, and anything it built spent
inputs the wallet could not match — signing would fail with "no path for
input". Silent, and only reachable on testnet.

Both registration sites (vault-derived and imported) now take the path
from adapter.snapshot().accountPath, which is by construction the tree
the wallet actually derives its addresses from.

Two wrong turns worth recording. defaultAccountPathFor() takes the coin
CONFIG object, not a chain string, so passing entry.chain returned null
and would have stopped WizardConnect registering at all — strictly worse
than the bug being fixed. chainMeta().defaultAccountPath was no better:
BCH has no coinType in COINS, so it is null for every BCH network. The
adapter is the only component that resolves this correctly, which is why
it is now the source.
2026-09-24 04:56:12 +02:00
Local Dev
f07df2b39e fix(aegis): 0.9.6 — adding a wallet asks create-or-import
"+ Add another BCH" created a fresh wallet on the spot. The old comment
argued that being inside a coin's address list made the intent
unambiguous; it isn't. "Add another BCH wallet" is just as often "bring
in the one I already have somewhere else", and guessing wrong is not
harmless — the user gets an empty new address and has to work out for
themselves why their funds aren't in it.

Adds aegisChoose(), a pick-one sibling of aegisConfirm for branches
where the honest answer is a question rather than a yes/no, and puts it
in front of every path that reached addWallet:

- + Add another <TICKER> in the coin drilldown
- + Add on an unowned coin in the browse picker (now routes to the
  existing New / Import / Connect chooser, with the coin still
  preselected through whichever branch is taken)
- + Add your first wallet on the empty state — the most important one,
  since someone arriving with an existing seed was being handed a
  create-only flow

The empty-state copy claimed "there's no separate seed to import",
which stopped being true when imports shipped and actively told users
the feature they wanted did not exist.

Verified in a rendered panel: the chooser appears, addWallet is not
called until Create is picked, Import opens the import modal, and
Cancel does nothing.
2026-09-23 08:33:29 +02:00
Local Dev
7b9049f6fc fix(aegis): 0.9.5 — WizardConnect signing actually works
Pairing already worked; signing would have thrown on the first request
a dapp ever sent. Found by testing against the real relay and the real
@wizardconnect/wallet library rather than reading the code.

Two bugs in wc-sign.js, both fatal:

- The WC message nests the whole WcSignTransactionRequest under
  `.transaction`, so the tx is at request.transaction.transaction and
  the spent outputs at request.transaction.sourceOutputs. We read
  request.transaction as the tx and request.sourceOutputs as the
  outputs, so tx.inputs was undefined. index.js already read the nested
  request.transaction.userPrompt for the approval dialog, so only the
  signer had it wrong. The flat shape is still accepted.

- generateSigningSerializationBCH takes TWO positional arguments,
  (compilationContext, {coveredBytecode, signingSerializationType}).
  We passed one merged object, leaving coveredBytecode undefined and
  throwing inside libauth. For P2PKH the covered bytecode is the spent
  output's locking script.

Now verified end to end: a two-input transaction spending from two
different derivation paths signs, decodes, and passes
createVirtualMachineBCH().verify() — consensus-valid, with
SIGHASH_ALL|FORKID|UTXOS (0x61) on every input as the protocol
requires.

Also: RelayStatus is an object ({status: "connected" | "reconnecting" |
"disconnected" | "session_deleted"}), and the snapshot read a
non-existent `.kind`, so every connection reported the literal
"[object Object]". Reads `.status` now, uses the documented
getConnections() accessor instead of the private connections Map, and
carries the library's own `label` ("dapp name once known, otherwise
Connecting…"). The panel shows a tag for anything other than connected
— "reconnecting" is the difference between a pairing that will see the
next signature and one that is dead, which was invisible before.
2026-09-23 03:23:41 +02:00
Local Dev
e9335f5e6b chore(aegis): 0.9.4 — merge the coins-row buttons too
0.9.3 merged ✎ and 🗑 into one ⋯ in the coin drilldown but left the
coins summary row with the old pair, so the two views disagreed about
the same idea. Both now carry a single ⋯ opening the manage modal.

Found by rendering the panel against stubbed host state over HTTP
rather than reading the diff — the file:// preview never executes
panel.js, so earlier checks could only confirm the markup existed, not
that it drew correctly.

Drops the now-unreachable removeWalletWithConfirm helper, the
data-wremove handler and the .wactdel style that went with them.
2026-09-23 03:05:22 +02:00
Local Dev
4956f8f6c1 chore(aegis): 0.9.3 — one row shape, assets for every coin
Address row is now icon · label · amount · one button. ✎ and 🗑 were two
controls for what is really one idea ("change this wallet"), and the
manage modal already holds rename, derivation path AND remove — so a
single ⋯ opens it. That also stops Remove sitting one stray click away
from Rename. Default/legacy wallets show a lock instead.

The per-address amount is back unconditionally: 0.9.2 hid it when a coin
had one address, but the row reads as a breakdown and the gap looked
like missing data. The duplicate that actually mattered — the drilldown
subtitle — stays gone.

The Assets card only ever had branches for SOL, BCH and TRX, so ETH fell
through to a hidden card despite the adapter returning tokens in the
same shape. The generic branch is now keyed on the DATA rather than a
list of chain ids: any chain whose snapshot carries `tokens` renders,
which means ETH works today and a chain added later works for free. Only
the label ("TRC20" / "ERC20" / "Tokens") varies by chain.

Dropped the "Pick a coin" title row from the coin picker — the search
field's own placeholder already says what the pane is, so the title was
a line of chrome restating it and pushing the list down. Search and
close now share the top row.

Token history stays in History despite the request coming from tokens
being mistaken for transactions: a wallet that had only ever moved USDT
still showed an empty history without it.
2026-09-23 02:49:05 +02:00
Local Dev
1be352dad6 chore(aegis): 0.9.2 — Receive tab reorder, one balance, token history
Receive was ordered QR → address → tokens, so 200px of always-on QR sat
above the thing people came for and pushed the asset list off screen.

- Address first, with Copy and a QR button; the QR expands inline and
  the choice sticks, because someone who receives by QR wants it every
  time and someone who copies never does.
- Explorer and Faucet moved up to the header status row. They act on the
  selected wallet, not on the act of receiving, and down there they
  competed with Copy for the one row that gets used.
- Assets card renamed from Tokens and now leads with the native coin, so
  "what does this wallet hold" is one list rather than two places.
- "+ Add another <TICKER>" moved below the address list.

The balance appeared three times — header, drilldown subtitle, address
row. Now once in the header; the subtitle keeps only the per-unit price,
and the per-address amount returns when a coin actually has more than one
address to compare. The address row drops its truncated address (the full
one is at the top of Receive) and keeps the wallet name.

The per-address asset list added in 0.8.8 duplicated the Assets card and
is removed — assets live in one place.

Token amounts were unreadable: an 18-decimal balance rendered as
60000000.000000005435817984. Capped to 8 decimals with thousands
separators, exact value on hover.

A symbol claimed by more than one contract now carries a LOOK-ALIKE tag.
The test wallet holds four different contracts all calling themselves
"Test USDT" — spam mints borrowing a trusted ticker so a careless send
lands on the wrong one. We can't tell which is genuine, so we mark every
member of the clash rather than guessing.

History showed native transfers only, so a wallet that had only ever
moved USDT looked empty. TRC20 transfers are merged in newest-first, each
carrying its own decimals and ticker (rendering a token against the
chain's scale would be off by orders of magnitude). Both feeds are on by
default; the checkboxes narrow rather than opt in, and the last one
checked can't be unchecked into an empty list.
2026-09-23 01:17:33 +02:00
Local Dev
7a71b08cf7 fix(aegis): 0.9.1 — an unset custom RPC no longer becomes the text "undefined"
Mounting an imported TRX/ETH/SOL wallet read the optional custom RPC as
String(stored || undefined), so a wallet with no override got the literal
"undefined" as its server: every history and token fetch went to
"undefined/v1/accounts/…" and the panel showed "undefined" under the
balance. Only a real https URL overrides the network default now, and the
adapter itself rejects anything that does not look like one.
2026-09-23 00:24:42 +02:00
Local Dev
8174fccba0 feat(theseus+aegis): WizardConnect auto-detection — wiz:// links + page scan
Completes the three detection paths. The injected provider shipped in
0.8.8; these two needed host support, because nothing in the add-on API
could reach the active tab's content (captureTab is pixels, not DOM).

wiz:// links (main.js)
  A click on a wiz:// anchor is intercepted in will-navigate and in the
  window-open handler (target="_blank" lands there instead), and routed
  to the wallet with the offering page's origin attached, so the
  approval names the real site. The tab never navigates. This needs
  nothing from the dapp beyond rendering the URI as a link, so it works
  for third-party dapps that will never adopt a Silent Mode API.

scan-page capability (addons-host.js + main.js)
  New capability backing api.scanActiveTabForUris({scheme, limit}).
  Deliberately NOT a "read the page" API: the host runs the match and
  returns only the URIs found, so an add-on holding this still cannot
  see page text, markup or form values. It sits well below page-inject
  on the trust ladder — it learns that a page offers a wiz:// code and
  nothing else. Scheme is validated against [a-z][a-z0-9+.-]* and the
  result count is capped.

  The matcher also accepts WizardConnect's QR-alphanumeric spelling
  (WIZ://%3FP%3D…), which is frequently the only form present when a
  dapp renders its pairing code as a QR, and decodes it. Verified
  against the SDK: decodeKeyExchangeURI accepts standard, QR-raw and
  QR-decoded alike.

  Regex sources are built host-side and passed as JSON rather than
  assembled inside the injected string — hand-escaping backslashes and
  quotes through two levels of literal was both wrong on the first
  attempt and unreviewable.

Aegis
  Declares scan-page, adds the wcScanPage handler and a "Scan page"
  button next to Connect. A scan fills the URI field and stops there
  rather than pairing outright: the user still chooses which wallet
  signs and still presses Connect, because a scan that silently paired
  would carry far more consequence than the button implies. Older hosts
  without the capability get a clear "update Theseus" message instead of
  a dead button.
2026-09-23 00:16:49 +02:00
Local Dev
64bc26ecf6 chore(aegis): 0.8.9 — ETH/SOL imported history + tokens, centred setup screen
Completes the parity work 0.8.7 started for Tron. Imported ETH and SOL
wallets showed a native balance and nothing else, because the JSON-RPC
endpoints they poll have no history or token concept at all.

- ETH history + ERC-20 balances via Blockscout, which needs no API key
  (Etherscan V2 does). Mainnet RPC moves off eth.llamarpc.com, which was
  answering 525 with an HTML error page — that parsed as a JSON error and
  showed as a 0 balance.
- SOL history via getSignaturesForAddress and SPL balances via
  getTokenAccountsByOwner, both keyless on the public RPC.

Three things the live testing turned up:

- A Blockscout mempool entry is {result:"pending", status:null}. Reading
  that as "not ok, therefore failed" showed pending sends as failures.
  Now carries a distinct pending state through to the row.
- History `delta` is now a number, a decimal string, or null. ETH wei
  needs the string (18 decimals overflows a JS number, and Math.abs was
  silently rounding it); Solana's signature feed carries no amount at
  all, and null >= 0 is true, so unknown amounts were rendering as a
  "+" that claimed a receive we cannot verify. Unknown now renders as a
  neutral row instead.
- A real address came back with 855 ERC-20s and 3078 SPL mints, nearly
  all airdrop spam, some with blank, zero-width or bidi-override
  symbols that render as an empty row borrowing trust from its
  neighbours. Token text is sanitised and lists are capped at 50, sorted
  so named tokens survive the cap.

Also: the first-run setup screen forced text-align:left on the form, so
its helper copy ran ragged under a centred mark, title and description.
The form now inherits the centred alignment; the mnemonic box stays
left-aligned on purpose, since centring wrapped seed words makes them
harder to check.
2026-09-23 00:05:14 +02:00
Local Dev
1b74b10fc5 chore(aegis): 0.8.8 — coin drilldown, per-address assets, WizardConnect from the page
Wallet strip:
- Clicking a coin opens that coin's page (addresses, price, totals, back
  and close) instead of only flipping the selection and leaving the list
  sitting there. The page already existed but was reachable only via the
  small count chip.
- The per-coin second action was a gear that selected the wallet and
  opened the global Settings tab — the same destination for every coin,
  so it read as a per-coin control that wasn't one. It is now Remove,
  behind a confirm, with the default/legacy wallet showing a lock
  instead since it gates legacy funds.
- Each address in the drilldown can expand to show what THAT address
  holds: TRC20/SPL via the adapter's tokens, BCH CashTokens via
  tokenBalances. walletSummary now carries both per wallet, so the view
  no longer has to borrow the selected wallet's assets.

WizardConnect — the Connect pane was effectively unusable:
- The locked-vault branch told the user to unlock and gave them nothing
  to click. It is reachable without the lock screen ever appearing,
  because a mounted imported wallet makes overallPhase read "ready".
  It now carries the same unlock form the lock screen uses.
- Imported BCH wallets were never registered with the WC manager —
  startForWallet ran only in the vault-derived mount branch. They mount
  as ready, so they appeared in the "Sign with" picker and then failed
  on pair. They now register from their stored seed. WC derives a child
  key tree, so single-key (WIF) imports genuinely cannot pair; those are
  disabled in the picker with the reason, rather than failing on click.
- Adds window.wizardconnect so a dapp can hand over the wiz:// URI it
  already generated instead of making the user copy it between tabs.
  The protocol is Nostr-relay pairing designed for phone-scans-QR, and
  the SDK has no in-page discovery at all, so this is our own surface:
  connect() + isReady(), plus a wizardconnect:announceProvider event
  shaped like EIP-6963 so several WC wallets can coexist. Pairing always
  goes through the approval modal; the URI is validated before any UI
  shows, and the wallet never reads the page to find one.
2026-09-22 23:08:16 +02:00
Local Dev
8785ecc7cf chore(aegis): 0.8.7 — imported Tron wallets get history + TRC20 tokens
Imported Tron wallets pointed at api.nileex.io, which only serves the
/wallet/* JSON-RPC family and 404s all of /v1/. That REST family is
where transaction history and the trc20 balance map live, so an
imported Nile wallet showed a native balance and nothing else. The
built-in Tron adapter was already on nile.trongrid.io, which is why
only imports were affected.

Switches the imported Nile endpoint to nile.trongrid.io and fills in
the two features that were never implemented for imported account-
model wallets:

- History via /v1/accounts/<addr>/transactions, with the signed delta
  computed by comparing owner_address against the wallet's own address
  in 41-hex form (the feed returns hex regardless of visible:true).
- TRC20 balances via /v1/accounts/<addr>, joined against token_info
  harvested from recent trc20 transfers to recover symbol + decimals.

Both are best-effort so a chain with no keyless feed can't blank a
wallet whose balance fetch succeeded. Contracts with no registry entry
render as "Unknown token" with a raw amount rather than a number
invented from assumed decimals, and named tokens sort above them so
airdrop spam can't bury real holdings.
2026-09-22 21:52:54 +02:00
Local Dev
30fbe73d21 chore(aegis): 0.8.6 — in-panel confirm/alert instead of native dialogs
window.confirm/alert render as chrome-owned, Theseus-branded OS boxes
outside the sidebar, which breaks the illusion that Aegis is one
coherent surface — and they can't carry an icon, a danger-styled
button, or formatted copy.

Adds aegisConfirm() / aegisAlert(): the same overlay shell the manage
and import modals already use, resolving like confirm() so callers
just await it. Escape cancels, Enter confirms, click-outside cancels.

Swapped at all four confirm sites (remove wallet from the picker,
remove wallet from Settings, remove PIN, sign out) and all seven
alert sites.
2026-09-22 21:44:03 +02:00
Local Dev
f3fb31e651 chore(aegis): 0.8.5 — Coin-Spectrum price parser reads body.asset.price_usd
Every Coin-Spectrum poll silently returned an empty map because we
were reading body.price_usd (top-level) while the API wraps its data
under body.asset. Every chain's Number(undefined) came back NaN, so
Settings › Prices showed "✓ 0 coins" and majority-vote reconciliation
had one fewer source than intended.

Now reads body.asset.price_usd (with a top-level fallback in case the
API is ever flattened).
2026-09-22 21:31:43 +02:00
Local Dev
e03c728116 chore(aegis): 0.8.4 — network chip on its own row + coin-catalogue search
Two follow-ups on 0.8.0's currency picker after a testing pass:

- The network label was a tiny gray suffix next to the wallet name in
  the header, so "which network am I on" wasn't obvious at a glance.
  Now it's a separate row of one clickable chip under the coin ticker;
  clicking jumps into the browse:chain view where the network strip
  actually switches network.

- Clicking the header opened a "Pick a coin" pane that only listed
  coins the user already had a wallet for — a first-time user with a
  single BCH wallet would see one row and no way to add more. Now it
  shows every supported coin behind a search box; owned coins jump to
  the wallet list, unowned coins jump straight into the create-wallet
  flow with that coin's network group pre-expanded.
2026-09-22 21:19:36 +02:00
Local Dev
b32b353db7 chore(aegis): 0.8.3 — default BCH explorer switches to bchexplorer.cash
Blockchair's BCH explorer is slow and ad-heavy; bchexplorer.cash is
the Bitcoin Cash community's own instance, faster on tx pages and
with a proper mempool view. Same /tx/ + /address/ path scheme
(address takes the bitcoincash: prefix as-is), so no other code has
to change.

Chipnet explorer stays on chipnet.imaginary.cash — bchexplorer.cash
is mainnet-only.
2026-09-22 20:44:44 +02:00
Local Dev
cb7ca53b01 chore(aegis): 0.8.2 — sectioned Settings tab
Settings grew tall enough that the user had to scroll past a dozen
cards to reach Prices, Sites or About. Split it into six named
sections (Security, Session, Wallet, Prices, Sites, About) with a
chip nav row at the top; only one section is visible at a time and
the choice persists across restarts.

Adds an About card that names the wallet, the aegis.x front-door
site and the silentmode.st umbrella, so support triage has a
one-click way to reach either from within the panel.

Includes the accumulated 0.7.x-0.8.1 wallet work that was already
shipping on OTA (CashTokens/BCMR, imported-wallet spend, siascan
integration, consolidate, currency picker, footer update chip).
2026-09-22 20:37:28 +02:00
Local Dev
f46e9112b7 chore(theseus): 0.3.47 — plug-in category + panel-driven addon self-update, aegis 0.6.31
Theseus core:
- addons-host: manifest.category ("plugin") propagates through snapshot(); new
  addon API surface checkAndStageSelfUpdate() + restartApp() so a plug-in
  can offer in-panel "update now → restart to apply" without pushing the
  user to Settings.
- main.js: wires the two new hooks into the AddonHost constructor.
- settings.html: Extensions listing filters out category==="plugin"; those
  add-ons live in Plug-ins instead, single source of truth.

Aegis 0.6.31:
- BTC picker trimmed to Signet only; testnet3 hidden (adapter kept so any
  existing wallet still loads).
- Wallet strip groups by chain, not chain:network; ticker gets a ▾ chevron
  and a dropdown listing every subnetwork with its own totals. Mainnet
  reads as the plain ticker; testnets carry a small Chipnet/Signet/Sepolia
  pill inline.
- Per-unit price sits directly under the ticker; amount + fiat mirror on
  the right — one glance covers name/price/holding/value.
- + Add and ⋯ More promoted from the strip into the header's action row,
  next to the new ✎ chip (was the redundant top ⋯). Duplicate "Manage
  current wallet" entry removed from the More menu.
- Footer update chip is a two-step flow via the new API: stage → restart.
  Falls back to opening Settings on any Theseus that lacks the hooks.
- Manifest declares "category": "plugin".
2026-09-14 02:30:51 +02:00
Local Dev
d7d127d7b4 fix(theseus/bns): failed content fetches get a real error page naming the upstream and cause
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.
2026-09-12 09:09:06 +02:00
Local Dev
1596463b70 chore(theseus): 0.3.46 2026-09-12 00:31:47 +02:00
Local Dev
3c0f13c1e5 fix(theseus/updater): run the installer only after the app has exited, via a detached batch helper with self-heal
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.
2026-09-12 00:31:46 +02:00
Local Dev
ed48646c71 feat(theseus/chrome): "Open link in new window" on the link context menu
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.
2026-09-10 22:25:19 +02:00
Local Dev
43d0021bf6 docs(theseus): add-on update channel lives on Sia, not the VPS — publish with sia-upload
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.
2026-09-10 22:25:18 +02:00
Local Dev
ee5e53512a docs(theseus/prompts): tool-agnostic phrasing in session-prompt templates 2026-09-10 22:21:19 +02:00
Local Dev
50eb143b0a chore(theseus): 0.3.45 2026-09-10 00:12:35 +02:00
Local Dev
c9dbcbde86 fix(ariadne): Theseus on/off toggle never reached the tasks; updater read a stale manifest; uninstall left NRPT rules
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.
2026-09-09 23:04:23 +02:00
Local Dev
56eff58fda feat(theseus/chrome): page zoom, 80/20 address/search ratio, collapsed dock renders icon images
- 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.
2026-09-09 23:04:22 +02:00
Local Dev
7b8539fb8d feat(theseus/screenshot): 0.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder
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.
2026-09-09 22:34:47 +02:00
Local Dev
8eda0433d7 feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls
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.
2026-09-09 22:01:06 +02:00
Local Dev
744574ba96 feat(theseus/screenshot): 0.6.2 — Copy/Save move to toolbar, Discard, right-anchored topbar
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.
2026-09-09 11:46:21 +02:00
Local Dev
8a99c0959c feat(theseus/chrome): Ariadne's Thread registry menu, address-bar overflow fix, full-width link pill
- 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.
2026-09-09 11:40:46 +02:00
Local Dev
c9a3db26ce fix(theseus/boot): paint the toolbar first — stop gating startup on chrome.html's load event
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.
2026-09-09 11:40:45 +02:00
Local Dev
bf6bcfade2 feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X
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.
2026-09-09 10:55:21 +02:00
Local Dev
35ec8a3904 fix(theseus/sidebar): visible-at-rest separator between the tab area and the sidebar
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.
2026-09-09 10:50:30 +02:00
Local Dev
0f39429d63 chore: push-split helper for per-project forge repos + Windows Sandbox installer config
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.
2026-09-09 10:33:23 +02:00
Local Dev
992c02ea89 feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect
Aegis Wallet 0.4.4 → 0.6.1:

- Vault lifecycle from the wallet gate. The locked / not-yet-created states
  now show a master-password form (with optional BIP39 mnemonic on setup)
  instead of redirecting users to Settings › Passwords. New
  api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by
  the existing "vault-derive" capability. api.openSettings(section) also
  added; settings.html honours a #section hash on open.
- Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44
  path or a WIF; the cashaddr is derived in the add-on, the signer material
  goes to a separate wallet-imports.enc via api.vault.imports {list, add,
  remove, signer}. Argus password-vault gains createImports / unlockImports /
  saveImports with its own KDF salt so the imports key is disjoint from the
  passwords key. lib/chain-bch-imported.js is a single-address Electrum
  adapter; spend support is deferred to M.1b.
- Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted
  in add-on storage. Fiat lines under balances, in the wallet picker, and a
  portfolio total when 2+ wallets are open. Settings tab is now reachable
  while the vault is locked so the toggle is always available.
- WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js).
  @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay
  on the right side of LGPL §4d. Sign requests go through approvalModal and
  are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS.
- DGB adapter load is now soft-fail: when Aegis runs from userData/addons the
  bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of
  taking the whole add-on down.
2026-09-09 10:33:21 +02:00
Local Dev
7405e444e7 feat(theseus/screenshot): 0.6.0 — crop + mosaic redaction, right-anchored sidebar controls, real shutter+print sounds
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.
2026-09-09 10:24:03 +02:00
Local Dev
882de1654f fix(theseus/net): sec-ch-ua client hints look like stock Chrome (Brave-style)
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.
2026-09-09 03:27:41 +02:00
Local Dev
124325673f feat(theseus/settings): per-extension update info inline on the card
The Extensions page had a "Pending updates" strip at the top listing the
staged versions AND a "Check for updates" button that dumped a summary of
every extension's status into a global status blob just below the button.
Two places to look for what a single card was doing.

Fold both surfaces into the extension card itself:
- Each card grows a small update line under its description: green ↻
  "Update vX.Y.Z staged — restart to apply" when a staged tarball is
  waiting, red "Update failed" (with the addon-updater's detail) when
  the last check-updates run couldn't advance the version, plain "Up to
  date" when it could and there was nothing newer.
- The top strip is gone. The "Check for updates" button now just prints a
  one-line summary (N staged / N failed / all up to date) — the detail
  lives on each card.
- listStagedAddonUpdates fires on tab visit and after Reload, so the
  card badge reflects the background poll without needing the user to
  click Check.
2026-09-09 02:43:28 +02:00
Local Dev
0d583fb749 fix(theseus/addons): sidebar auto-restore on tab switch + more forgiving tar
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.
2026-09-09 02:38:32 +02:00
Local Dev
71b803e020 fix(theseus/screenshot): 0.5.1 — hide "Loading capture" for real + centre the tool bar
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.
2026-09-09 02:33:45 +02:00
Local Dev
c64e81a959 toolbar 30% ratio floor + placeholder-safe search + brand map for .x names
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.
2026-09-09 02:25:12 +02:00
Local Dev
dcbe4d55f9 fix(theseus/bookmarks): brand-case .x TLD labels (theseus.x → Theseus.X)
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'.
2026-09-09 02:17:27 +02:00
Local Dev
8649bb97ea docs(theseus): draft wallet multi-account amendment
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.
2026-09-09 02:17:01 +02:00
Local Dev
30734847e9 fix(theseus): tab context menu goes native + bigger uniform bookmark chips with title-only labels
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.
2026-09-09 02:05:46 +02:00
Local Dev
ba806005fa fix(theseus/updater): re-add --force-run so Theseus auto-relaunches after silent install
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.
2026-09-09 00:58:26 +02:00
Local Dev
81d276f655 feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
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.
2026-09-09 00:56:41 +02:00