Three home-page tweaks:
- Removed the YOUR LINKS and SILENT MODE section headings; the two
panels now stand on their own without labels.
- The + Add card cell now matches the real cards' size and shape so
it reads as a peer of the grid rather than a short dashed strip.
- Empty slot cells in the left grid are invisible by default and
only reveal their dashed outline when you hover the strip or drag
a link over them. The + add-link cell stays visible as the
discoverable entry.
AI engine picker:
- The chevron badge on the AI button is now a plain span with the
button role, not a nested <button> inside the main AI button.
Chromium flattened the invalid nesting so the chevron's own click
handler never fired; the picker now opens from the chevron and
from keyboard Enter / Space as well as right-click.
Performance (Settings > Performance > Startup):
- Pithos ships with activation: on-demand; its control server for
s3d no longer boots at launch. Wakes on first panel open or a
pithos.sia navigation.
- Hermes relay auto-restore (1.5 s after launch) is now gated by a
new setting hermesAutoRestore (default on). Turn it off to skip
background Nostr relay connections for users who don't live in
Hermes.
- New setting pithosAtLaunch (default off) mirrors the existing
walletAtLaunch so users who do want Pithos ready at launch can
opt back in.
- The Lightest preset now also turns off pithosAtLaunch and
hermesAutoRestore.
Setting the connector up meant copying a command with a token into a
terminal, and since 0.3.107 the token rotates whenever the connector
stops, so a configured client silently lost access after any extension
toggle and had to be set up again.
The panel now has a Connect button per app. Claude Code is registered
through its own CLI (claude mcp add-json, user scope), so its config is
written by Claude Code itself; Claude Desktop gets only the theseus entry
in claude_desktop_config.json, with a backup and no write to a file that
does not parse. Apps connected this way are re-registered after every
start and every Revoke when the address or token no longer matches. The
panel opens by itself the first time the extension is turned on. The
copy-a-command route stays under "Set up by hand".
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
When the relays were unreachable (after a sleep, or a relay offline) the
relay client reconnected with no delay at all, re-signing the queued
wallet_ready on every lap. On Theseus's main thread that spun a core for an
hour, grew the process to 2.5 GB and froze the browser. connect() now waits
2 s, 4 s ... up to 60 s between tries, and starts over once a connection
has held for a minute.
Wallet permissions were filed by the origin Theseus reports, which is
https://name for both registries. Whoever registered a colliding name in
BNS inherited the real site's connection and its silent-payment allowance,
and the other way round. A page served from BNS is now filed, and named on
every approval, as bns://name.
Grants saved before this stay under https://name: a saved connection only
discloses an address and everything else still prompts, so the BNS site of
that name simply asks again. Payment allowances are reset once, because
nobody can say which registry's site an old one was granted to.
Needs a host that reports the registry; older ones behave as before.
Start of the next batch; 0.32.1 is published.
Bitcoin inputs were given the BIP125 sequence that opts in to
replace-by-fee. Aegis does not replace transactions on any chain and has
no way to bump one, so advertising a payment as replaceable only told the
recipient to distrust it until confirmed. Inputs are final again.
Since Theseus 0.3.80 the vault PIN can be 6 to 8 digits, but Aegis's
pads still submitted at six, so anyone with a 7- or 8-digit PIN got
"wrong length" from every Aegis prompt and had to use the master
password. The lock-screen, transaction and reveal pads now draw as many
dots as the host reports (pinLength) and submit at that length; a
wrong length is explained instead of shown as a wrong PIN. Aegis's own
PIN on older hosts stays at six.
Export JSON… gives scripts and agents one file with endpoint, key, secret
and drive. The desktop app now ships for Linux too (AppImage, .deb,
tar.gz), using per-user s3d paths there.
Files in your own Sia account can now be shared with anyone through a
navigate.st/sia link that keeps working with this computer off; the
navigate.st service is live, so the buttons can ship.
On a host with api.vault.pin, Aegis no longer keeps a PIN of its own:
its lock-screen, reveal and transaction pads send the digits to the
Theseus vault PIN, which is checked in main against the one strike
counter Settings, the unlock prompt and Pithos also use. The vault is
opened there, and the panel receives a single-use proof (two minutes)
where it used to receive the master password; vaultUnlock, revealSecret
and pinGateSatisfied accept it, still bound to the request it was
entered for. The master password no longer passes through Aegis or its
panel for a PIN entry.
An existing Aegis PIN moves to the vault PIN on its first correct entry.
If Theseus already has a different PIN, the user is asked once which
one to keep. Setting and removing the PIN act on the vault PIN, and
Settings says that it is shared. Hosts without the API (Theseus
0.3.74-0.3.76) keep Aegis's own PIN exactly as before.
- Electrum: a reply larger than 8 MB ends the connection; a server
streaming without newlines used to grow the buffer without bound and
re-parse it on every chunk, on the Electron main thread.
- Overlay amounts are formatted from integer strings: 0.1 ETH read
0.100000000000000006, and 1 ETH + 1 wei read 1.
- The panel escapes a broadcast txid shown without an explorer link.
- A TPM refusal hands back only its own PIN attempt, not the count
before it, which could undo guesses made in parallel.
- BCH WIF imports must carry this network's version byte and a valid
compression flag; a 64-byte Solana keypair must have a public half
that matches its secret half.
- WizardConnect: the overlay shows the network fee (exact, as
SIGHASH_UTXOS commits to the inputs), marks outputs to this wallet's
own addresses, lists every token input and every output instead of
"... and N more" (more than 30 is refused), and a very high fee or
token inputs get the danger button.
- DAI permits: expiry 0 reads "never expires", and the allowed flag is
read the way the encoder signs it.
Dapp calls resolved their wallet afresh every time: Ethereum took the
first ready wallet on the site's chain, Solana and Tron the sidebar
selection. A site connected to wallet B was served wallet A, choosing
another wallet in the sidebar re-pointed every connected Solana/Tron
site and told it the new address, and a connected site could list
every EVM wallet's address by looping wallet_switchEthereumChain and
reading the account after each switch.
Grants now record the wallet id and address the user approved, and
calls use exactly that wallet (older grants are bound on first use; a
missing wallet is an error, not a substitute). A switch to a chain
whose wallet has a different address is asked for. A site can no
longer "add" a built-in chain id with its own RPC and get a second
mainnet wallet; chainMeta has no chainId for built-in networks, so
that check never matched.
The HD wallet's plan listed the change output as a recipient with no
address, so a dapp payment overlay showed "To #2: undefined" and an
Amount and Total inflated by the change; the imported wallet's total
counted change and left out the fee. Plans now list payees only, with
total = payees + fee.
A site's memo went on-chain as OP_RETURN data without appearing on the
overlay, and could ride on a silent allowance payment. It is now a row,
and a payment carrying data always asks. A 32-byte-hash cashaddr was
forced into a 20-byte template, producing an unspendable script; it is
refused.
Non-Permit typed data was flagged only when its primaryType was on a
short list (Seaport, SafeTx, ...); any other order, relay or
account-abstraction type got a plain Sign button and no PIN. Every
signed field is now listed with its declared type, and a payload that
names an address other than the user's and the verifying contract
together with an amount, or carries raw bytes, gets the danger button
and the PIN; text-only payloads stay ordinary.
The encoder signed "false" as true, turned non-hex characters into zero
bytes, wrapped integers past their width (2^256+5 signed as 5) and
signed a non-array as an empty array, each while the overlay showed
the original value. Such input is now refused before any overlay.
Domain rows and the chain check use only the fields EIP712Domain
declares; others are labelled as not signed.
Solana: a ComputeBudget price the site added was paid on top of the
base fee but shown as "program ComputeB...: not decoded", so a
transaction could burn the balance in priority fees behind a plain
Sign button. The maximum network fee is now a row, and Assign, durable
nonces, Approve/ApproveChecked, SetAuthority, closing a token account
to someone else and very high fees get a warning and the danger button.
signAndSend also requires one signature slot per required signer.
Ethereum: only allowances of 2^255 and up counted as unlimited; 2^96
and up now does, and Permit2 approve, increaseApproval, NFT
safeTransferFrom and multicall are decoded. The estimate shown was
gas x the node's price even when the site set a far higher tip, which
is what is actually paid; it now uses base fee + the real tip (or the
full legacy gasPrice) and warns when the site's fee is far above the
network's or a fifth of the balance. A personal_sign over 32 raw bytes
is a hash a Safe or an order book will take as approval of something
unseen, so it gets the danger button and the PIN.
Tron: TRC10 sent along with a contract call (call_token_value) was
never read, contract types Aegis does not decode were shown by name as
if harmless, a truncated TRC20 call rendered as "undefined", and the
validity window was hidden. Those are now shown, flagged or refused;
the TronGrid draft check also refuses a memo, a permission id or an
expiration more than a day away.