Startpage layout reverted to the pre-0.3.131 shape. 0.3.131's full-page-
height sidebar was a mistake — slots in a short strip under a hero at
the top, cards and slots sharing one row of panels at the same height,
is what matches the design. .page/.main-col/.sidebar structure dropped,
hero back as a body sibling of .homepage, slots-list back to repeat(3,
72px) with minSlots=9. Two of the user's own tweaks baked into the
defaults: .section.chromeless on the left column so slot icons float
on the page background instead of sitting in a boxed panel, and
defaultState().iconSize = "large" (60 px tiles).
Game.X in the SM cards (replaces SilentMode.X in its slot, stub
description + a built-in gamepad brand mark until game.x goes live).
SilentMode.X moved to the quick-links strip as the 5th slot. moveItem
now strips the pinned-slot anchor when a q item moves to sm — a link
dragged onto the cards used to flip back on the next render because
its slot number was still set.
Nostr NIP-07 signer grows per-site identities (0.1.0 → 0.1.1). First
approval modal picks between the Hermes identity (one npub for DMs
and public notes, the one published as the np record on your BNS
name) and a site-specific HKDF-derived npub (unique per origin, so
Nostr clients can't cross-correlate your activity). The chooser shows
both npubs so the user sees what each button reveals. Grants without
a stored profileId (migrated from 0.3.131) default to the Hermes
identity so existing approvals keep behaving the same.
lib/hermes.js:nostrKeyFromRootForOrigin HKDFs with info
silentmode/nostr-origin/v1/<origin> over the existing 32-byte
messengerRoot. main.js _nostrAPI methods take {profileId, origin};
signer cache per profile.
Silent Mode Mail retired as an extension — now a Sidebar quicklink
(id: silentmail). Its 0.1.0 panel iframed mail.silentmode.st, which
sends X-Frame-Options: DENY; the browser rendered the block page and
STILL fired the iframe's load event, so the 5 s "did it embed?"
watchdog saw it as loaded and the panel stayed black. The whole
extension was just a landing page with an Open Mail button — exactly
what a sidebar quicklink does, without taking a panel slot or an
Extensions row. migrateAegisRename auto-retires any existing
extensions/mail/ to addons-backups/mail-migrated-<stamp>/ on first
boot of this Theseus; "mail" added to the first-party reserved-ids
list so a community add-on can't claim the id.
Claude connector 0.2.1 → 0.2.2: the bearer token no longer rotates on
every server stop. The old stop() generated a fresh token every time
Theseus quit, the panel toggled off, or a 0.3.X install replaced the
app — the Connect button wrote the new token to ~/.claude.json but
any already-running Claude Code or Claude Desktop process held the
previous token in memory and only re-reads config on launch, so /mcp
reconnect kept sending the stale bearer and the server rejected it
forever. Infinite loop: install Theseus upgrade, click Connect, MCP
still 401, close Claude, open Claude, install another upgrade a day
later, repeat. The explicit Revoke token button still rotates for
users who want fresh keys; everything else is persistent.
Sidebar panel apps blank to about:blank after 2 min of being closed to
free their render tree (iframes, media, listeners). Fast reopen within
the window still reuses the loaded page.
Light theme palette flip under body.light — the full set of
--ink / --mut / --card / --line tokens flip plus hero + card
translucent panels switch to light-glass, so hero titles, SM card
text, slot labels and the ".bch" tld read correctly on the light
background preset instead of as pale ghosts.
Users can now sign in with name + password on a device that has never seen the
account by opting into a cloud backup at account creation. The seed phrase is
locally re-wrapped with scrypt + AES-GCM keyed by the password and published
as a NIP-78 kind:30078 event under d-tag `hermes.backup.v1/<sha256(name)>`.
Relays only ever see opaque ciphertext; the password is the only decryption
key, so backup strength is password strength.
Signin tries the local wrap first (fast, no network) and falls back to the
cloud copy if the device hasn't been used before. Settings → Backup now has
"Back up to cloud" / "Re-upload" / "Remove cloud backup" controls that reuse
the Reveal-seed password field as the key.
Site: added @media breakpoints to shared.css so the header collapses cleanly
on narrow screens (icon-only brand under 380px, hidden "Sign in" label under
640px), the hero mark drops to 84px, h1 scales down, and buttons shrink to
touch sizes. Also fixed a stale reference to the removed `mnemonicBox` id in
the Create-account toggle that was throwing a TypeError on mobile.
The main.js scrypt call also carries its own maxmem option now (commit
00286f76), but main.js lives inside app.asar and can't be hot-patched
onto an installed Theseus. lib/hermes.js lives at
resources/lib/hermes.mjs OUTSIDE the asar — patching it with the
module-level crypto.scryptSync override means a plain file-write onto
an installed Theseus fixes Create account without rebuilding and
reinstalling.
The patch guards itself with __hermesScryptPatched so repeated
loads of hermes.mjs (dev reload, test harnesses) don't re-wrap and
blow up the function reference chain.
Users can link one or more phone numbers to their Hermes identity by
publishing a kind:30078 claim event whose d-tag is a SHA-256 hash of the
E.164 number. The number itself never leaves the device. Add-contact and
message-send now also accept a +phone input directly — the client hashes it
and queries relays for the matching Hermes identity.
- lib/hermes.js: normalisePhone(str) strips separators + a leading 00
into E.164, rejects anything that doesn't fit +[country][7-15 digits].
hashPhone(str) → base64url-truncated-to-32 SHA-256 digest, used as the
d-tag suffix under PHONE_DTAG_PREFIX. buildPhoneClaimEvent /
buildPhoneRetractEvent emit the signed kind:30078 / NIP-09 kind:5.
- main.js: _hermesAPI.getPhone / setPhone / clearPhone / findByPhones.
Plaintext phone persists in the identity meta (locally only) so the
user can see what's linked and retract it; event ids are kept so a
retract can reference the exact wire event. hermesResolveTo takes a
phone-shaped input and does the discovery lookup transparently, so
contactsAdd and send accept phones without a new entry point.
- panel.html: Settings → Phone discovery (link / unlink + privacy note),
add-contact placeholder now lists +phone alongside the other forms.
Privacy stance stated in the UI: "anyone who already knows your number
can find you — same tradeoff as number-based messengers." No PSI tricks
yet; plain hash. Users who don't want it just don't link, which is the
default. Future slice can add a pepper if operators want to narrow the
brute-force window.
Peers' chat-list labels now cascade: contact nickname → kind:0 display name →
BNS handle → pk preview. Avatars render as a URL picture when kind:0 carries
one, with initials as fallback. Profiles are fetched on demand (background,
non-blocking) when a new peer pk shows up in the inbox or contact list, and
cached on disk so a cold start renders instantly.
- lib/hermes.js: buildProfileEvent({sk, displayName, picture, about}) emits
a signed kind:0 with `name` + `display_name` set together (clients vary
on which they prefer). parseProfileContent(content) handles both.
- main.js: profile cache at <userData>/hermes/profiles.json; auto-fetch
from HERMES_DEFAULT_RELAYS with a 4 s per-relay cap. hermesPublishEvent
centralises relay publish (used by profile save; send() retains its
inlined copy for the DRY-up later). hermesRecord() kicks off a background
profile fetch the first time a peer is seen; result fires on the
hermes-profile bus event.
- main.js: _hermesAPI.updateProfile / getProfile. Addon proxies both.
- panel.html: Settings → Profile editor (display name, picture URL, about);
publishes on save, pre-fills from the cache. Picture validated as http(s).
labelFor() + avatarFor() centralise the display cascade. Profile state
lives in state.profiles.
Panel + standalone window now accept all three handle forms as synonyms, and
the UI defaults to the hermes:alice scheme form. Signin is persistent across
restarts via safeStorage-sealed identity; the ⋯ menu adds Sign out.
- lib/hermes.js: parseHandle(input) → {kind, value} canonicalises any of
hermes:alice, alice.erm, alice.bch, alice@parent.tld, npub1…, or raw hex.
renderHandle(name, mode) formats a .erm name as hermes:alice (default),
alice.erm (dotted), or alice@hermes.tel (email). Non-.erm names always
render dotted so a .bch handle can't masquerade as a sub-identity.
- main.js: hermesResolveTo() unifies contacts-add + send + their panel-API
twins behind one path. Sub-identity (alice@parent.tld) resolution returns
a crisp "not available yet" until the mainnet mint of the parent name's
signed-records manifest lands — the parser accepts the form today so UI
copy and placeholders can show it.
- main.js: safeStorage-sealed identity at <userData>/hermes-identity/,
auto-restored on app ready. New _hermesAPI.signOut wipes it. init() takes
{ remember: false } for a one-shot session (default: persist). A
hermes-restored event fires on auto-restore so the panel skips the
signin screen.
- panel.html: renderHandle() mirror client-side; ⋯ menu with "Signed in as
hermes:alice" header + Sign out; placeholders show all three forms; the
signin hint tells users they stay signed in.
On-chain `.erm` and `hermes.tel` mints are deliberately deferred to mainnet
per user direction — the parser and display paths are tld-agnostic, so a
mainnet switch is one constant change in lib/hermes.js (HERMES_TLD /
HERMES_EMAIL_PARENT) plus the panel's mirror of them.
- password-vault: createVault takes { messengerRootHex } opt; unlockVault
surfaces messengerRoot (null on legacy vaults, so nothing regresses);
saveVault persists it; bindMessengerRoot mutates an unlocked state for a
later attach-mnemonic-to-existing-vault flow.
- main.js password-setup: when the user provides a mnemonic, ALSO compute
seedToPurposeRoot(seed, "messenger/0") and store it. Random-seed vaults
stay as-is (no mnemonic = no messenger root to store).
- main.js hermes-init: two modes now — { mnemonic } (unchanged) or
{ useVault: true } (uses vaultState.messengerRoot directly, no mnemonic).
Response includes source: "vault" | "mnemonic" for the panel to badge.
- main.js hermes-can-use-vault: cheap availability probe used by the panel
to decide whether to show the vault sign-in shortcut.
- hermes.js: nostrKeyFromRoot(root) accepts 32-byte Uint8Array or 64-char
hex; produces the same {sk, pkHex, npub} as nostrKeyFromMnemonic for the
same seed (unit-verified: derivation paths converge on f2c92519...67f4).
- Messages panel: "Sign in with password vault" button appears above the
mnemonic entry iff the vault is unlocked AND was set up from a mnemonic.
The mnemonic path stays as the always-available fallback — bind is
genuinely optional, not required.
- Hermes/proof/: standalone keystone — BIP-39→Nostr, NIP-17 wrap/unwrap,
name-addressed send/receive verified end-to-end via public relay
- Argus/src/lib/hermes-derive.js: HKDF(silentmode/messenger/0) using libauth
so the registrar can compute np without pulling nostr-tools into Argus
- Argus/src/register-hermes-chipnet.mjs: idempotent REG/UPD script; publishes
np (Nostr pubkey) and nr (relay) records for a chipnet name
- TheseusNavigator/lib/hermes.js: same derivation + wrap/unwrap + record
parsing, canonical for Theseus; guarded by lib/package.json type:module
- TheseusNavigator/messages.html + messages-preload.js: Messages panel UI
(identity from mnemonic, live inbox, compose by .bch name), .bch suffix
stripped from displayed names
- TheseusNavigator/main.js: HERMES_MOD + loadHermesLib next to VAULT_MOD;
ipc handlers (hermes-status/init/close/inbox/send/open), per-relay
subscription with auto-reconnect, status pushed on WS open/close,
reverse-resolve pk -> .bch name via cached BNS index, Ctrl+Shift+M shortcut
via web-contents-created (works from any tab)
- Chipnet hermes.bch registered with np=f2c92519...67f4 nr=wss://nos.lol
(txid 7fc6de4544c13b782f163fdb892ea6749785886d05a336282fa659d722c7e92b);
end-to-end verified in Theseus