Hero mark now sits in front of the "Theseus Navigator" wordmark inside a
translucent rounded panel, so the identity reads on any wallpaper. The
tag line and the hint got their own matching pill backings for the same
reason.
Left-column slots grew from a fixed 3-column grid in a 240–280 px panel
to an auto-fill grid in a 360–440 px panel — about 5 columns of 72 px
tiles instead of 3, so more quick-launch icons fit without the panel
feeling cramped. No change to the icons themselves.
Home page splits into two self-contained panels so the left-column
links no longer scale with the Silent Mode cards. The slot grid is
narrower, the icons default closer to Windows desktop-shortcut size,
and each section carries its own subtle panel background and heading.
Claude connector hardening (the opt-in MCP add-on from 0.3.106):
- Pin the approved origin to the TabDriver on every Controller.use()
and re-check the tab's current URL before each CDP read / input
(isolated, snapshot, screenshot, clickAt, insertText, press,
setFiles, evaluate). A meta-refresh or soft-nav to an unapproved
origin between approval and the call now throws instead of leaking
content or steering input to the new site.
- Rotate the bearer token on every stop() so toggling the connector
off (toolbar chip, panel switch, deactivate) invalidates existing
credentials. Re-enabling issues a fresh token; the panel's install
strings update on the next start. Matches the mental model where
"off" means "the keys change" instead of "the server stops".
- click without a ref now resolves the element at (x, y) through
DOM.getNodeForLocation and refuses if it's a password / one-time-
code field. Previously only ref-based click ran isSecretField; raw
coordinates let focus land on a password field before FOCUS_GUARD
kicked in to block typing.
Claude sessions could not work inside the user's own profile: the desktop
app's browser lost synthetic pastes and typing in Medium's editor, and a
browser-wide --remote-debugging-port would hand every session to any local
process. The connector is a built-in extension, off until turned on in
Settings > Extensions, that serves MCP on 127.0.0.1 behind a per-install
token and drives one tab at a time through that tab's webContents.debugger.
- Host: a "tab-control" capability (built-in add-ons only) for listing,
opening, navigating, closing and focusing tabs, handing out web tabs'
webContents (never Settings, extension or home tabs), and a toolbar chip
plus tab outline while a client is connected; the chip's X stops it.
Built-in add-ons can ship "defaultEnabled": false.
- Tools: list_tabs, open_tab, navigate, activate_tab, close_tab, screenshot,
read_page_text, snapshot (accessibility tree with refs), click, type
(insertText + real Enter), press_key, paste_html / paste_text (system
clipboard + webContents.paste, clipboard restored), upload_file
(DOM.setFileInputFiles), wait_for, and evaluate behind its own switch.
- Safety: first use of each site asks the user; password and one-time-code
fields are refused; uploads only from folders the user added; requests
from web pages or foreign Host headers are refused; every action is
logged with a timestamp in the panel and actions.log.
- Claude Desktop runs stdio-shim.cjs through the Theseus executable in Node
mode.
- Tests: unit (protocol, auth, policy, shim) and an end-to-end run on a
throwaway profile that types, pastes HTML with a heading and a link, and
uploads a PNG on a local page.
Claude sessions need to act inside the signed-in profile (a Medium draft
failed from the desktop app's own browser), and a browser-wide debugging
port would expose every session to any local process.
Right-click or chevron-click on the sidebar strip's AI button now opens
a borderless overlay instead of a plain native menu: enabled engines
are listed with their bundled icons, the current engine is highlighted
with the acid accent + check, and a settings row at the bottom jumps
to Search settings. Clicking an engine selects it as the default and
opens it in the left panel in one click.
Bundled engine icons gained an svg-first fallback: engine-icons/<id>.svg
is picked up before <id>.png so plain color-coded letter tiles ship
alongside the existing bitmap favicons. Added tiles for ChatGPT, Grok,
Gemini and Phind — each a simple initial-on-color mark.
Left column slot grid grows to a visible 3-column fixed cell layout.
Each link can be pinned to a specific slot by dropping onto an empty
cell; unpinned links flow top-to-bottom through the remaining cells.
Dropping an item onto another slot swaps the two positions, so the
grid never collapses underneath the user.
The Ariadne.X home card now uses the brand kit's "ivory on dark"
mark (variant-ivory-glow) instead of the terracotta pottery sketch.
The ivory bust reads cleanly against the navy card surface — the
pottery version was designed for cream backgrounds and got lost
against the dark card.
A wiz:// link that opens a new window with rel="noreferrer" (Potidaea's "Open in wallet", and many other dapps) was dropped before it reached the wallet; a click on one in the page now opens Aegis's pairing approval. Links inside iframes are still refused.
Potidaea's "Open in wallet" is <a href="wiz://…" target="_blank"
rel="noopener noreferrer">, a shape many dapps use. A new-window wiz:// link
goes through the window-open handler, which credits a pairing to the page
only through the request's referrer, so it can tell the page's own link
from one in a frame. noreferrer strips the referrer, so the link was
dropped and the wallet never heard of it.
The inject preload, which runs in a tab's top document only, now hands a
trusted click on a wiz:// link straight to main, and main accepts it only
from the tab's main frame before routing it like any pairing link. Links
inside iframes still go the old way and are still refused without a
referrer.
The file tracks working-tree state that is not yet committed or shipped;
0.1.7 is both, so leaving the block would misdescribe the tree to the next
session reading it.
Typing never triggered a translation. The input handler was wired only to the
language detector, so nothing happened until Enter, the Translate button or a
language change -- the panel looked broken to anyone who simply typed and
waited. Translation now runs 500 ms after the text settles.
Each request carries a sequence number. Typing outruns the network, and
without that guard a slower earlier response paints its stale text over a
newer one.
Detection already worked, but wrote its answer only into the text of the
collapsed "Auto-detect" option, which a closed select hides -- so it read as
"detection is broken" when it had in fact succeeded. Name the language in a
row under the selects, and lower the 12-character floor to 5, since short
phrases never cleared it. With translate-as-you-type on, skip the standalone
detect call: the translation response already carries the detected language,
so asking twice only doubled the load on a public mirror.
Two of the three fallback mirrors were dead, checked by posting to both
endpoints: translate.plausibility.cloud answers 405, and lingva.ml serves an
HTML page because it is not a LibreTranslate API at all, so it could only ever
produce the "HTML instead of JSON" failure. Drop both and add
silentmode.st/libre, which stays a fallback rather than the default because it
covers six languages against disroot's fifty.
Auto-translate sends text to a translation server as it is typed, so it is a
visible toggle rather than silent behaviour. Default on.
Fixes the "unknown invoke: fetchCloudBackup" dead-end on mobile sign-in
and the empty hooks the Settings UI depended on. The shim now implements:
- Local wrap: on signup it generates a BIP-39 mnemonic (@scure/bip39),
derives the Nostr sk via PBKDF2+HKDF with purpose "messenger/0" (same
chain Theseus runs), scrypt+AES-GCM wraps the mnemonic (@noble/hashes
scrypt matching HERMES_SCRYPT N=2^15 r=8 p=1), stores it in
localStorage.hermes.wrap. hasBackup/getBackup use that wrap, so a
password sign-in now works on the same device.
- Cloud backup: publishCloudBackup / fetchCloudBackup / removeCloudBackup
/ hasCloudBackup matching Theseus's dtag format
(hermes.backup.v1/<sha256(bareName)[:32]>). Password unlocks the local
wrap, re-wraps for the cloud copy with a fresh salt, publishes a
kind:30078 to every relay, remembers the event id for later retract
via NIP-09.
- Phone discovery: setPhone / clearPhone / findByPhones with the same
hermes.phone/<base64url(sha256(E.164))[:32]> d-tag Theseus uses, so a
vCard import on the browser surfaces the same matches a Theseus user
would see.
- contactsUpdate / contactsRemove rounded out so the panel's contacts
menu is fully wired.
Also:
- Default relay list bumped to [wss://hermes.x, wss://relay.silentmode.st,
wss://nos.lol] in both the Theseus host and the web shim, so every
publish/fetch hits our own strfry on the VPS and the cloud backup lives
somewhere we control.
- Theseus host: addons-host.js + main.js now understand a `width` field
on sidebar panels, so the Hermes panel declares width:500 and opens at
a Telegram-ish width by default (user can still drag; saved per-panel).
- Panel + web messenger: forced color-scheme:dark (the brand mark has
hard navy cutouts that stand out as "black inside the envelope" on a
light background; dark everywhere is cheaper than theme-aware marks).
- Password fields: 👁 toggle appears on every <input type="password">
via a tiny JS enhancer in both the panel and the web messenger.
- Settings/signin tabs: Sign in / Sign up persistent pill, drag-to-resize
contacts/thread splitter (persisted per device), 2-column always.
Right-click on a sidebar panel (WhatsApp, Telegram, LLMs, add-on panels
like Hermes) now opens a native menu with Copy image / Save image as /
Open link in new tab / Copy / Back / Forward / Reload. Previously the
sidebar views had no context-menu listener attached, so sites like
WhatsApp Web that intercept right-click left the user with no way to
copy an image.
Hermes mark in the sidebar strip now zooms to the V-fold and wings;
envelope body partially visible. The original viewBox had ~43% empty
space top-and-bottom, so the glyph rendered at ~15 px inside the 26 px
icon frame.
Theseus's sidebar panel.html had the pre-redesign flow (two primary buttons
+ Create a new account toggle + Name+password/Seed-phrase chip switcher).
Replaced with the same persistent pill tab bar the web messenger uses:
Sign in / Sign up visible together, active one filled, inactive outlined;
tapping an inactive tab swaps the form below with no back step. "Recover
with seed phrase" is a quiet link that opens its own form (with ← Back).
The Theseus-only "Open with Theseus ID" quick path now lives inside the
Sign in tab's form and hides entirely when the vault is locked — no more
disabled button + hint about unlocking the vault cluttering the signin
screen. Bumped the bundled addon to 0.2.9.
The /overview page's UI mockup used a fixed 220px/1fr grid that overflowed on
375px viewports. Added a @media (<=640px) rule that drops the contacts pane,
expands the thread to the full width, breaks out to the screen edges, and
tightens the window-bar + compose padding so the mockup reads cleanly on a
phone.
Added tests/hermes-two-users.test.mjs — a Node integration suite that drives
two personas (alice, bob) through the full flow against the live nos.lol
relay: BIP-39 key derivation, phone-claim publish, phone→pubkey discovery
both directions, bidirectional NIP-17 gift-wrap DM delivery, vCard parse +
findByPhones. Eight assertions, all pass. Run with:
node TheseusNavigator/tests/hermes-two-users.test.mjs
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.
Two Settings-pane fixes:
1. .modal was clipped on short viewports (mobile landscape, small
laptop screens): place-items:center + fixed max-width with no
max-height meant the content below Phone discovery / Backup fell
off the bottom. Add max-height: calc(100vh - 24px) + overflow-y:
auto so the whole thing scrolls inside the backdrop. This is why
the user couldn't see Phone discovery on mobile.
2. Address book section between Phone discovery and Backup: 'Import
contacts from vCard' triggers a hidden file picker (<input
type=file accept=.vcf>) that reads the file client-side, extracts
FN + TEL per VCARD block, hands the phone list to findByPhones,
and auto-adds the matched Hermes identities as contacts with the
vCard name as nickname.
Lives in both the Theseus addon panel and the hermes.x web messenger.
Phone discovery was already in Settings — the modal clipping just hid
it on smaller viewports.
Signin screen (both the Theseus addon panel and hermes.x/messenger):
- Open with Theseus ID and Create a new account stay as top-level
actions (unchanged).
- Beneath them, a 'Sign in' divider + two chips [Name + password]
[Seed phrase] drive one unified Sign in button. Toggle swaps the
input area between the password form and the mnemonic textarea.
The old 'Sign in with name + password' and 'Open an existing
account' secondary buttons are gone.
Site nav restructure (site-hermes-x/):
- Header now: Hermes.X brand + Overview + Sign in icon (opens
/messenger). Messenger tab, Branding tab, and Get Theseus pill are
all removed from the header.
- Footer carries Branding + Open Messenger + Get Theseus links as
secondary nav. Branding is 'here' on the brand kit page.
Mobile dark bg fix (shared.css):
- color-scheme: dark on :root so mobile browsers don't flip the
page into a light theme that makes the sky-blue mark unreadable.
- html { background: var(--bg) } so the pre-body paint is dark too
(Safari / Firefox mobile briefly show the html bg before body css
loads).
Scheme form "hermes:alice" was leaking into the UI (default display
mode + hero copy + placeholders). Drop it as a visible option: the
display-mode picker now offers bare / dotted / email and defaults to
bare so users just see "alice" in chat lists and settings. The
hermes: URI scheme stays available as an input form (parseHandle
still accepts it) — just not surfaced in the UI.
New sign-in path: a "Sign in with name + password" button on the
signin screen opens a two-field form that calls getBackup({password})
to unwrap the locally-saved mnemonic, then init({mnemonic, handle})
in one shot. This covers the "come back to the same device" case
without typing the full seed every time. From a new device the seed
phrase path is still there — the form hint says so.
Applied to both the desktop addon panel (TheseusNavigator/bundled-
addons/hermes/panel.html) and the hermes.x web messenger
(site-hermes-x/messenger/index.html) so they stay in step.
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.
Before: the on-sky lockup had a dark mark with dark cutouts — the fold
V-shape disappeared because the cutouts matched the mark colour instead
of the sky backdrop. Same on cream.
Now three per-surface mark variants, cutouts coloured to match the
expected background so it shows through:
mark.svg sky body + navy cutouts → dark panel
mark-on-sky.svg navy body + sky cutouts → sky-blue surface
mark-on-light.svg navy body + cream cutouts → light surface
Logo lockups rebuilt the same way: logo-horizontal-on-sky.svg uses the
dark+sky mark variant, logo-horizontal-light.svg uses the dark+cream
mark variant, same for stacked.
Branding page's on-sky / on-light preview cells repointed at the new
variants. addon.json bumped to 0.2.6 (same mark, re-emitted from
pipeline for byte parity with the sites).
N=2^15 r=8 p=1 needs 128 * N * r = ~32 MiB. Node's default maxmem for
scryptSync is exactly 32 MiB, so the derive call throws
MEMORY_LIMIT_EXCEEDED on the first create-account click. Pass maxmem:
128 MiB explicitly so the ceiling is above the KDF's working set with
headroom.
Applies to both wrap and unwrap paths; wrap params carry the KDF cost
inline so changing the N here would invalidate existing backups, but
unwrap already reads N from the blob — the maxmem lift is the only
needed change.
The slot grid on the left of the home page switches from `repeat(3, 1fr)`
to `repeat(3, 100px)` with `justify-content: start`. Cells no longer
stretch to fill the column, so icons sit in a tight desktop-style grid
anchored to the left edge instead of drifting inside wide 1fr cells.
The 900px breakpoint keeps 100px cells and left-anchors them too.
Also carries the Hermes brand kit updates that landed on master between
0.3.101 and now — the Hermes.X Silent Mode card now renders the new
blue envelope mark from home-brand/hermes-mark.svg instead of the old
inline red envelope. The sidebar strip icon size for Hermes was bumped
to 26px in the same cycle to balance the new mark's negative space.
Two tweaks so the mark reads at sidebar size.
- addon.json/mark.svg (and every mirror of mark.svg across the sites):
rebuild with ZERO padding around the content bbox so the icon fills
the slot vertically — before it was centred in a square viewBox that
wasted ~45% of pixels. Version bumped to 0.2.5 so Theseus re-seeds.
- quicklinks.html: .icon svg, .icon img goes from 20×20 to 26×26
(container stays 34×34; same breathing room, bigger glyph). Ships
with the next Theseus release — can't be hot-patched into the asar.
Deploy state: site-hermes-x is live on the VPS and in bns/hermes.x/ via
the silentmode-sia-sync systemd timer, which was extended to include
hermes-x alongside sirius-x and bergamot. Future rsyncs of site-hermes-x/
to the VPS auto-sync to Sia within 5 minutes.
User supplied the real vector source at Hermes/Icons/file.svg. Dropped the
background rectangle, kept the sky-blue foreground + dark inner detail
cutouts, squared the viewBox around the content bbox with a small margin.
Result: ~6.6 KB SVG, pixel-perfect at every size — no more 60x downscale
blur.
- bundled-addons/hermes/assets/mark.svg: tight-cropped vector
(viewBox centres the icon in a square so 1:1 slots don't crop).
- bundled-addons/hermes/assets/mark-64.svg: same, pre-normalised to a
0..64 viewBox for legacy consumers that assume that range.
- bundled-addons/hermes/addon.json: version 0.2.4; manifest icon is now
the SVG as a data:image/svg+xml;base64 URI (8.8 KB vs the previous
16 KB PNG data URI).
- bundled-addons/hermes/panel.html: both img tags point at mark.svg at
24 and 72 px.
- home.html / home-brand/hermes-mark.svg: Hermes.X tile uses the vector.
- site/hermes/{favicon,branding/mark}.svg: both now the user's artwork.
- site-hermes-x/assets/{favicon,mark}.svg: same.
- All /hermes site HTML img tags swapped from mark.webp to mark.svg.
Installed bundle + user-data cache hot-patched to 0.2.4 so this takes
effect on the running Theseus without a reinstall.
The 1461x720 webp downscales badly to 24 px (sidebar strip) and 72 px (signin
header) — browser is doing a ~60x reduction and the thin wing lines turn to
mush. Pre-crop around the icon's content bbox, make it square with a small
breathing margin, resize to 128 px and 256 px, drop transparency on the dark
backdrop. Addon manifest icon is the 128 PNG as a data URI (16 KB vs the
26 KB uncropped webp), and the panel pulls mark-128.png / mark-256.png at
the two sizes it needs. Also home.html's Hermes.X tile now points at the
cropped PNG so the home card isn't blurry.
Version bumped to 0.2.3 so seedBundledAddons picks it up on a release
bundle swap. The installed bundle + user-data cache were hot-patched
too so the fix is visible immediately in the running app.
Was still rendering the red fill-in SVG left over from before Hermes had
a proper brand. Switch to the {src: "home-brand/hermes-mark.webp"} form so
the home tile matches what the sidebar icon + the hermes.x hero already
show. Lands next time home.html ships (part of a packaged Theseus release;
can't be hot-patched into the installed app.asar without rebuilding).
Three more LLM engines land in the catalog and in DEFAULT_ENABLED, so the
sparkle button's picker lists five AI engines out of the box: Perplexity,
Phind, ChatGPT, Grok, Gemini. The first two answer a URL query without any
login, the latter three need a free-tier account; all three accept the
chat URL as the "open the home" target, which is what the sidebar button
needs. A one-time offer migration adds them to any existing profile's
enabledEngines (users who disable one keep it off across restarts via a
new `enginesOffered` field).
Sidebar strip polish. The gear button now opens Settings › Sidebar on a
single click instead of popping a one-item native menu first. The "+"
add button under the gear is removed — adding a web app goes through
Settings › Sidebar anyway, which the gear opens. The quicklinks-add IPC
is kept but repointed to Settings › Sidebar (it used to open General,
which was the wrong page for the add-a-web-app flow).
User-authored artwork wins. mark.webp is the main icon; two sibling variants
(mark-wings-both.webp, mark-variant.webp) ship alongside for brand uses.
- bundled-addons/hermes/addon.json: version 0.2.2, icon/panels[0].icon are
now data:image/webp;base64,... from the main mark.
- bundled-addons/hermes/assets/mark.webp: served by the host via the panel
page's relative path (seedBundledAddons' fs.cpSync recursive carries it).
- bundled-addons/hermes/panel.html: both top-bar and signin inline SVGs
become <img src="assets/mark.webp"> at 24 and 72 px.
- site/hermes/index.html: hero SVG → <img> at 120 px.
- site/hermes/branding/{index.html,mark.webp,mark-wings-both.webp,mark-variant.webp}:
branding kit now carries the three webp files; the inline hero SVG uses
the main one.
- site-hermes-x/{index,branding,messenger}/index.html: same SVG → <img>
swap with the appropriate assets/ relative path.
- site-hermes-x/assets/mark{,-wings-both,-variant}.webp: sibling of the
existing favicon.svg and mark SVGs (kept for backward refs that still
point at them).
Legacy mark.svg + mark-stroke.svg + favicon.svg stay in place for any
external references; nothing in-repo points at them for the primary icon
slots anymore. favicons still point at the SVG files — safe to leave;
flipping those to webp is a one-liner if you want it.
0.3.99 installed users hit "Cannot find package 'nostr-tools' imported from
resources/lib/hermes.mjs" the first time they tried the Create account /
Open existing account button on the Hermes panel. The module was extracted
to resources/lib/ via extraResources (so the same .mjs/.js pattern as the
other lib files), but every other extracted .mjs happens to only pull
node:* builtins — hermes.mjs is the first one that needs real npm deps.
Node's resolver walks up from resources/lib/hermes.mjs looking for a
node_modules sibling and finds nothing: node_modules lives inside app.asar,
unreachable from an extracted file.
Fix: extraResources the nostr-tools dir + its three dep namespaces + the
one WASM dep so the walk finds them.
The gear popover at the bottom of the left strip is reduced to a single
"Open sidebar settings" item; the overlay/push radio and "Reset panel
widths" controls move to Settings › Sidebar permanently, so there's one
source of truth for the sidebar's own options.
A new sparkle button is added below the icon stack: it opens the user's
default AI engine (any search engine with kind=llm — Perplexity ships
on by default now; Phind is in the catalog). Click opens the engine in
the left panel like the existing web-app quick-links; right-click (or
the small chevron in the button's corner) pops a native menu of every
enabled LLM, each one-click to switch default + open. The button hides
itself when no LLM engine is enabled.
Hermes consolidation. The hermes.x web quick-link added in 0.3.99 is
retracted — the bundled Hermes extension already registers a left-strip
panel (side: "left"), so the web link was duplicating it. Any profile
that caught 0.3.99 gets the stray entry yanked on first load.
Ctrl+Shift+E now opens the Hermes panel in the active window's left
sidebar instead of the standalone Messages BrowserWindow; the window
is still reachable via the ⧉ pop-out button in the panel header for
users who want a free-floating client. The home-page Silent Mode card
for Hermes.X comes back (it's a link to the web page, not a shortcut
to the panel — different purpose).
Two more resolvers under Settings › Names & Certificates. SNS — .sol names
resolve through Bonfida's public SDK proxy, preferring an explicit URL
record over an IPFS record (served via a configurable IPFS gateway).
Unstoppable Domains — the full UD TLD roster (.crypto, .nft, .wallet,
.bitcoin, .blockchain, .dao, …) through resolve.unstoppabledomains.com;
browser.redirect_url takes precedence over an ipfs record. .x stays with
BNS — the UD adapter explicitly skips it so a Sirius registration can
never be shadowed by an Unstoppable one.
Both proxies are Cloudflare-fronted; users on blocked IPs can swap the
endpoint in Advanced. The handshake adapter's skip-list grows to cover
.sol and every UD TLD so a user with HNS + UD + SNS all enabled gets the
specific adapter, never the generic hns.to gateway, for those names.
Hermes.X moves from the home page's Silent Mode card grid to the Theseus
left-sidebar quick-link strip, slotting in right after Proton Mail. The
default list grows to 6 entries; existing profiles pick it up through the
LATER_DEFAULTS one-time offer (same mechanism Proton used in 0.3.88).
Users who still have Hermes on their custom home page can remove it manually.
The seedBundledAddons() version check is strict less-than: a cached v0.2.0
in %APPDATA%\Theseus\extensions\hermes\ wouldn't be overwritten by a bundle
also at v0.2.0, even if the files inside changed. Bumping to 0.2.1 forces
a reseed on the next restart so all of today's panel + API work (three-form
parser, Settings panel, kind:0 profiles, phone discovery) ships without a
manual cache wipe.
Hermes handles are not tied to the .bch TLD — the user's names live on .x
today and the planned Hermes TLDs are .erm/.tel — so the hermes.x site,
the silentmode.st Hermes page and the add-on's description and panel now
say "on-chain registered name" and use alice.x / bob.x as examples.
Add-on wording ships on its own OTA; no version bump here.
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.
Third signin option on the panel: enter a bare name + password, Hermes mints
a 12-word BIP-39 mnemonic locally, derives the Nostr key, password-wraps the
mnemonic for later Settings → Backup, and persists the sealed sk the same
way the other paths do. Save-forever still handles routine signin — the
password is only used to reveal the seed phrase and only from Settings.
- main.js: hermesWrapMnemonic / hermesUnwrapMnemonic via scrypt (N=2^15) +
aes-256-gcm. Wrapped blob at <userData>/hermes-identity/mnemonic.wrapped.json,
plaintext JSON — the wrap itself is the protection. On sign-out the blob
is nuked alongside the sealed sk.
- main.js: init() now takes { create: true, handle, password }. Handle is
validated (1-32 chars, kebab-friendly) and stored as `<name>.erm` so a
mainnet free-manifest reservation lands on the same canonical form later.
Mnemonic-path init optionally takes a password too — "promote this
seed-phrase session to a password-protected backup" in one step.
- main.js: new _hermesAPI.getBackup({password}) and .hasBackup. status() now
returns handle + source so the panel can label the restored session.
- panel.html: "Create a new account" button on signin reveals handle +
password + confirm. ⋯ menu adds Settings… showing account info, display
mode picker (hermes:alice / alice.erm / alice@hermes.tel — persisted via
silentmode.storage), and the gated seed-phrase reveal. Reveal lays words
out 12-across with 1-indexed numbering so a user copying to paper doesn't
miscount. Backup section hides for vault / mnemonic sessions.
Still off-chain only — handles created this way are local labels until the
mainnet mint of the free manifest lets us register them against the
`hermes.erm` signed-records store (see [[hermes-name-scheme-plan]]).
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.
The sidebar panel used to be a launcher that opened the standalone Messages
window; now it IS the messenger — signin screen with "Open with Theseus ID"
(derives the Nostr key from the unlocked vault seed) or "Open an existing
account" (12/24-word mnemonic fallback), then a contacts + thread view with
a narrow-width single-column fallback. The ⧉ button still pops the standalone
window out, so the Ctrl+Shift+E flow keeps working.
- main.js: expose a global._hermesAPI with status/canUseVault/init/inbox/
send/markThreadRead/contacts* so the addon can proxy panel calls without
granting it ipcRenderer or crossing the HERMES_ONLY sender gate.
hermesEmit also fans out on an in-process __hermes_bus so the panel gets
live inbox/status pushes decoupled from hermesWin's lifetime.
- bundled-addons/hermes/index.js: proxies each allow-listed method onto
global._hermesAPI, forwards bus events to api.emit for the panel.
- panel.html: signin + app screens, mobile-style single column under 540px.
- Mark updated to the spread-wings variant: envelope sized up (34×22),
three tapered trails extending outside the viewBox so the wings don't
bunch at the back of the envelope at any size. Applied to the addon
icon (manifest base64), the panel header, both /hermes sites, and
site-hermes-x's favicon + mark SVGs.
Two more adapters under Settings › Names & Certificates:
Namecoin — .bit names resolve by calling name_show "d/<name>" on the
configured JSON-RPC endpoint and following `translate` or `alias` records
to a clearnet host. Pure IP records aren't followed yet (that needs
in-browser DNS override + TLS pinning, which comes later). Namecoin has
no free public RPC, so the toggle is off by default and the row carries a
"Needs an RPC URL" note instead of a "Not wired yet" one; set yours in
Advanced.
Handshake — non-ICANN hostnames resolve through the public hns.to web
gateway (user-overridable in Advanced). hns.to proxies the request to a
Handshake node and serves the on-chain content. A bundled list of common
ICANN TLDs (original gTLDs + popular new gTLDs + every ccTLD) keeps the
default collision policy ("icann-wins") from hijacking clearnet names.
Setting the policy to "hns-wins" removes that gate.
Both adapters follow the ENS adapter's shape — tryExtraResolvers in main.js
calls each in turn after the BNS lookup; the first one that returns a URL
loads it. Settings copy on both rows updated.
Earlier iterations left the nested site/hermes/* on the superseded winged-
helmet design and the Theseus addon on acid-green despite the branding
tokens moving to sky-mid. This brings every surface to the same mark + colour:
- TheseusNavigator/bundled-addons/hermes/addon.json: both icon fields
(toolbar + panel declared) swap from acid-green helmet to sky-mid
(#38BDF8) tapered-trails envelope. Picks up on next addon re-copy.
- TheseusNavigator/bundled-addons/hermes/panel.html: inline SVG swapped
from helmet dome paths to the tilted-envelope + 3 tapered trails. Uses
currentColor so the panel's own colour token drives it.
- site/hermes/branding/mark.svg + mark-stroke.svg: standard (3 trails) +
simplified 16-20px (1 trail) variants match site-hermes-x/assets/.
- site/hermes/branding/index.html: six inline mark cells (hero + 5 size
cells) swapped in bulk.
- site/hermes/index.html: hero mark SVG swapped.
Grep confirms all seven surfaces now carry 'rotate(-22 32 32)' — the
tapered-trails signature. Nothing left on the old helmet.
First real resolver behind the Names & Certificates toggles. When the ENS
switch is on, typing a .eth name in the address bar runs:
1. namehash(name) (EIP-137 keccak256 recursion)
2. eth_call resolver(bytes32) on the ENS registry
3. eth_call contenthash(bytes32) on the returned resolver address
4. decode EIP-1577 (varint protocol + CID) → ipfs|ipns|swarm
5. navigate to <gateway>/ipfs/<cid> (or /ipns/<cid>)
The adapter lives in lib/resolvers/ens.cjs; main.js's navigateTab calls it
through tryExtraResolvers after the BNS check and before the clearnet
fallback. Defaults: publicnode's free Ethereum RPC (Cloudflare's eth-RPC
returns "Internal error" for ENS calls in testing) and ipfs.io as the
gateway (Cloudflare retired their IPFS gateway in 2024). Both are
user-overridable in Settings › Names & Certificates › Advanced.
CIDv0 (Qm… base58) and CIDv1 (bafy… base32) both encode; swarm names
cache a "not yet supported" negative result for one minute until we add a
swarm gateway option. Positive answers cache for 10 min, negatives for 1 min.
New site-hermes-x/ — standalone .x site (own nav, own assets, own OG tags),
sibling to site-theseus-x and site-sirius-x. Internal nav stays on hermes.x:
Overview, Messenger (preview + UI mockup), Branding (brand kit). The only
cross-link is 'Get Theseus' pointing at theseus.x where appropriate.
Mark redesigned: tilted winged envelope with three tapered speed-trails on
the back-left (top longest, bottom shortest). Default colour sky #38BDF8 —
swappable via --sky in site-hermes-x/assets/shared.css. Alternate orange
palette documented but not applied by default. Mark propagated to:
- site-hermes-x/assets/mark.svg + mark-stroke.svg (16-20px variant)
- site/hermes/{index.html, branding/{index.html, mark.svg, mark-stroke.svg}}
- TheseusNavigator/bundled-addons/hermes/{addon.json, panel.html}
Theseus addon moves out of the top-right extensions dock and into the
left-strip sidebar: panels[].side = 'left' + registerSidebarPanel({side:'left'}).
Appears alongside Mail/Pithos/Telegram, excluded from rightPanels() so the
extensions dock no longer lists it. Verified via sidebarState() CDP query —
hermes:main is no longer in the right-panels list.
site/hermes/ (the nested silentmode.st/hermes page) also updated to the new
mark for consistency; both the sovereign hermes.x and the silentmode-nested
page now show the same winged envelope.
The left-column icons return to a fixed 3-column grid, each icon in its own
cell. Drag reorders which cell holds which icon, with before/after indicators
matching the right-side cards. Drops out of x,y positioning: no more free-
floating icons sticking against the card grid; defaults flow left-to-right
across the three cells. The "+" add button sits in the next free cell at
the end of the slot grid.
Homepage max-width widens to 1680px (from 1400) so on wide screens the left
column doesn't crowd the cards. Legacy x,y values on stored icons are
ignored by the renderer (array order is what shows).
New top-level settings page lets the user pre-configure which non-BNS name
systems Theseus will resolve (ENS, Namecoin, Handshake, SNS, Unstoppable)
and which extra TLS roots it will trust (Russian National CA, scoped by TLD).
BNS is always on; everything else is off by default. Each resolver row has
its own Advanced disclosure for RPC URL, gateway, endpoint and collision
policy. The Russian CA row's Advanced holds the comma-separated TLD scope
list that limits what the CA can sign for (default .ru/.рф/.gov.ru/.online.ru).
State persists to %APPDATA%\Theseus\extra-resolvers.json via settings-only
IPC (extra-resolvers-get / extra-resolvers-set / extra-resolvers-reset,
origin-gated to the settings page). The adapters and setCertificateVerifyProc
hook that read this file land in 0.3.96 (ENS) → 0.3.99 (Russian CA); each
row's "Not wired yet — coming in 0.3.9X" caption goes away as its adapter
ships. No home.html change in this release.
Shortcut: Ctrl+Shift+M -> Ctrl+Shift+E. Chrome owns Ctrl+Shift+M as
'switch profile' and E matches the Greek spelling (Ermis).
Sidebar discoverability: new bundled add-on at bundled-addons/hermes/
registers a left-strip icon + launcher panel. The panel is a thin chrome
(mark + 'Open Messages' + shortcut hint) that forwards clicks through
api.onMessage -> ipcMain.emit('hermes-open-request') -> openHermesWindow.
All real messaging code stays in main.js — add-on is purely for discovery.
Address book: opt-in vCard (.vcf) import. Dialog in the Add Contact modal
lets the user pick a file; main.js parses BEGIN:VCARD blocks for X-BNS-NAME
/ X-NOSTR-NPUB / X-NOSTR-PUBKEY plus .bch names and npubs mentioned in
NOTE fields. Candidates are previewed per-contact with checkboxes; nothing
is added until the user confirms. Non-matching vCards are returned too (so
the user can still add manually) rather than silently dropped. Nothing is
uploaded; parsing is local.
site/hermes: status badge flipped from 'designed - not yet built' to
'MVP - desktop'. New 'What ships today' grid covers threaded chat, send by
name, address book, local history (all live) and mobile + Tor (next /
planned). Hero carries the new Ctrl+Shift+E shortcut and a link to
/hermes/branding.
site/hermes/branding: new page. Mark SVG (winged envelope, two variants
for 16-20px and 24-160px display sizes), palette (reuses Silent Mode
tokens + an amber accent that's distinctly warm-yellow, not Hermes-orange
— so it never collides with Hermès the fashion house), name explanation
(English Hermes / Greek Ermis), voice notes, do/don't table, MPL-2.0
attribution.
Hermes/MOBILE-RECOMMENDATION.md: analysis of three mobile paths (inside
Ariadne / standalone / hybrid). Recommendation is Option 1: ship as a
screen inside Ariadne. Reuses the wallet seed, aligns with the design
doc's 'one seed many purposes' architecture, lowest total cost.
The left column goes back to free positioning: each icon stores its own
x,y and snaps to a 20px grid; a faint grid shows underneath while dragging
so placement feels OS-desktop. Live-move via pointer events (clicks still
navigate thanks to a 4px move threshold). Dropping a left icon over the
right card grid re-parents it as a card; dropping a card onto the left
area turns it into a free-positioned icon.
Silent Mode cards get their one-line description back under the title.
Add/edit modal gains a Description field. Defaults seed sub-text on every
Silent Mode card and x,y on every Your-links icon.
Builds on the previous Hermes sessions (NIP-17 crypto + chipnet hermes.bch
+ Theseus Messages window) to turn the flat inbox into something usable.
main.js (hermes block):
- Persistent inbox per identity at
<userData>/hermes/<pkHex>/inbox.jsonl (append-only JSONL, replayed on
init). Read-state at <userData>/hermes/<pkHex>/read.json so unread
badges survive restarts.
- Address book at <userData>/hermes/contacts.json: list / add / update /
remove. hermes-contacts-add resolves .bch name OR npub OR hex pk,
canonicalises, writes.
- Reverse-resolve consults contacts first, chain second: a user-curated
nickname wins over the on-chain np lookup.
- hermes-send also records the outgoing message to the inbox so thread
view shows both sides (only when at least one relay accepted).
- Inbox messages carry direction + peerPkHex + peerName so the renderer
can group by thread without re-doing the pairing logic.
- --messages CLI flag opens the panel at launch (first instance via
whenReady, second-instance via handleLaunch) so the dev harness and
power users don't need Ctrl+Shift+M.
- Dev-only auto-init: THESEUS_HERMES_DEV_MNEMONIC env, gated on
THESEUS_USER_DATA so it refuses to touch a production profile.
- HERMES_INBOX_LIMIT raised 200 -> 2000 now that history actually sticks.
messages-preload.js:
- Adds hermes.contacts.{list, add, update, remove} and
hermes.markThreadRead; status + onMessage/onStatus unchanged.
messages.html:
- Replaces the flat inbox with a two-column layout: contact list (search
+ unread badges + last-message preview) on the left, selected thread
on the right with sender bubbles, timestamps, inline Enter-to-send
compose, remove-contact affordance.
- .bch suffix stripped from displayed names (TLD is noise in chat UI;
other TLDs stay as disambiguation).
- Mark-thread-read fires on open; vault-bind path kept intact.
The left column grows from a single narrow stack to a 3-wide grid of slots
so added icons flow sideways instead of pushing the page tall. The "+" add
button sits in the next free cell of that same grid. Drag-reorder on the
left column switches from above/below to before/after indicators to match
the right-side cards; both sections are 2D grids now.
Pulls the Theseus Navigator hero (compass + title + search + .bch tip) back
to the top so identity + search stay the first thing on the page. Underneath,
the two-column desktop keeps the shape from 0.3.91 but drops the second
search bar: a narrow left column of stackable slot tiles (hello.bch,
sia.coin, Pithos.sia, Bergamot.X, + add) and a 3-column grid of nine Silent
Mode cards on the right. Right-click opens context menus — background edits
(change bg, icon size, add link/card, reset) on empty space, and copy image
/ copy link / open / edit / remove on any icon or card. Copy image renders
the brand mark to a 256px PNG and writes it with navigator.clipboard.write.
Slot order is HTML5 drag-and-drop between either column; schema is v5 and
passes through migrateCards unchanged.
Three changes to the desktop home that landed in 0.3.90:
- Hero header is back. The compass mark, Theseus Navigator title, tagline
and search sit centered at the top of the page, same shape as the home
before the desktop redesign. The Edit button moves to the top-right
corner so the hero reads as identity, not chrome.
- Silent Mode on the right is CARDS again, not just tiles. Each card
carries the brand icon on the left, title + one-line description
stacked, and a muted acid-colored badge on the right (Navigator /
Registrar / Code host / ...). Cards sit in a center-right column that
stops before the right edge, giving the left column breathing room.
- Your-links on the left is free-drag, OS-desktop style. Each icon
stores x,y and snaps to a 20px grid; dragging an icon moves it live
while the grid faintly shows underneath. The schema gains x,y on
Your-links items — unchanged for Silent Mode cards. Legacy icons
without x,y auto-layout in a tidy 2-column grid near the top.
Compass mark replaces the chain emoji in the hero. The on-disk schema
nudges to v4; v3 and v1 reads still migrate (missing x,y get an
auto-layout fallback at render time).