Reviewed both pages at desktop and phone widths. Desktop was fine; on
phones three things were wrong. The top bar wrapped into four rows
because the font-comparison pill and full-size links all stayed; on
screens under 640px the pill is hidden and links and buttons tighten.
The dashboard menu rendered as five full-width blocks because the
wide-screen width:100% rule outranked the phone override; it is now a
row of pills, the wallet block collapses to one line (short address,
live balance, refresh), and the sub-tabs scroll sideways instead of
wrapping. The long address had also been pushing the whole page wider
than the viewport, fixed with minmax(0,1fr) on the grid column.
The market showed an empty filter bar and a one-line status when there
were no listings; it now shows a proper empty state with the two things
a visitor can do (sell from the dashboard, register a new name) and
hides the filters until there is something to filter. Buy buttons go
full width on phones.
Ariadne's resolver copy and the Android indexer still admitted every
REG under a cosign or hidden TLD, so they disagreed with the gateway
and Theseus about which names exist. Ariadne now carries the current
Argus resolver-web.js verbatim (the copy had drifted: no TLD beacon, no
owner tracking, old electrum list); its daemon only imports buildIndex
and normalizeName, both unchanged. Bns.java gains the same rule in
Java: walk the TLD beacon, keep each TLD's TUPD policy timeline, and
drop a REG whose TLD required a co-signature at that height unless the
transaction re-issues the TLD certificate. If the TLD beacon cannot be
read the mobile index degrades open rather than empty. Needs an APK
build (0.19) and an Ariadne zip rebuild to reach devices.
Dashboard balance: it was read once from the gateway at sign-in and
never again, so payments and spends never showed. The sidebar and
Settings figures now come straight from electrum every 15 seconds
while the tab is visible, after every broadcast the page makes, on tab
focus and on a new refresh button; unconfirmed coins count as spendable
(listunspent includes the mempool) with a small "incl. N sat
unconfirmed" line. The header wallet pill asks the dashboard for a
fresh figure when opened, and the register flow keeps its stored
figure current while it watches for funding.
The dashboard was capped at 1200px with single-column lists, so a
1920px display showed a narrow strip of cards. Above 1280px it now
widens to 1720px with a 260px sidebar, name/TLD lists become card
grids (auto-fill, 360-400px columns), and Settings and TLD detail lay
their cards out in two or three columns. Narrow screens are unchanged.
The header Dashboard button was hidden until sign-in, which left new
visitors with no obvious way in; it is now always shown and lands on
the portal's sign-in when signed out.
Also closes a Settings-pane markup bug: the Wallet card ended one div
early, which pushed the "Reading, language & help" and "This device"
cards outside the pane so they appeared under every other pane.
The wallet dropdown had become a navigation menu (My names, Admin panel)
while wallet facts were nowhere. Now a Dashboard button sits left of the
wallet pill on every page while signed in, and the wallet menu shows the
address, balance and holdings, copy address / token address, the faucet,
wallet settings and sign out. The admin panel moves into the dashboard
sidebar and only appears for the operator wallet. Translations included.
The portal was a flat page with tabs that asked for the password or PIN on
every visit and offered little beyond a records form. Owners need a
control panel they can live in.
Sign in once: the recovery phrase is kept encrypted under a
non-extractable browser key (IndexedDB) so the next visit opens the
dashboard silently; sign-out or the Settings toggle destroys it. Payments
approve with one click unless "Ask for PIN before payments" is on.
Dashboard: left menu (Overview, Domain names, My TLD list, Wallet,
Register name/TLD, Settings). Per-name detail with a summary, a DNS
record table (A, AAAA, CNAME, MX, TXT, NS, SRV, CAA) that signs the
manifest, content & hosting (h, s3, p, ip, tls), a redirect tab (u),
ownership transfer (UPD that re-issues the certificate to the recipient)
and zone-file/JSON export. Per-TLD detail with policy, on/off, owner
registration and the public list of names under it. Overview flags names
that point nowhere or lack DNS.
Removes the sessionStorage plaintext-mnemonic cache (fixed under the
same commit) and replaces it with a PIN-encrypted blob in localStorage.
No plaintext secret ever touches disk or memory outside the live
BuiltInWallet object.
New js/pin-escrow.js — WebCrypto PBKDF2(50k) + AES-GCM(256). Public
API on window.siriusPin: savePinBlob(mnemonic, pin), tryUnlock(pin),
hasPin(), attemptsUsed(), attemptsRemaining(), clear(), MAX_ATTEMPTS.
Iteration count is lighter than BuiltInWallet's 250k because a 4-6
digit PIN's key space is small anyway; the point is 'not plaintext at
rest,' not brute-force resistance — the durable secret is the full
password.
register-flow.js:
- After a fresh password unlock (Import / Create / Unlock), stepSetPin
offers a 4-6 digit PIN with confirm — skippable with 'Not now'.
Never overwrites an existing PIN blob.
- stepUnlock now shows a numeric PIN pad when a PIN blob is present;
the password field only appears when the user opts to 'Use password
instead' or after the blob was wiped.
- Wrong PIN → increment counter, surface 'N attempts left'. Third
wrong PIN → wipe blob and route to a 'PIN reset' screen that hands
off to the password form.
- Correct PIN → decrypt the mnemonic in-browser, rebuild the
BuiltInWallet, finishSignIn(). Attempt counter resets to 0.
profile-menu.js:
- Sign-out clears the PIN blob (via siriusPin.clear()) alongside the
siriusProfile so the device isn't quick-unlockable with a stale PIN.
All pages that host the sign-in flow now include pin-escrow.js. Same
tabbed layout in stepUnlock — the PIN pad and the password field both
live under 🔓 Unlock, transparent tab-switch works exactly as before.
Verified live:
- Fresh Import → 'Set a PIN' step → 4242 confirmed → blob written
- Reload → PIN pad, 3 attempts remaining
- Correct PIN 4242 → signed in, counter resets to 0
- 3 wrong PIN attempts → 'Wrong PIN — N attempts left' per attempt,
then 'PIN locked — enter your full password to continue', blob
wiped, next reload shows the password form
- localStorage contains only ciphertext + salt + iv + counter; no
plaintext mnemonic anywhere on disk or in sessionStorage.
Two issues reported after an Import sign-in:
1. Done button was stuck — user saw the ✓ Signed in screen but the
portal never switched to the names view. Root cause: finishSignIn
fired siriusProfileChanged BEFORE setting window.siriusWallet, so
the portal's listener called adoptWalletFromModal() while
window.siriusWallet was still null, saw no wallet, and did nothing.
Fix: expose the live wallet BEFORE writeProfile so the sync
listener sees it and can enterPortal() immediately.
2. Every reload asked for the password again. Cache the wallet's
mnemonic in sessionStorage on sign-in — same tab (or a page
reload) rebuilds the BuiltInWallet silently via
BuiltInWallet.fromMnemonic; a full browser close clears
sessionStorage and the user is back at the Unlock tab.
sessionStorage is per-origin per-tab so an XSS on Sirius.X pages
would still be able to read it — that's the tradeoff for the
convenience. Chipnet only; mainnet gets the PIN escrow pattern
(3 wrong PIN tries → escalate to password) that Digibyte.x/web
already uses. PIN implementation is deferred to its own commit.
portal.html adoptWalletFromModal now tries sessionStorage after the
in-memory check; register-flow.js startFlow/startTldFlow do the same
via a new async adoptSessionWalletAsync so name and TLD mints on any
page reuse the session wallet with no re-prompt. profile-menu.js
sign-out clears sessionStorage + window.siriusWallet so signing out
really does drop the user.
Nav wallet dropdown was calling window.siriusSignInWallet(action),
which opens the shared modal on whatever page you were on — so from
tld.html or docs/, a Create/Import/WC click drew the sign-in over the
current page as a full-screen overlay. User: 'still opens as a
separate console on top of the page, it should be within the page'.
Now the click handler:
- if we are already on portal.html AND
siriusRenderSignInInline is loaded, paint the tabbed sign-in
inside portal's #signin-inline directly and scroll to it
- otherwise navigate to portal.html?mode=<action>, where the
bootstrap already renders the tabbed sign-in inline as the
page's own content
Same tabs, same forms — the four options (🔓 Unlock / 🆕 Create /
📥 Import / 🔗 WizardConnect) — but they now live inside the page,
not on top of it. Unlock falls back to Import when there's no saved
wallet in that browser.
Two features:
1. Operator can hide TLDs from the public /api/tlds listing so hidden
TLDs stop appearing in name-search UIs. On-chain registrations
under a hidden TLD keep resolving — this is a UX filter, not
enforcement.
Gateway (public-gateway.mjs):
- Persistent HIDDEN_TLDS set backed by hidden-tlds.json next to
the service script
- GET /api/tld-visibility -> {hidden:[...]} (public)
- POST /api/tld-visibility -> updates the list (operator-gated by
Bearer BNS_OPERATOR_TOKEN env var; if unset, all writes refused
so we default-deny)
- /api/tlds filters out HIDDEN_TLDS; add ?include_hidden=1 to see
everything (used by the admin panel to show all rows)
- Operator token installed via systemd override on the VPS
Admin panel:
- New 'Operator token' card at the top of the TLD-registry section;
token stored in sessionStorage (not localStorage) so a full
browser close forgets it
- Each TLD row got a 'Hidden from public' checkbox that POSTs on
toggle and refreshes the table; failures roll back the checkbox
and surface the error next to the token field
2. Wallet dropdown restored to 4 direct actions
(Unlock / Create a wallet / Import a wallet / WizardConnect) and
the shared mint/sign-in modal grew a tab strip so users can switch
between the four wallet actions from any step without going back
to a choice screen.
register-flow.js:
- renderTabs(active) prepended to stepCreate/stepImport/stepUnlock/
stepExternal when signInOnly is set. Unlock tab only appears
when a saved wallet exists.
- Delegated click handler on the sheet routes tab clicks to the
matching step; switching away from a live WC session tears it
down first so we don't leak WebSockets.
profile-menu.js:
- Restored 4-item onboarding menu (Create/Import/WC plus Unlock
when saved). Each item is a direct entry point; the tabbed modal
lets the user pivot to any other option without closing.
Cache-buster bumped on all 8 sirius-x pages to ?v=20260908tabs.
Two changes:
1. Wallet dropdown simplified from three direct-action items into one
'Sign in or create wallet' launcher (plus 🔓 Unlock when a saved
wallet is present). The single item opens the shared modal at
stepWallet — the choice screen showing all four options (Unlock /
Create / Import / WizardConnect) as clear cards. Clicking a card
reveals its form. Users see the options first, then commit to a
path, instead of landing on a form for one path without seeing the
others.
register-flow.js's stepWallet grew a signInOnly-aware header ('Sign
in or create a wallet' / 'Pick one. Your keys stay in this browser')
so the choice screen reads as sign-in context, not a name mint.
startSignIn still accepts direct-mode entry points (new/import/wc/
unlock) for deep-links like portal.html?mode=X, but the nav dropdown
funnels to 'choose' for the choice-first experience.
2. Theseus subpage copy updated per request: 'native .bch / BCNR name
resolution built in' -> 'native Bitcoin Cash Domain Names resolution
built in'. Meta description mirrored.
Previously, the only way to unlock a wallet that had been saved in this
browser was to click Register (name or TLD), open the modal, and pick
'Unlock my browser wallet' from the wallet-choice step. From the nav
dropdown you could only start onboarding fresh (New / Import / WC).
profile-menu.js now checks localStorage for the BuiltInWallet's storage
key (bns.wallet.v1) at every render, and when a saved wallet is present
inserts a '🔓 Unlock my wallet' item at the top of the not-signed-in
dropdown — with a divider below it, so it reads as the primary action
and the Onboarding options stay available for adding a different wallet.
The dropdown click handler already lazy-loads register-flow.js and
calls window.siriusSignInWallet(action); register-flow.js's startSignIn
grew an 'unlock' mode that opens the modal directly at stepUnlock (the
password prompt). Same success path as every other sign-in: on unlock
success, siriusProfile is written and the modal closes with '✓ Signed
in' — the nav pill flips to the address without a reload.
Verified end-to-end on landing: saved wallet detected -> dropdown shows
Unlock as first item -> click -> password -> '✓ Signed in' -> pill
becomes 'qqyx49…zx8x seed ▾' with no reload. TLD mint from tld.html
also verified end-to-end: search 'e2etldtest' -> Register -> modal ->
Unlock -> password -> Fund -> Confirm (fee 1,250,000 sat + beacon dust
~1,300 sat) -> Register -> checking-availability -> loading-coins ->
'wallet is empty' (expected without chipnet funds; downstream code is
the same registerTldWithBuiltInWallet path the CLI uses).
Wallet dropdown items (New / Import / WizardConnect) were navigating
to portal.html?mode=X and asking the user to click again on arrival.
That is 'the sign in landing page which is not functioning' from the
user's perspective — a whole redirect for one form.
Now every dropdown item opens the mint modal (register-flow.js) inline
at the matching step, on whatever page the user is on:
New -> stepCreate ('Create your wallet' — password + generate)
Import -> stepImport ('Import a recovery phrase' — textarea)
WizardConnect -> stepExternal ('Open your wallet' — QR/URI)
register-flow.js grew a signInOnly mode: state.name is null, the step
Back buttons close instead of going to a wallet-choice step there is
no context for, and on wallet-loaded the flow writes siriusProfile
and shows a 'Signed in' confirmation instead of Fund -> Confirm -> Mint.
WC signInOnly keeps the session alive (state.session) so a later
record edit can reuse it without a fresh QR handshake.
profile-menu.js: menu items became <a data-action='new|import|wc'>
and the click handler lazy-loads register-flow.js on demand — the
docs/brand/theseus pages don't ship it in their initial payload, so
their nav pill loads it the first time a wallet button is clicked and
caches it for subsequent opens. Falls back to portal.html?mode=X if
the module can't load. Cache-buster bumped so cached copies pick up
the new behavior.
Importmap for @bitauth/libauth added to docs/, brand/, theseus/ so
the bundle's bare specifier resolves when register-flow.js is
lazy-loaded from those pages.
Verified end-to-end on the live docs page: all three dropdown items
open the modal inline at the correct step, no console errors, no
navigation.
Three UX fixes:
1. Beautiful footer on every page. Extracted the 4-column landing footer
into js/site-footer.js — reads the nav brand link to derive per-page
base ('./' at root, '../' in subdirs) so links resolve from any depth
without duplicating the HTML across 8 pages. Every page now ships an
empty <footer id="site-footer"></footer> and includes the injector;
inline footer + its CSS block removed from landing too so the source
stays in one place.
2. Wallet button redesign. Pill now shows a state dot (dim when not
signed in, green with subtle glow when signed in), a monospace short
address label when signed in ('qra6rs…7yl2') OR 'Wallet' when not,
a WC/seed badge on signed-in state, and a subtle caret. Full border
and hover states, proper aria-expanded/aria-label wiring. Dropdown
menu itself upgraded: grid layout per item (icon column + title +
hint), heavier backdrop blur, larger min-width, clearer typography.
3. Dropdown options actually do something. Removed the redundant 'Open
sign-in page →' item — the other three all land on portal too, so
listing 'open the page' as a fourth option was noise. On portal
arrival, the ?mode= handler now AUTO-TRIGGERS the primary action for
each mode:
new -> click Generate a phrase (12 words appear immediately)
import -> focus the seed textarea (cursor ready to paste)
wc -> click Connect wallet (WC session starts, URI shown)
No extra clicks between dropdown choice and the flow it names.
Cache-buster on the profile-menu.js include bumped to
?v=20260907wallet so cached copies pick up the new design; footer
injector at ?v=20260907rel.
Five UX fixes from a review pass:
1. profile-menu.js: dropdown links (New/Add/WC wallet, Admin) were
absolute /sirius-x/portal.html paths. That's fine on silentmode.st
but under the BCNR route (sirius.x/) the origin is different, so
the gateway forwarded to Sia which returned 'NoSuchKey' XML. Now
derive the base URL from the nav's own .portal anchor href — which
is already set per page with the right relative path — so it works
from the root, from subdirs, and under any BCNR gateway. Admin URL
is derived from the same base.
2. Landing hero got a search input. Submitting it normalises the
label ([a-z0-9-], 63 chars) and redirects to register.html?q=<label>.
register.html now honours ?q= on load: prefills the input, triggers
the parallel multi-TLD lookup, and scrolls into view. Single source
of truth stays in register.html; the landing just hands it a query.
3. Portal TLD-mint error path now special-cases 'wallet is empty' and
shows a friendlier message with the wallet's address and links to
two chipnet faucets, so the fix is one click away instead of a
guess.
4. Removed 'Pantheon' from every page's top nav — it's a section on
the landing that anyone scrolling will discover, and keeping it in
the nav crowded the bar.
5. Cache-buster ?v=20260907rel on the profile-menu.js script include
across all 7 pages so browsers that cached the pre-fix version
pick up the new one on next load.
Closes the loop between the nav's profile dropdown and portal.html:
- Adds a 🆕 New wallet card that calls BuiltInWallet.create() to generate
a fresh BIP-39 phrase in the browser. The phrase is shown once for the
user to write down; sign-in only unlocks after they tick the 'I have
written this down' acknowledgement. Copy button included.
- Sign-in (both New and Import paths) now writes localStorage.siriusProfile
= { address, tokenAddress, signedInAt } so any page on the same origin
can render 'signed in' state. Sign-out clears the key and also resets
the New wallet card so a re-sign-in starts clean.
- Handles ?mode=new|import|wc from the profile dropdown's deep-links:
scrolls the matching card into view and briefly outlines it in acid so
the user knows which one they were sent to.
profile-menu.js:
- Re-renders the dropdown on every open() call rather than caching the
first paint, so a sign-in that happens *after* the script's initial
run (same tab: portal.html; other tabs: storage event) reflects in the
nav without needing a full reload.
- Listens for the storage event (cross-tab) and a custom
'siriusProfileChanged' event (same-tab) — portal.html fires that on
every writeProfile/clearProfile call.
registrar.js: BNS.connect() now filters silentmode.st/electrum and
coinspectrum.duckdns.org:50011 out of the default electrum list. Those
endpoints are our own bns-indexer.js — beacon-only, serves get_history +
transaction.get but NOT listunspent for arbitrary scripthashes. Every
wallet op (getBalance, getUtxos, edit signing) needs listunspent, so
picking a beacon indexer first (which we did for best reachability) broke
every wallet unlock with "-32601 unsupported method: blockchain.
scripthash.listunspent". New `beaconOk: true` opts back in for pure-
resolution paths.
Rebuilt the browser bundle (site/js/bns-register.js) so the fix reaches
portal + admin + register.html + anything else that imports BNS.connect.
site-sirius-x/js/profile-menu.js: a small shared script that transforms
the "🔑 Sign in" nav pill into a dropdown menu on every sirius.x page.
Signed-out shows New wallet / Add wallet / WizardConnect + a link to the
sign-in page. Signed-in (reads localStorage 'siriusProfile') shows the
short address, My names, Admin, Sign out. Included via one <script defer>
tag on each of the 6 pages (landing / portal / admin / docs / theseus /
brand); dropdown CSS is inlined by the script itself so consumers don't
need a matching stylesheet.
Portal.html writing to localStorage.'siriusProfile' after sign-in is a
follow-up so the dropdown reflects state across pages — until then the
menu always shows the onboarding options.