Commit graph

105 commits

Author SHA1 Message Date
Local Dev
5c2f0b78fc feat(theseus/aegis): EIP-3085 wallet_addEthereumChain + EIP-3326 switchChain
Aegis now handles the standard MetaMask try-switch-then-add flow. A dapp
that wants to route through Polygon (or Base, or Arbitrum, or any other
EVM the Silent Mode user hasn't added yet) calls the pair the industry
already wrote for it — Aegis registers the chain, provisions a wallet on
it under the same vault seed, auto-connects the origin, fires
chainChanged, and hands the dapp back a provider pointed at the new
chain. No sidebar detour, no Custom RPC copy-paste. Users still see
every chain in the picker post-add and can revoke sites in Settings.

- lib/chain-eth.js: EthWallet accepts a customNetwork override
  ({id, label, chainId, defaultRpc, explorerTx, explorerAddr, ticker}).
  When present it replaces the NETWORKS lookup so mainnet+Sepolia
  ship built-in and every EIP-3085 chain is a runtime override the
  addon persists. The ticker flows into snapshot() so the send
  approval reads MATIC / BNB / whatever the chain's native currency is,
  not a hardcoded ETH.
- index.js customEthChains storage: `{[chainId]: {chainName, rpcUrl,
  explorerTx, explorerAddr, ticker, addedAt, addedByOrigin}}`.
  Persisted under api.storage.customEthChains, so an added chain
  survives Theseus restarts. chainMeta("eth", "custom-<chainId>")
  synthesizes the meta from storage so the panel renders custom
  chains without needing them in COINS at module-load time.
- eth.addChain handler (EIP-3085): approval overlay shows chain
  name, decimal + hex chain id, native ticker, RPC and explorer
  URLs (the phishing-signal quartet). On approval, persist config +
  create wallet with a custom-<chainId> network + auto-grant the
  origin readAddress on this chain. No-op success if the chain is
  already added.
- eth.switchChain rewritten to be EIP-3326 correct: look up any
  ready ETH wallet whose adapter reports the requested chainId,
  make it the selected wallet, fire chainChanged. When no wallet
  matches, throw with .code = 4902 (the standard 'chain not
  added' code) so wagmi / RainbowKit / any 3326-aware dapp does
  the fallback wallet_addEthereumChain call in the same click.
- eth.state handler: cheap {address, chainIdHex, networkVersion}
  peek for the origin's currently-connected wallet (no approval,
  no key access). The main-world bridge calls it after every
  switch/add to emit chainChanged + accountsChanged locally — the
  events MetaMask fires and RainbowKit listens for.
- wallet-inject.js: routes wallet_addEthereumChain via
  eth.addChain, preserves the 4902 code across the postMessage
  boundary on switch failures, calls pullEthStateAndEmit() to fire
  the post-switch/add events.
2026-09-07 22:26:27 +02:00
Local Dev
3d3a735eb1 feat(theseus/aegis): EIP-712 signTypedData_v4 + Solana multi-signer send
Two follow-ups to the dapp bridges. Both change wire shape only — no new
UI, existing wallets keep signing byte-identically for the flows they
already covered.

- lib/eip712.js: full EIP-712 typed-data encoder — encodeType with
  alphabetically-sorted transitive sub-types, typeHash, encodeValue for
  string / address / bool / uint*/int* (any width) / bytes / bytesN /
  nested structs / dynamic and fixed arrays, hashStruct recursion,
  digest = keccak256(0x19 || 0x01 || domainSeparator || hashStruct).
  Verified against the spec §"Ether Mail" test vector — hashStruct on
  both the domain and the message plus the final digest all match the
  canonical values byte-for-byte (see scratchpad/verify-eip712.mjs).
- chain-eth.js: exposes signTypedDataDigest(digest32) that signs the
  precomputed digest with r||s||v (v = 27+recid), the same envelope
  personal_sign uses. Aegis computes the digest server-side (in the
  addon) so a bug in the encoder can't be tricked by a malicious dapp
  into signing over data the user never saw.
- index.js: eth.signTypedData handler shows domain (name · version ·
  chainId), primary type, and a truncated JSON preview of the message
  in the approval overlay — every classic phishing signal (mismatched
  domain, unexpected primary type) is in front of the user before they
  hit Sign. Accepts either an already-parsed typedData object or the
  JSON-string form older MetaMask specs used.
- wallet-inject.js router: eth_signTypedData_v4 (and _v3 for the same
  payload shape) route to eth.signTypedData. v1's flat "type[]" form
  is unwired — dapps that still use v1 should upgrade.
- Solana signAndSend: bridge now passes the FULL wire (from
  tx.serialize({requireAllSignatures:false, verifySignatures:false}))
  instead of just the message. The addon parses compact-u16 signature
  count, finds this wallet's pubkey in the message's account-key list,
  signs the message, and patches ONLY its own slot in the signature
  array — any partial signatures the dapp had already filled with
  tx.partialSign() (session keys, escrow co-signers, permissioned
  authorities) are preserved. Multi-signer flows work now; single-signer
  is the degenerate case of sigCount=1.
- Approval overlay for sol.signAndSend now shows required-signer count
  and the wallet's slot index so multi-signer requests are visibly
  distinct from a plain single-signer send.
2026-09-07 22:19:51 +02:00
Local Dev
081e0cad5b feat(theseus/aegis): EIP-1193 + Solana wallet-adapter bridges; BTC signet
Aegis now integrates with the two dapp-wallet APIs the wider ecosystem
actually uses — MetaMask-style window.ethereum for Ethereum, Phantom-style
window.solana for Solana — plus BTC signet as a third Bitcoin network
alongside mainnet + testnet3.

- wallet-inject.js: adds a main-world bridge, installed via a one-shot
  <script textContent=…> appended to <head> and immediately removed.
  Electron's contextBridge shallow-copies args and strips methods, which
  means BCH- and Tron-shaped params (plain data) work in the isolated
  world but Solana's wallet-adapter dapps — which pass @solana/web3.js
  Transaction objects and expect .serializeMessage()/.addSignature() to
  fire on them — need code that lives in the same world as the dapp.
  Bridge talks back to the isolated world via window.postMessage on a
  namespaced envelope (aegisTag = "aegis-" + addonId), which forwards to
  theseus.invoke. Same pattern MetaMask + Phantom use.
- window.ethereum (EIP-1193): request({method, params}), on(),
  removeListener(), chainId, networkVersion, selectedAddress. Handles
  eth_requestAccounts, eth_accounts, eth_chainId, net_version,
  personal_sign, eth_sign, eth_sendTransaction, wallet_switchEthereumChain
  (rejects with "use the Aegis picker"), wallet_addEthereumChain
  (rejects, chains come from Settings), wallet_get/requestPermissions.
  Every other eth_*/net_*/web3_* method passes through to the wallet's
  configured RPC via a new eth.rpc handler. EIP-6963 announceProvider
  event fires so wagmi / RainbowKit / any 6963-aware dapp discovers
  Aegis alongside MetaMask instead of racing for window.ethereum.
- window.solana (wallet-adapter shape): connect(), disconnect(),
  publicKey (with toString/toBase58/toBytes/equals — the PublicKey
  interface dapps check), signMessage(u8) → {publicKey, signature: u8},
  signTransaction(tx) → mutates + returns the same tx with the
  signature added, signAndSendTransaction(tx) → returns {signature: txid},
  signAllTransactions([tx]), request({method, params}). isPhantom flag
  set true so dapps that gate on it pick us. on/off events for connect
  / disconnect / accountChanged.
- Handlers in index.js registerPageMessages: eth.requestAccounts,
  eth.personalSign, eth.sendTransaction, eth.switchChain, eth.rpc,
  sol.connect, sol.signMessage, sol.signAndSend. Every write path is
  per-origin gated + goes through api.approvalModal with the wallet
  label + network in the row list so the user always knows which
  Aegis wallet is about to sign.
- Signet added to chain-btc.js — signet shares testnet3's address
  format and SLIP-44 coin type (BIP-325 only changed consensus/signing),
  so bitcoinjs-lib.networks.testnet handles derivation unchanged. Only
  the electrum pool (aranguren + wakiyamap) + explorer (mempool.space
  /signet) + faucet (signetfaucet.com) differ. Registered as
  btc:signet in COINS with per-network coinType lookup.

Known limits (follow-ups in the same shape as existing chains):
- SOL signAndSendTransaction is single-signer only; dapps that combine
  the wallet's sig with co-signer sigs need the wire assembled on the
  dapp side.
- ETH eth_signTypedData_v4 (EIP-712) is not wired — the handler set
  covers personal_sign only.
2026-09-07 22:08:56 +02:00
Local Dev
b3e90a3ad5 2026-09-07 21:55:44 +02:00
Local Dev
af8e167120 feat(theseus/aegis): BTC address-family picker (BIP44/49/84/86 + Taproot)
BTC now matches DGB's family selector: pick BIP44 (1…), BIP49 (3…),
BIP84 (bc1q…, default) or BIP86 Taproot (bc1p…) from Settings, on
mainnet or testnet3 (paths shift coin type 0 → 1 automatically).

- lib/chain-btc.js: paymentFor(purpose, node, network) returns the
  right bitcoinjs-lib payment (p2pkh / p2sh(p2wpkh) / p2wpkh / p2tr)
  keyed off the derivation path's purpose. WalletKeys.entry captures
  the family, redeem script (BIP49) and internal x-only pubkey
  (BIP86) alongside the standard script/address fields.
  bitcoinjs.initEccLib(ecc) is called once at load so p2tr resolves.
- Registry: BTC + DGB address families are purpose-only now; a
  helper (addressFamiliesFor / defaultAccountPathFor) computes the
  concrete m/PURPOSE'/COIN'/0' per (chain, network) — coin type
  {mainnet:0, testnet:1} for BTC, always 20 for DGB. chainMeta
  expands the list so the panel doesn't need per-chain knowledge.
- Panel: #btcSettings block mirrors #dgbSettings (family select →
  path input auto-fill → Apply). The family-select listener + the
  fillFamilyPicker() helper are shared between DGB and BTC — the
  DOM prefix is the only per-chain input.
- Send is wired for BIP84 (default) and BIP49 (adds redeemScript to
  the PSBT input). BIP44 (needs nonWitnessUtxo prev-tx fetch) and
  BIP86 (needs tap-tweaked signer) throw a clear "not yet in this
  rev — sweep to BIP84" error so users hit it at plan time, not at
  broadcast time. Receive works on all four families today.
- Verified all four families derive the canonical BIP44/49/84/86
  spec test vectors for the standard abandon×11 mnemonic — see
  scratchpad/verify-btc-families.mjs. Byte-identical to the BIPs.
2026-09-07 21:23:15 +02:00
Local Dev
a0a22bc69a feat(theseus/aegis): Bitcoin adapter (mainnet + testnet3, BIP84 native SegWit)
Seven coins across twelve networks now — BTC joins the shipping roster.

- lib/chain-btc.js: BIP84 native SegWit — m/84'/0'/0'/0/x → bc1q…
  (mainnet), m/84'/1'/0'/0/x → tb1q… (testnet3). Reuses the exact same
  stack the DGB adapter already pulls in: bitcoinjs-lib for network
  params + payments.p2wpkh + PSBT, bip32 for HD derivation, ecpair for
  the Signer interface, ecc (@bitcoinerlab/secp256k1) for message-sign
  recoverable sigs. No new npm deps.
- Backend: same lib/electrum.js Aegis uses for BCH and DGB — plugged
  into a public Bitcoin ElectrumX pool (blockstream.info, lu.ke,
  grey.pw) for mainnet and aranguren.org / blockstream.info:993 for
  testnet3. Send flow: PSBT build + per-input signInput +
  finalizeAllInputs + broadcast. BIP-137 recoverable message signing.
- Registered as btc:mainnet + btc:testnet in COINS with the orange
  Bitcoin disc SVG logo. Mount case mirrors DGB (accountPath honored,
  so switching to m/44'/0'/0' or m/49'/0'/0' via the setAccountPath
  message gives legacy 1… or wrapped-segwit 3… — same one-line UI plumb
  as the DGB address-family selector, deferred to a follow-up).
- Panel: sat as the small-unit label, bitcoin: BIP21 QR payload,
  chain-aware send placeholder ("bc1q…" mainnet / "tb1q…" testnet).
- Verified: BIP84 spec test vector — abandon×11 mnemonic derives
  bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu at m/84'/0'/0'/0/0
  (byte-identical to the vector in the BIP text). Testnet variant
  produces tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl at m/84'/1'/0'/0/0
  (cross-checkable on iancoleman.io/bip39 with coin BTC Testnet).
2026-09-07 21:15:45 +02:00
Local Dev
65c306d553 feat(theseus/aegis): Ethereum + Solana adapters, DGB address-family picker, Aegis-branded shield
Multi-currency coverage matches what aegis.x has been advertising: BCH,
TRX, SC, DGB, ETH, SOL — six coins, two-step coin/network picker for
each. Panel logos, favicon and fallback all read as Aegis.

- Ethereum (lib/chain-eth.js): mainnet + Sepolia. BIP44 m/44'/60'/0'/0/0
  → secp256k1 → EIP-55 checksummed hex address (verified against
  MetaMask's canonical abandon×11 vector 0x9858EfFD23…4EcaEda94). JSON-RPC
  backend (Cloudflare mainnet, PublicNode Sepolia by default; per-wallet
  override). EIP-1559 send with an inline RLP encoder + secp256k1
  recoverable sign; broadcast via eth_sendRawTransaction. personal_sign
  message signing follows the \x19Ethereum Signed Message:\n prefix.
- Solana (lib/chain-sol.js): mainnet-beta + devnet. SLIP-0010 ed25519
  derivation at m/44'/501'/0'/0' (all-hardened), base58 address (@noble
  ed25519). SLIP-0010 layer verified against spec Test Vector 1 in
  scratchpad/verify-slip10.mjs. Native SOL transfer via the system
  program with compact-u16 message serialization + ed25519 sign +
  sendTransaction. Devnet gets a faucet.solana.com link in Receive; the
  panel appends ?cluster=devnet when opening the explorer.
- DGB address family selector (lib/chain-dgb.js already carried the
  paths): the Settings block now shows a Native SegWit / Taproot /
  Wrapped SegWit / Legacy P2PKH picker. Selecting a family auto-fills
  the derivation-path input with that family's default; Apply
  rebuilds the wallet against the new path. Address families exposed
  via chainMeta.addressFamilies so the panel can render them from data.
- Panel branding: inline SVG shield (hexagonal aspis, same silhouette
  as the aegis.x hero) replaces the "?" fallback in logoSvg() and is
  what the header shows before a wallet is selected. Data-URI favicon
  wired into panel.html so the Theseus sidebar tab icon reads as Aegis
  rather than a chain-specific coin mark.
- QR payloads now follow each chain's own URI scheme (BIP21 for BCH/DGB,
  EIP-681 for ETH, Solana Pay for SOL) so external scanners route the
  scan to the right wallet.

Not shipped: EIP-1193 provider (window.ethereum) and wallet-adapter
protocol (window.solana). The signing paths exist; only the page-inject
bridge glue is missing. History for ETH/SOL is also empty in this rev —
both need indexer plumbing (Etherscan V2 for ETH, getSignaturesForAddress
+ getTransaction pagination for SOL).
2026-09-07 20:31:27 +02:00
Local Dev
4c63ae1bc7 feat(theseus/aegis): DGB adapter on @dgb-wallet/{core,psbt} vendored packages
Aegis now shares its DGB code with the standalone DigiByte web-wallet at
D:\Dev\SilentCode\Digibyte. Address derivation and PSBT construction come
from that project's @dgb-wallet/core and @dgb-wallet/psbt packages instead
of Aegis-local reimplementations. Any bugfix upstream flows in via a
re-vendor of dist/*.

- lib/dgb/{core,psbt}/ — vendored dist/ output of the two packages plus
  a tiny package.json shim marking them as ESM. @dgb-wallet/core's own
  import specifier "@dgb-wallet/core" inside psbt/*.js is rewritten to
  "../core/index.js" so the sibling module resolves without a workspace.
- New Theseus deps: bitcoinjs-lib, bip32, bip39, @bitcoinerlab/secp256k1,
  ecpair — the peer deps the vendored packages need. Loaded via
  api.require in index.js's loadDeps().
- chain-dgb.js is a thin adapter now: BIP32 tree via bip32 + DGB
  Network object, addresses via core.p2wpkhAddress, tx via
  psbt.buildPsbt + PSBT.signInput (per-input, since each UTXO's key
  differs) + psbt.finalizeAndExtract. Runtime backend stays the same —
  Theseus's lib/electrum.js against the DGB ElectrumX pool.
- Verified end-to-end in scratchpad: abandon×11 mnemonic derives
  dgb1q9gmf0pv8jdymcly6lz6fl7lf6mhslsd72e2jq8 (matches iancoleman.io/bip39
  and the previous inline implementation, so no on-chain address change
  for anyone who was already using Aegis's DGB slot). PSBT build+sign+
  finalize on a mock UTXO produces a valid 223-byte witness tx.

BIP44 (D…) and BIP49 (S…) address families are implemented in the
vendored core but not yet exposed in Aegis's picker — the panel needs
an "address family" selector inside the DGB settings block first. Left
for a follow-up; today's DGB pick uses BIP84 native SegWit only.
2026-09-07 02:19:44 +02:00
Local Dev
118de0ef5c feat(theseus/aegis): fold Sia into the addon; add DGB (BIP84 native SegWit)
Aegis now covers four coins across two-step coin+network picks: BCH
(mainnet + chipnet), TRX (mainnet + Nile), SC (mainnet), DGB (mainnet).

- Sia (SC): pulled the standalone siawallet's lib into
  bundled-addons/bchwallet/lib/sia/ and wrote lib/chain-sia.js exposing
  the common adapter shape. The very first SC wallet the user adds in
  Aegis reuses purpose "siawallet/mainnet/0" so pre-Aegis funds carry
  over automatically; subsequent SC sub-accounts start at
  "bchwallet/sc/mainnet/1". Per-wallet walletd URL setting; empty URL
  shows a "Point Aegis at a walletd node" gate in the panel.
- Vault-derive gate now honors a manifest-declared `absorbs` list, so
  Aegis's addon.json can list `absorbs: ["siawallet"]` and the derive()
  guard accepts paths under either the current id or the absorbed one —
  the mechanism a superseding add-on uses to inherit an older add-on's
  keyspace without orphaning funds.
- DigiByte (DGB): lib/chain-dgb.js ports the relevant bits of the
  SilentCode Digibyte design — SLIP-44 coin type 20, BIP84 native SegWit
  (m/84'/20'/0'/0/x → dgb1q…) via ripemd160(sha256(pubkey)) + bech32.
  ElectrumX-DGB backend reuses lib/electrum.js (public wss:50022 pool).
  BIP143 P2WPKH sighash + witness-tx serialize implemented inline (no
  FORKID — DGB uses standard Bitcoin sighash). Derivation cross-checked
  against a known BIP39 vector in scratchpad/verify-dgb.mjs — the address
  for "abandon×11 about, m/84'/20'/0'/0/0" is
  dgb1q9gmf0pv8jdymcly6lz6fl7lf6mhslsd72e2jq8, matching iancoleman.io/bip39.
- Panel: SVG coin logos for SC (green disc with S) and DGB (blue
  octagon with D) alongside the BCH/TRX marks. Chain-specific settings
  block per coin (walletd URL for SC; derivation path for DGB). Balance
  render uses BigInt-safe arithmetic so 24-decimal SC amounts don't
  lose precision on the way through the panel; amount input on SC
  returns a hastings string.
- Every chain adapter's snapshot fits the panel's shared shape
  (address/balance/history/etc.), so future chains only need a new
  chain-<x>.js file, a COINS registry entry, a matching case in
  mountWallet, and an SVG logo.

Standalone siawallet addon stays as-is on disk; users can delete it once
they've confirmed Aegis shows the same balance. Nothing here disables it.
2026-09-07 01:56:25 +02:00
Local Dev
a3810ef1eb feat(theseus/aegis): SVG coin logos, two-step coin/network picker, BCH Chipnet
- Inline SVG logos for BCH (green disc + ₿) and TRX (red disc + geometric T)
  replace the 🟨/🔴/🔵 emoji in the sidebar header and wallet picker rows.
  The approval overlay stays text-only ("Wallet: <name> — BCH · Mainnet")
  because that surface renders plain rows, not HTML.
- Wallet picker's "Add wallet" is now two-step: click a coin to expand its
  networks, then click a network to create the wallet. The flat list is gone.
- BCH Chipnet is a real chain option now: bchtest cashaddr prefix,
  m/44'/1'/0' derivation (BIP44 testnet coin type), bundled Chipnet electrum
  defaults, chipnet.imaginary.cash explorer, tbch.googol.cash faucet link
  in Receive. The shared electrum-servers setting stays mainnet-only in
  this rev; Chipnet uses adapter-embedded defaults.
- lib/chain-bch.js gained a BCH_NETWORKS table so mainnet vs chipnet
  differences (prefix, path, servers, explorer, faucet) live in one place.
- Registry is grouped by coin ({networks:{…}}) instead of a flat
  chain:network map — snapshot exposes coins[] for the panel and adds
  coinLabel/networkLabel/testnet fields per wallet.
- Testnet wallets get a small "TEST" tag next to the network name so the
  user can never mistake a chipnet or Nile balance for real money.

Legacy BCH mainnet index 0 derivation unchanged (bchwallet/mainnet/0 →
m/44'/145'/0' → bitcoincash prefix); the network parameter defaults to
"mainnet" and BCH_NETWORKS.mainnet reproduces the pre-change constants.
2026-09-07 01:07:31 +02:00
Local Dev
5574641fb9 feat(theseus/aegis): multi-wallet + Tron mainnet + Tron Nile in the bundled addon
Turns the single-account BCH addon into Aegis: a chain-agnostic wallet manager
with a wallet picker in the sidebar header, per-wallet sub-accounts, and Tron
mainnet + Nile alongside BCH. Add-on id stays "bchwallet" so vault-derive paths
stay in the same namespace and the legacy BCH default wallet uses PURPOSE
"bchwallet/mainnet/0" byte-identical to before — funds are untouched.

- lib/chain-bch.js wraps the existing keys/tx/wallet/electrum stack with the
  common adapter shape and scopes each wallet's storage under wallets/<id>/…
- lib/chain-tron.js: m/44'/195'/0'/0/0 → secp256k1 → keccak256 → 0x41 || h20
  → base58check. Balance + history via TronGrid v1, send via createtransaction
  + sha256(raw_data_hex) sign + broadcasttransaction. Mainnet and Nile share
  the address format; different vault paths mean different keys so a mainnet
  wallet can never accidentally sign against Nile.
- lib/base58check.js: bitcoin-alphabet base58 with sha256d checksum. k=1
  derivation verified against Ethereum's canonical k=1 H160 in a scratchpad
  harness (correct-by-construction for Tron address).
- Combined wallet-inject.js: window.bitcoincash on .x pages (unchanged gate),
  window.tronWeb + window.tronLink on any https page. tron_requestAccounts
  triggers the approval overlay; sign / sendRawTransaction / signMessageV2
  route to the currently-selected Tron wallet. Emits accountsChanged /
  setNode messages TronLink dapps listen for; chain ids 0x2b6653dc /
  0xcd8690dc match what TronLink itself uses.
- New panel: chain-aware wallet picker in the header (badges 🟨 BCH,
  🔴 Tron, 🔵 Nile), Add-wallet dropdown per chain, per-chain unit picker
  (BCH/sat, TRX/sun), per-wallet rename + remove (isLegacy default is
  protected). Sends show the chosen wallet in the approval overlay so the
  user can never mistake sub-account.
- Migration on first launch: pre-multi-wallet storage (top-level
  receiveCursor / txCache) is rehomed under wallets/bch-default/… and the
  legacy account path is preserved.

Not shipped: user is bundling into the next release. Live Nile broadcast +
real dapp connect need a set-up vault; the code paths are unit-verified end
to end but a testnet send + tronscan.io/nile connect are user-side steps.
2026-09-06 22:00:27 +02:00
Local Dev
878b728ef8 fix(theseus/light): darker --acid in light mode so brand green stays legible
#d6ff3d on a #ffffff / #f6f8fb background sits at ~1.3:1 contrast — the
acid green went almost invisible any time the user flipped Settings >
Theme to light. Override --acid to #4d7300 in every chrome page's
prefers-color-scheme: light block. Same hue family, ~5.5:1 contrast
on white, still reads as the same brand color.

Also: chrome.html was missing --acid: #d6ff3d in :root entirely (every
site used var(--acid, #d6ff3d) fallbacks). Adding the real declaration
means the light override can actually take effect.

Files touched: chrome.html, home.html, settings.html, error.html,
approval.html, messages.html. downloads.html is a dark-only overlay
(hardcoded), collision.html already had a proper light-mode --bcdn.
Tint fills (rgba(214,255,61,X) at low alpha) stay as-is — the specific
hue barely matters through 8% opacity.

Verified live via CDP: getComputedStyle(--acid) returned #d6ff3d in
dark, #4d7300 after cfg.set('theme','light').
2026-09-06 21:12:45 +02:00
Local Dev
f269a23e5f Ship Theseus 0.3.9 cc676e62 (dock on one row + retire Aegis placeholder)
Setup    cc676e62057b42c1e1221f0e9b69a55806bab90259bbbb4e6eb64701ab3d420a
Portable 1ab811f7edba74220b452763ee0f3329ba91d84c834d3e38be6426de0b04f1c7

Two changes since 0.3.8:

f1d117e - .extbtn was display:grid, which is block-level, so multiple
extension buttons inside #extbuttons stacked vertically. Switch to
inline-grid — same icon-centering, no forced line break between
siblings. Verified on a running instance: all 4 dock buttons at the
same y-coordinate.

(this commit) - Remove the static Aegis ₿ placeholder button and its
handler. Redundant now that the real bchwallet addon ships in the box
and registers its own dock entry. Also flip the dock-hidden logic to
disappear entirely on a fresh install with no add-ons registered, so
the toolbar doesn't carry an empty slot for users who never install
one.

Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.9.
2026-09-06 20:28:14 +02:00
Local Dev
aabcfc8a04 fix(theseus/chrome): extension dock icons render on one row
.extbtn had display:grid, which is block-level by default. Multiple
buttons inside #extbuttons stacked vertically because each was a block.
Switch to inline-grid — same centering behaviour for the icon inside
the button, no forced line break between siblings. Verified on a
running instance: all 4 buttons render at the same y-coordinate with
strictly increasing x.
2026-09-06 20:10:37 +02:00
Local Dev
c269615f69 docs(theseus): note the Siacoin wallet's derivation and node-URL policy 2026-09-06 18:49:33 +02:00
Local Dev
7931d981aa feat(theseus/siawallet): bundled Siacoin wallet add-on (walletd-backed, v2)
Second bundled wallet, same shape as bchwallet:
- keys: api.vault.derive("siawallet/mainnet/0") as the seed for walletd's
  KeyFromSeed(seed, index) (blake2b(seed||index) -> ed25519); addresses are
  standard unlock hashes, so a future walletd seed import yields the same
  addresses. Seed and keys live in memory only.
- lib/sia.js: Sia binary encoder, StandardUnlockHash, address checksum,
  v2 InputSigHash ("sia/sig/input|" + replay byte 2 + transaction
  semantics), transaction weight, walletd JSON. Address hashing and the
  sighash were verified against real mainnet v2 transactions (signatures
  from block 591853 verify under this implementation).
- lib/walletd.js: address-scoped walletd HTTP client (tip, fee, balance,
  outputs with proofs, events, broadcast). The node URL is a user setting
  with no default; hosted providers embed the access key in the path, so
  only the origin is ever displayed or logged.
- lib/wallet.js: gap-limit discovery via events, mature/immature balance,
  history deltas from v1/v2/foundation/miner events, largest-first
  selection with change to the current address, fee = walletd rate x
  weight x 1-3 multiplier, broadcast with the outputs' basis. A signed tx
  built here was accepted structurally by a live walletd (rejected only
  for the stub key not owning the parent).
- panel: Receive (QR), Send, History, Settings (node URL, derivation info,
  seed reveal behind approval, connected sites); gates for locked vault,
  no vault, no node URL.
- window.siacoin dapp bridge: getAddress (rememberable), signAndSend with
  100/1,000/10,000 SC allowances, signMessage (ed25519 over blake2b-256 of
  the message) — same approval and permission rules as the BCH wallet.
2026-09-06 18:49:13 +02:00
Local Dev
e3a44e42e3 Ship Theseus 0.3.8 b2b7819e (resizable toolbar + DevTools shortcut)
Setup    b2b7819e7a5f1ae1aa7ac79dd8e150f7c11f238eb3ab98a53fae51186dd4dc5b
Portable c971931f380245e44064720b132859917039488c12e0f654a2eeb406cf465d37

Bundled fixes since 0.3.7:

b649398 - User-resizable address bar + search box. Settings > Appearance >
Toolbar now offers urlBarSize (wide/medium/compact) and searchBoxSize
(hidden/compact/normal/wide). Applied as data-attrs on .bar so the flex
basis of .urlwrap and the width of .searchbox swap live. Motivation:
the per-extension dock (0.3.7) needs room to grow as users install more.
Settings-set broadcasts settings-update to chrome so resizing is instant
without a relaunch.

ab87576 - F12 / Ctrl+Shift+I opens Chromium DevTools on the active tab
in a detached window. Wired in the same before-input-event handler that
owns reload / sidebar shortcuts. Always targets the active tab
regardless of which view received the keystroke.

Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.8.
VPS archive step 7: 0.3.6 pair moved to Sia dl-archive/, only 0.3.7 +
0.3.8 remain on /opt/silent-mode/dl/ (per shipping-mirrors memory).
2026-09-06 17:19:15 +02:00
Local Dev
998775af42 feat(theseus/toolbar): user-resizable address bar + search box
Two new settings under Settings > Appearance > Toolbar:

  urlBarSize:    wide (default) | medium | compact
  searchBoxSize: hidden | compact | normal (default) | wide

Applied as data-attrs on the .bar element in chrome.html; CSS switches
the flex-basis of .urlwrap and the width of .searchbox. min-width on
.urlwrap guards against squeezing the URL invisible.

Settings-set now broadcasts settings-update to the chrome renderer, so
resizing takes effect live without a relaunch. Same channel is exposed
for future chrome-side settings.

Motivation: the extension dock grew a per-addon button per install, and
the URL bar (flex:1) had been eating all the remaining space. Users who
want more room for extensions can now shrink or hide the search box and
cap the URL bar width.
2026-09-06 17:14:57 +02:00
Local Dev
1c34fc2426 feat(theseus/keys): F12 / Ctrl+Shift+I opens tab DevTools (detached)
The packaged build stripped the native app menu, which took Chromium's
default DevTools accelerators with it. Wire the two everyone expects —
F12 and Ctrl+Shift+I — in the same before-input-event handler that
already owns reload / sidebar shortcuts. Always target the active tab
regardless of which view received the keystroke (chrome, overlay, tab)
so debugging is consistent with every other browser. Detach mode keeps
the tools out of the tab strip.
2026-09-06 16:50:50 +02:00
Local Dev
b5c4e94433 Ship Theseus 0.3.7 37fd8db5 (per-extension dock + right-click search-for-selection)
Setup    37fd8db5388cc2486b48d282bf38e1d5b18a40de5b7319950edcc412f3ddb683
Portable befab4c21ef78d7c92eb7d399e163d3e53e202a0bda9fe95f8d8db916db2e242

Bundled fixes since 0.3.6:

1026b08 - Per-extension toolbar dock: replaces the single sidebarbtn with
one button per registered addon sidebar-panel. Notepad (📝) and Silent
Mode Relay (🌐) appear automatically from their manifest icons; a static
Aegis Wallet placeholder (₿) marks the upcoming built-in BCH wallet.
Click a live button → open the sidebar on that panel, click active →
collapse. Preload adds openSidebar(panelId) / closeSidebar() wrappers.

622aaee - Right-click "Search for '<selection>'" in the page context
menu. Uses SEARCH() so it honours the current default engine, opens in
a new foreground tab, and truncates the label at 40 chars.

Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.7.

Ops note: silentmode.st /var/log had grown to 6GB (journal + rotated
syslog), which broke the first portable upload. Vacuumed journal to
500M cap; ~5.9G free after cleanup. Old Theseus builds not touched.
2026-09-06 13:45:58 +02:00
Local Dev
f5a500c796 feat(theseus/chrome): per-extension toolbar dock + Aegis placeholder
Replaces the single sidebarbtn with a dock that renders one button per
registered addon sidebar-panel. Each button shows the panel's icon
(the emoji from its manifest) and opens the sidebar on that panel.
Clicking the currently-active button collapses the sidebar; clicking a
different one swaps the visible panel. Notepad (📝) and Silent Mode
Relay (🌐) appear automatically because they already register panels.

Also lands a static Aegis Wallet placeholder button (₿) next to the
live ones, styled as .soon so it reads as coming-soon. It'll come out
once the addon registers a real panel.

Preload adds openSidebar(panelId) and closeSidebar() wrappers around
the existing sidebar-open / sidebar-close IPC handlers.
2026-09-06 13:32:41 +02:00
Local Dev
40391798e0 feat(theseus/bchwallet): per-site payment allowance for remembered sends
The dapp send approval gains an "Afterwards" dropdown: ask every time, or
allow up to 0.001 / 0.01 / 0.1 BCH more without asking. The allowance is
stored as permissions[origin].sendTx {capSats, usedSats}; sends within the
remainder go through silently and draw it down, a larger request re-prompts
(showing what is left) and the choice made there replaces the allowance.
No unlimited option. Settings > Connected sites shows the remaining budget
and Revoke clears it. Message signing still asks every time.

Host: approvalModal accepts `select` {id, label, options}; a chosen value
comes back as "+<id>=<value>" and is validated against the offered options.
2026-09-06 12:43:17 +02:00
Local Dev
a7cbc42dfa docs(theseus): record the add-on vault-root derivation as decided
bchwallet keys = HKDF(passwords/0 root, silentmode/addons/bchwallet/mainnet/0)
-> m/44'/145'/0'. Confirmed; must not change once funds exist.
2026-09-06 12:36:53 +02:00
Local Dev
7b3b5f7be8 feat(theseus/ctxmenu): search-for-selection in page right-click menu
Adds a "Search for '<selection>'" item to the page context menu when
the user right-clicks with text selected. Uses SEARCH() so it honours
whichever engine the user has picked (Startpage default, or their own
via the picker), opens in a new foreground tab so the current page
isn't lost, and truncates the label at 40 chars so a long selection
doesn't stretch the menu. Ampersands in the label are doubled so
Chromium doesn't eat them as accelerator markers.
2026-09-06 12:24:41 +02:00
Local Dev
debff9fc73 chore(theseus): declare @noble/curves, @noble/hashes, @scure/bip32 used by bchwallet
Already present in the tree via nostr-tools; pinned explicitly so the wallet
does not silently break on a nostr-tools bump.
2026-09-06 02:57:11 +02:00
Local Dev
67939b1493 feat(theseus/bchwallet): window.bitcoincash dapp bridge with per-origin permissions
wallet-inject.js runs in the isolated world of https://*.x pages and exposes
window.bitcoincash { isTheseus, version, network, getAddress, signAndSend,
signMessage }. Every call is routed page -> addon-page-msg -> activate()
handler -> approval overlay showing the requesting origin:
- getAddress: approval with an "always allow" checkbox; grants persist in
  api.storage.permissions and are listed/revocable under Settings.
- signAndSend / signMessage: approval on every call, never remembered.
  signMessage returns a BIP-137 recoverable signature (verified offline).
- one pending approval per origin; page-facing errors never echo balance.
Host fix: the inject IPC assigned event.returnValue twice, so pages always
got an empty script list.
2026-09-06 02:56:34 +02:00
Local Dev
821cc8e808 feat(theseus/bchwallet): send — plan, approval overlay, sign, broadcast
Send tab: recipient (cashaddr or legacy, testnet rejected), amount with
BCH/sat toggle and Max, 1-5 sat/B fee slider, live fee/total preview via
planSend. Sending goes plan -> approval-modal (To/Amount/Fee/Total) ->
ECDSA DER + SIGHASH_ALL|FORKID -> blockchain.transaction.broadcast, then
shows the txid with an explorer link. Confirmed coins are spent before
unconfirmed; dust change folds into the fee. Verified end to end against a
fake Fulcrum: broadcast tx re-parsed, sighash recomputed, signature checks.
2026-09-06 02:49:09 +02:00
Local Dev
de576935c1 feat(theseus/bchwallet): receive + history — vault-derived keys, cashaddr, QR, electrum
Wallet core on mainnet:
- keys from api.vault.derive("bchwallet/mainnet/0") -> BIP32 m/44'/145'/0'
  (@scure/bip32), never persisted; wiped on deactivate.
- lib/cashaddr.js (encode/decode + legacy Base58Check, spec vectors pass),
  lib/keys.js (hash160, p2pkh, electrum scripthash, ECDSA DER + BIP-137
  recoverable signing), lib/tx.js (serialization, SIGHASH_ALL|FORKID
  digest, coin selection, fee estimate), lib/electrum.js (Fulcrum WSS
  client with failover + subscriptions), lib/wallet.js (gap-limit scan,
  balance, UTXOs, 25-tx history with per-tx deltas, cached public txs).
- qr.js: dependency-free QR encoder (byte mode, v1-10, EC M/L; verified
  against jsQR).
- panel: balance header, Receive (QR, copy, next unused address, explorer),
  History (deltas, confirmations, explorer links), Settings (derivation
  path, electrum server list, xpub / approval-gated xprv reveal). Locked
  and not-set-up vault states explained in-panel.
- host: api.import for ESM-only deps, api.openTab for explorer links; an
  add-on whose activate() throws is no longer listed twice.
2026-09-06 02:46:41 +02:00
Local Dev
dcab5c10ec feat(theseus/bchwallet): bundled Bitcoin Cash wallet add-on skeleton
Manifest declaring sidebar-panel, vault-derive, page-inject (https://*.x)
and approval-modal; registers the Wallet sidebar panel. Shows up in
Settings > Extensions and opens from the sidebar.
2026-09-06 02:33:27 +02:00
Local Dev
ffeda26345 feat(theseus/addons): vault-derive, page-inject and approval-modal capabilities
Three opt-in capabilities for add-ons, plus the plumbing they need:

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

Bundled fixes since 0.3.5:

d3c3298 - Chrome-view gap root cause: body no longer inherits the chrome
view's height, so document.body.scrollHeight now measures actual content
and syncHeight can shrink chrome back after a menu close. The growing
blank strip above the page is gone (was previously ratcheting to the
260px clamp on every menu open).

1b4a3c3 - Bookmarks: favicons on every row (backfilled on revisit for
older entries) + Edit title actually works via an in-page modal (Electron
BrowserViews disable window.prompt, which is why the previous menu item
silently did nothing).

952bf5f - Tab tooltip on hover shows title + URL, the way Chrome/Firefox
do it. Uses the url field already present in the per-tab payload.

f591475 - Error page gets a first-class "You appear to be offline" kind
that fires when navigator.onLine is false — no more false "not registered"
verdicts when you're just disconnected. Plus a "did you mean" row that
suggests near-match BCNR names (Levenshtein ≤ 2, same TLD) from the warm
shared index — no network required, works offline. Click a suggestion
to retry with the original path preserved.

b9d70e4 - 750ms self-heal on chrome-view height as belt-and-suspenders
for any future overlay path that bypasses closeAllMenus.

Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.6.
2026-09-06 02:16:42 +02:00
Local Dev
67c306293c feat(theseus/tabs): title + URL tooltip on hover
Every .tab now carries a native title="<title>\n<url>" so hovering the
tab shows the page's own title and the URL (the way Chrome/Firefox do
it). Uses the url already present in the per-tab payload emitTabs sends;
no extra IPC.
2026-09-06 01:46:58 +02:00
Local Dev
61ebe44dac fix(theseus/bookmarks): favicon + working edit-title (window.prompt is blocked)
Two bugs the user hit:

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

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

Older bookmarks.json files stay valid — no schema migration needed;
missing favicon just renders the empty placeholder slot.
2026-09-04 22:12:03 +02:00
Local Dev
9d966fe354 fix(theseus/chrome): remove body height:100vh — the actual gap-growth root cause
The 750ms self-heal (b9d70e4) was a bandage. Real root cause: body had
height:100vh, so document.body.scrollHeight always equalled the chrome
view's current height (which IS the viewport). Once a menu grew chrome
via growChromeForMenu, syncHeight measured the inflated body and re-sent
the same big number — CHROME_H never shrank back.

Fix: drop height:100vh so body sizes to its content. scrollHeight becomes
the natural chrome height, and menu-close paths correctly report a value
smaller than the current CHROME_H. The self-heal stays in as insurance
for any future overlay that bypasses closeAllMenus.

Bar layout unchanged — every bar has an intrinsic height (.tabs 34px,
.bookmarks 26px, .bar auto), so body flow reflects them naturally.
overflow:hidden stays to clip any pathological overflow.
2026-09-04 21:55:54 +02:00
Local Dev
7aae2b2f3b feat(theseus/error): offline banner + BCNR did-you-mean on typos
Two problems bundled into the branded error page:

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

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

Origin-gate on the IPC uses the existing isErrorPageSender check, and
error-preload.js exposes only the invoke — no arbitrary index access.
2026-09-04 20:21:30 +02:00
Local Dev
cd3c4462d1 fix(theseus/chrome): self-heal chrome-view height drift every 750ms
The onTabs / onNav re-sync catches the common menu-close path, but a menu
that gets orphaned by a drag interruption, a focus flip, or a submenu
race can leave the chrome view taller than the natural body-scrollHeight
until the next tabs/nav event fires. Symptom: a huge blank strip between
the address bar and the page that grows and doesn't shrink until the tab
changes.

Fix: a 750ms interval that re-runs syncHeight whenever no .ctxmenu /
.grouppop is present. setChromeHeight in main.js only re-layouts when
the value actually changes, so this is a cheap no-op in the steady state.

Holding the ship: will bundle with the next batch per the slower-cadence
policy. Users on 0.3.5 keep the existing gap fix; this one lands with
the next release.
2026-09-02 22:16:09 +02:00
Local Dev
b714e31adc Ship Theseus 0.3.5 935d637a (fix chrome-gap + tab groups: drag-drop + collapsed popover)
Setup    935d637ae19ea821f7e89b0f9a802b4e774b6d1ae3254a70f3e5f17f89424177
Portable bb6bea253d6f32f86e8fdd152cf4c969cf311c991329d5e94fce31215ecf7f00

Chrome-view height gap: any tabs event that fires while no menu/popover
is open now normalises the chrome-view height via syncHeight(). A leaked
menu-close path could previously leave the strip inflated; the next
tabs update guarantees it shrinks back to the natural body-scrollHeight.

Tab-group drag-and-drop: group chips are now valid drop targets in the
same drag session as tab reorder. Dragging any tab onto a chip and
dropping assigns that tab to the chip's group (which also auto-clusters
it via the existing tab-group handler in main). Chip highlights acid
green while a valid drop hovers.

Collapsed group vertical popover: click a collapsed group chip and a
floating panel opens below it, listing every tab in the group. Each
row shows the favicon + title + a ✕ to close that tab. Clicking a row
switches to the tab AND expands the group so the newly-active tab
appears in the strip (tabGroupToggle). Popover closes on outside click
or Escape; the outside-click filter also ignores clicks inside .gchip
so opening the popover doesn't immediately close it.

growChromeForMenu() and closeAllMenus() now include .grouppop in their
overlay queries so the popover contributes to chrome-view sizing and
gets cleaned up alongside the ctxmenus.

Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.5.
2026-09-02 04:39:02 +02:00
Local Dev
c5dd03adc3 Ship Theseus 0.3.4 3d6d3d6a (address-picker delete X, Enter picks highlight, URL bar override)
Setup    3d6d3d6aeacf482284707f50d129315d07a593e09b5d68af852abe5aa0ed4c92
Portable 6c8c5ef212bfccb377e17404e83a18cf2f09771d0eb3e0ec86a85587efdcfc9e

Three address-bar suggestion fixes.

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

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

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

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

Three 0.3.2 misses fixed:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Icons regenerated from theseus.x's favicon.svg; the .svg itself
shipped in the 0.1.x window when the theseus.x site went live.
2026-08-31 16:32:16 +02:00
Local Dev
ee0548fec9 Ship Theseus 0.2.2 972f6209 (Extensions rename + draggable sidebar + session-proxy)
Setup    972f6209639122f32f032d5f2f9fc5a4808e0d4a810f88ae38ec9a1275ac52ac
Portable a9771f054ec36b9aff6ddee958cecf7e84cdacd4d7932aa8385c445aab4d29be

User-visible rename: the Settings tab and its labels say "Extensions"
now instead of "Add-ons". Internal identifiers (disabledAddons, the
addons/ folder, IPC channels, capability strings) stay put — code
churn wasn't worth it, and users only see the user-facing text.

Draggable sidebar. sidebar-preload.js now injects a 5px grip strip
along the LEFT edge of every panel document. mousedown+mousemove
streams delta-x px to main via sidebar-drag IPC; main clamps to
[200, 800] and debounces a save to settings.sidebarWidth. Width is
restored on next launch. The default is still 340. Faint acid-green
highlight on hover so the affordance is discoverable.

New extension capability: session-proxy. An extension whose addon.json
declares "session-proxy" gets api.setSessionProxy(rules) which routes
to session.defaultSession.setProxy — the same primitive Tor already
uses under the hood. Rules can be a string ("socks5://host:port") or
an object matching Electron's setProxy shape; null clears. The
capability is opt-in: an extension without the declaration gets an
error if it tries to call setSessionProxy. This is the framework
surface a private 3-VPS relay extension would build on (extension
folder stays on the operator's disk only; nothing about it appears in
the public build).

Deployed: scp installers + manifest + tools/ + releases/ pages to
VPS, sia-upload of both trees, verified HEAD 200 and manifest 0.2.2.
2026-08-31 16:00:34 +02:00
Local Dev
1a6b631d98 Ship Theseus 0.2.1 67e3cda1 (in-app update download + Ctrl+B sidebar)
Setup    67e3cda1b76a6dba966d91a43e78fdb52378f860c0f75a7775d11b96e1b4f5e3
Portable 415c804f75df6fab5072650fbdc3cffa4124ed02f8ff53a91281ebd2dc4775c3

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

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

Deployed: scp installers + manifest + tools/ + releases/ to VPS,
sia-upload of both trees, verified HEAD 200 and manifest 0.2.1.
2026-08-31 15:35:47 +02:00
Local Dev
dbb84eb840 Ship Theseus 0.2.0 c5202ddb (add-on framework + branded error pages)
Setup    c5202ddba5a4bf4cd8d71c665c72bdd996ced761f9a7aae295cb960e17988f47
Portable 9142764f78e41c6a6220960d5f7278e0abe6c1c91e5033871d95225355d8feda

0.1.x -> 0.2.x — two new subsystems land together. 0.1.3 (which had
only the error pages) is retired; those hashes never went live, so
we jump 0.1.2 -> 0.2.0 for clean version->hash 1:1.

Add-on framework:
- Discovery scans <userData>/addons/<id>/; each add-on carries an
  addon.json manifest and an activate(api) CommonJS entry. Nothing
  about a private add-on ships in the public build - drop the
  folder, restart, it's live.
- Bundled 'Notepad' reference add-on ships in resources/bundled-addons/
  and is seeded into the user's addons dir on first boot. Right-
  sidebar textarea, autosave, char/word counter.
- Extension point in this rev: sidebar-panel (WebContentsView on the
  right, SIDEBAR_W=340). Toolbar toggle appears only when at least
  one add-on registers a panel.
- Settings gains an Add-ons tab: list installed, per-add-on enable/
  disable toggle (settings.disabledAddons persists), Show folder /
  Open add-ons folder / Reload buttons, trust-model warning.
- Origin-gated per-addon storage: main derives the add-on id from the
  sender file:// URL, so a panel can only touch its own kv store
  under <userData>/addons-data/<id>.json.

Branded error page:
- did-fail-load on every tab -> error.html with kind = name-not-
  registered (BCNR host + ERR_NAME_NOT_RESOLVED) | name-unreachable
  (non-BCNR host + NAME_NOT_RESOLVED) | unreachable (conn refused/
  reset/timeout) | tls (cert range) | generic.
- Actions per kind: Retry + Home always; Search for '<host>' and
  Register-on-Sirius where a host is present; Tor guide link on
  unreachable.
- Address bar keeps the failed URL (refreshTabUrl already skips
  file://) so the user can edit and retry.
- Ignorable codes (-3 ABORTED, -20 BLOCKED_BY_CLIENT, subframe
  errors, internalNav) skipped so normal user actions don't paint
  an error page.

Deployed:
- scp installers + manifest + tools/ + releases/ pages to VPS
- sia-upload of ../site (silentmode.bch) and ../site-theseus-x
- verified HEAD 200, manifest reads 0.2.0 2026-08-31

Existing 0.0.6/0.1.2 installs will surface 0.2.0 in the update chip
on next launch.
2026-08-31 14:06:18 +02:00
Local Dev
0117986657 Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.

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

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

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

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

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

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

Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00