aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/nostrlogin.js
Commit message (Collapse)AuthorAgeFilesLines
* Feed: a wheel scroll through a loading feed no longer stutters on a laptop. ↵Adam Soltys3 days1-4/+14
| | | | | | The phone was fine (its scroll is the compositor's) but the desktop's wheel coast is driven from the main thread, so every long task froze it, and a feed that was still loading ran dozens of them: each ready post went in through its own admitFeed → holdScroll → full render (200–450 ms apiece; a Popular switch did 49 in a few seconds, one of them 1.3 s). Inside those renders the window's list getter — run on every scroll frame too — filtered every note through the mute/spam/report checks, and each check re-derived the login identity (two parses of the stored state plus a bech32 encode): 11 s of a 2-minute profile. Now ready posts are admitted together, one render per 120 ms, and while the reader is moving the page the batch waits for a quiet moment (up to 1.5 s); nostrLoginIdentity keeps its answer for 250 ms (dropped by its own saves, a signer change or another wallet's key), npubOf is memoized, modOn reads the cached messages state, the foreign-language guess is remembered per post, and the window list is reused between renders. Measured on Adam's dev tab (tools/feed-scroll-jank.js: real CDP wheel notches, chip clicks mid-load, rAF frame gaps, long tasks, a CPU profile that names each busy stretch): stalled time during a switch 1.9–2.5 s → 0–83 ms, no frame over 50 ms on the wheel path, main thread busy 33% → 17%; settled scrolling was already clean. feed-streaming's "tap reveals the oldest ready new post" fails on the previous commit too. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Lock with fingerprint or face. The padlock's "Protect this device?" now ↵Adam Soltys4 days1-1/+1
| | | | | | | offers a passkey instead of a password, and Settings → Security can turn it on or off. The saved-wallets vault is sealed under a secret only that passkey's PRF produces (behind the phone's fingerprint/face check, the passkey kept by the platform's secure hardware or password manager), so nothing in this browser's storage opens it. Opening the app on a locked device asks for the passkey by itself; a cancel leaves an Unlock button. The prompts that need the vault opened (save, forget, lock a stray wallet) take the passkey too; there's no Change password for a vault that has none; the recovery words remain the way back. The lock passkey has its own PRF salt and a marked user handle, not discoverable where the platform allows — passkey sign-in refuses it rather than derive a stranger's key from it. English + Italian strings. tools/passkey-lock-test.js (Chrome's virtual authenticator with PRF). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Messages: the Amber notice no longer waits for Amber to wake. "Messages sent ↵Adam Soltys5 days1-2/+4
| | | | | | | to you can't be opened here · Connect Amber" showed only once the Amber signer had resumed, and the DM drain is what resumes it — on a phone whose every wrap the old code had already logged as opened, nothing is pending, so nothing might ever ask. nostrLoginIdentity now says how the account came in (`via`), and an Amber account counts as one whether or not its signer is live yet (the notice, and the flag that re-opens written-off wraps once a decrypting signer takes over). tools/amber-dm-upgrade-test.js runs that case with ALL_LOGGED=1. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Messages: Amber users can read their DMs. Signed in with Amber by intent ↵Adam Soltys5 days1-6/+35
| | | | | | | (NIP-55), nothing sealed to the login npub was ever decrypted — each decrypt would be a trip through the app — so isolabellart's thread with Adam showed only what that phone had sent. Worse, decryptorsComplete() counted the Amber signer as complete, so those wraps were tried on the wallet key alone and logged as opened, hidden for good. Now an interactive signer counts as incomplete (wraps wait, uncounted); Messages and the DM thread say "messages sent to you can't be opened here" with Connect Amber, which opens a nostrconnect:// link for the SAME account over NIP-46 (Amber's bunker mode; a different npub is refused, a callbackUrl reload resumes the handshake). Once a signer that can decrypt takes over, a wallet that ran with Amber re-runs the heal pass over every wrap the relays hold, so messages the old code wrote off come back. tools/amber-dm-upgrade-test.js (plays the remote signer over a shimmed relay). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Amber (NIP-55) signs you in on Android: one intent per request, the answer ↵Adam Soltys2026-09-271-2/+30
| | | | | | | | | | | | | | | read off the clipboard or pasted. Closes #7. A "Sign in with Amber" door on Android opens nostrsigner: intents for get_public_key, sign_event and nip44 encrypt/decrypt, with a waiting card that reads the clipboard when the tab returns (or takes a paste). Requests queue one at a time — a sign-in fires the backup, relay list and follows together — and each answer is checked for its type so a stale clipboard is never mistaken for one. An interactive signer is not asked to decrypt DMs or private lists that arrive on their own; each trip through Amber is a deliberate one. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* New identities start with something to read: Adam and coinos followed, a ↵Adam Soltys2026-09-211-0/+1
| | | | | | | | | | | | | | | Bitcoin feed, and #gardenstr. A wallet the wizard made from fresh words, or a sign-in that found no wallet on its identity, gets one kind-3 update following the two coinos accounts (a sign-in identity only if it follows nobody yet), plus two starter feeds. The Bitcoin feed has a follow-pack slot (STARTER_BITCOIN_PACK) and reads the #bitcoin topic until a pack is chosen; #gardenstr shows what a topic feed is. Once per identity. The feed test now runs through the real first run, reading the words off the screen. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* Seed words read down each column; the Google central is a parameter with a ↵Adam Soltys2026-09-211-2/+11
| | | | | | | | | | | | | | door to the previous one. Word grids flow down a column then the next, with the row count from the word count — on a phone the first column is 1–6, the way a hand copies a list. pomegranate.js takes the central as an option (CENTRALS.coinos is auth.coinos.io, now deployed; the default stays auth.njump.me until its Google client is live), and the sign-in card grows a "previous server" line once the default moves, so accounts made under njump keep working. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* A remote signer that drops its socket comes back by itself.Adam Soltys2026-09-141-1/+22
| | | | | | | | | | | | | | | | "My signer keeps disconnecting": a phone freezing the tab cut the relay socket the bunker session talks through, every request after that hung to its deadline, and the app declared the signer gone — a Reconnect button, tapped over and over. Three changes: the bunker client now talks through a pool that pings and reconnects on its own, so a dropped socket is re-opened and the subscription re-fired without anyone noticing; a request that does fail on a dead connection drops the session and dials again silently, so the notice only appears if that resume fails; and coming back from the background probes the signer with a quick question, replacing a dead session before the next real request runs into it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
* Each account in the switcher wears its own face, and its own renewal warning.Adam Soltys2026-09-101-10/+23
| | | | | | | | | | | | | | | | | | | Two accounts could show the same name and avatar: the identity list read a key stamped onto the account record, and the stamp was taken during the gap in a wallet switch where the new account is already loaded and saved but the features haven't re-inited — so the PREVIOUS account's signer was still answering "who are you". The live signer now re-checks on every read that it still belongs to the open wallet, not just at init(); and the switcher reads each account's own persisted login (or its seed's nostr key) instead of the stamped mirror, so a record stamped wrong before this still lists right. The Spending renewal warning belonged to the wallet that computed it and stayed on screen after switching — the old wallet's amount and deadline over the new wallet's balance, with a 30-minute throttle keeping it there. It now resets with the ark manager, and the next connect re-decides. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* Passkey identities stay signed in across reloads: the passkey-derived nostr ↵Adam Soltys2026-09-041-6/+27
| | | | | | | key is now kept in the wallet's nostrlogin state and resumeLogin reattaches from it instantly, so opening chat no longer greets passkey users with "Your nostr signer isn't connected". Kept deliberately and only for the passkey door: a pasted nsec keeps its used-once-never-stored promise, but a passkey key exists only through this app's PRF derivation and re-deriving it costs a WebAuthn ceremony (system dialog + biometric) on every open — and the same storage already holds the wallet seed, so this is the same exposure class. Existing passkey sessions heal on their next reconnect: that ceremony saves the key, and none is asked for again Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
* The Amethyst handshake survives its own launch: opening the signer app ↵Adam Soltys2026-09-021-3/+19
| | | | | | | freezes the tab (relay sockets die as the approval publishes) and its callbackUrl then RELOADS the page, discarding the client key mid-handshake — so the pending connect now persists to sessionStorage, the wait races the live subscription against a stored-event poller kicked on every return to visibility, and a fresh boot resumes the handshake instead of shrugging. Proven both ways with a scripted signer: approval after reload, and approval stored while the page was gone Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The Amethyst signer stops dying every time a stranger's wrap fails to ↵Adam Soltys2026-08-301-45/+96
| | | | | | | decrypt: only connection-shaped failures (with a real per-op deadline now — a backgrounded signer app used to just hang the call) drop the live bunker, the reconnect surfaces offer only the door the account actually came through (a recorded 'via', so an Amethyst-linked npub no longer stares at Google and passkey buttons), the settings card quietly reattaches from the stored session instead of waiting for a tap, and the DM retry drain eases to three full-strength passes per wrap Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* DMs stop losing the race with the signer, and your own address stops paying ↵Adam Soltys2026-08-301-0/+10
| | | | | | | yourself: undecrypted wraps are kept and retried when a signer comes live (the backfill used to be consumed and dropped whenever the login signer hadn't resumed yet — each device kept a different random subset of history), the inbox also reads the relays our kind-10050 actually advertises, and pasting your own ark address or coinos name into Send now opens the board panel from Savings instead of a silent Spending→Spending self-send; a foreign ark address while Savings is selected says to switch instead of quietly spending the other balance Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The address is fetched while the key still turns in the lock: sign-in races ↵Adam Soltys2026-08-281-1/+8
| | | | | | | a registrar lookup against the seed work and seeds the name before first paint, so the Receive pane opens straight onto its QR instead of "setting up your payment address" Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The wallet remembers it has a spending side: the relay backup carries the ↵Adam Soltys2026-08-281-4/+15
| | | | | | | hint, stamped the moment spending becomes real, so a restore paints Spending from its first frame instead of reflowing when the name lookup catches up — and the sign-in toasts are gone, since a wallet appearing announces itself Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The wallet arrives without ceremony: sign-in swaps screens instantly (a ↵Adam Soltys2026-08-271-0/+2
| | | | | | | one-shot no-anim flag on the handover), and the spending-setup ask holds its tongue until the name restore has settled instead of flashing while the answer is seconds away Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* One fade, not two: the onboarding wizard hands over before the screen flips, ↵Adam Soltys2026-08-271-2/+5
| | | | | | | so signing in no longer re-animates the welcome for a beat — the wallet rises in with its content already aboard Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Steady hands at the door: during sign-in the button's icon becomes the ↵Adam Soltys2026-08-271-4/+13
| | | | | | | spinner and the label stays put — same size, no column reflow — and the passkey button now shows the same quiet work instead of just dimming Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The face arrives with the key: sign-in warms the identity's profile and ↵Adam Soltys2026-08-271-0/+3
| | | | | | | preloads the avatar bytes in parallel with the seed work, so the home screen's first paint already wears the picture instead of a blank circle Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Each door leads to its own room: choosing Nostr shows only the nostr ways in ↵Adam Soltys2026-08-271-4/+8
| | | | | | | — Google and passkey stay on the porch where they already stand, though link and reconnect keep the full set, since a Google-born account needs the same door back Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* One tap, one dialog: a passkey sign-in with nothing on the device rolls ↵Adam Soltys2026-08-271-22/+11
| | | | | | | straight into creating one — the browser's own passkey sheet is the confirmation, and dismissing it is a quiet cancel, not an error Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The Google button keeps its own counsel: no flickering narration beneath it ↵Adam Soltys2026-08-261-9/+10
| | | | | | | — just a quiet spinner in place of the label while the popup does the talking Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* No pausing at the threshold: a fresh identity walks straight into its new ↵Adam Soltys2026-08-261-26/+7
| | | | | | | wallet — the confirmation interstitial is gone, since a brand-new key is the normal first run for Google and passkey users, not something to warn about Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The question finally gets asked out loud: the new-wallet confirmation ↵Adam Soltys2026-08-261-0/+5
| | | | | | | surfaces on the welcome screen too — a first Google sign-in was completing its whole sharding ceremony and then waiting forever on a confirm card only the unlock screen knew how to show Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Every door gets its crest: the four-colour G for Google, a person-and-key ↵Adam Soltys2026-08-261-6/+17
| | | | | | | mark for passkeys, sized and padded to stand level with the ostrich Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* All three doors on the porch: Google, passkey, and Nostr sign-in stand ↵Adam Soltys2026-08-261-13/+40
| | | | | | | directly on the welcome screen — Google and passkey act in place, the Nostr button steps into the signer list as before Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Two new doors, no new locks: "Sign in with Google" (pomegranate — the ↵Adam Soltys2026-08-261-1/+65
| | | | | | | nostr key FROST-sharded 2-of-3 across community operators, Google as coordinator and recovery, a plain bunker:// URI feeding the existing signer flow) and passkey sign-in (WebAuthn PRF derives the key deterministically, treated exactly like a pasted nsec, synced wherever the platform carries the passkey) — both lazy chunks, neither touching the first paint Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The page arrives light and paints at once: index.html shrinks to 46KB with a ↵Adam Soltys2026-08-261-4/+15
| | | | | | | static boot shell baked in — brand row, and the profile frame when the path names one — while the 789KB bundle moves to a content-addressed app-<hash>.js the browser keeps (a repeat visit re-downloads kilobytes, not the app); the nostr-tools barrel import goes subpath, and the NIP-46 signer client ships as its own lazy file fetched on first bunker login Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* A missing signer opens a door instead of a toast: the reconnect screen ↵Adam Soltys2026-08-181-0/+42
| | | | | | | offers the extension, the signer-app link, or a pasted key — same account enforced — and the silent reattach closes it on its own when it lands Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The login identity stays with the wallet that earned it: a live signer no ↵Adam Soltys2026-08-171-1/+16
| | | | | | | longer follows you into wallets that never linked it, where it claimed names, wore crowns, and answered to the wrong name Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The wrong-signer error names the whole npub — a truncated key can't be ↵Adam Soltys2026-08-151-1/+1
| | | | | | | checked against anything Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* The Nostr card owns its built-in account; the exported key opens this wallet ↵Adam Soltys2026-08-041-4/+73
| | | | | | | anywhere Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M5kFodm9azZJVTQxnkDdKe
* A restored login ends the wizard instead of stalling in itAdam Soltys2026-08-041-0/+2
| | | | | | | | | | Signing in mid-wizard with a key whose wallet was restored left you on the sign-in step: the back-button hold matched the restored account and blocked the auto-advance. A login now clears the hold explicitly — and a restored wallet skips the rest of onboarding entirely; it was already onboarded wherever it came from. Co-Authored-By: Claude Fable 5 <[email protected]>
* Never mistake a failed backup lookup for 'no wallet yet'Adam Soltys2026-08-041-5/+9
| | | | | | | | | | | | Signing in with a key whose wallet backup exists on relays could still land in 'create a new wallet' when the lookup failed (relays unreachable, signer decrypt denied) — and confirming would have replaced the real backup on any relay the new one reached. Absence is now only believed when a relay actually answered; decrypt failures surface as errors; and the create path takes one last look and adopts a backup that surfaces rather than clobbering it. Co-Authored-By: Claude Fable 5 <[email protected]>
* One question fewer on the way in, and an 'or' that means itAdam Soltys2026-08-041-0/+3
| | | | | | | | | | | | | | | | | | | | | The sign-in screen stacked 'Connect a signer app' directly above a bunker/nsec field, which reads as two steps rather than two ways in. A divider says which it is. The wizard's second step asked whether you have a nostr account, then sent you to a screen that asks the same thing. Step one now offers both paths directly — Get started, or sign in with Nostr — and the middle step is gone, along with its strings. The welcome blurb goes too: the screen is one decision, and prose above it is something to read before you can make it. The footer sinks to the bottom of the window rather than trailing the content up the page. #app is a full-height column now; the chat card sizes itself against the viewport, so the layout test covers it at three sizes. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Drop the 'signer reconnected' toastAdam Soltys2026-08-031-1/+2
| | | | | | | | | | A dark pill floating at the bottom of the screen reads as a stray button under the footer, and it was saying something the page already showed: the badge flips to connected and the reconnect controls disappear. Failures still surface — those go in the card, where the problem is. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Give the nostr settings page a way to reconnectAdam Soltys2026-08-031-17/+37
| | | | | | | | | | | | | | | | | | | | | The app could tell you your signer wasn't connected and then send you somewhere with nothing to click: once an account was linked, the nostr card showed only an npub. It now shows connected/not connected, says what's missing, and offers the same signer options as signing in — refusing a different account rather than quietly swapping identities, since this is a reconnect and not a login. Pasted keys go back to not being persisted at all. Storing one would have survived a reload, but the UI promises a pasted key is used once and never kept, and that promise is worth more than saving a paste; those logins reconnect from this card like any other. The chat banner now tracks the live signer alone. The flag that suppressed it after a reconnect would also have hidden a later genuine disconnect. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Don't keep holding a remote signer that stopped answeringAdam Soltys2026-08-031-4/+24
| | | | | | | | | | | A NIP-46 connection can die quietly, leaving the app with a signer object that will never reply — and since it's still the cached live signer, every send after that fails the same way until the page is reloaded. Wrap a remote signer so the first failed call drops it, letting the next attempt resume a fresh connection instead. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Speak as the account you logged in with, or not at allAdam Soltys2026-08-031-9/+54
| | | | | | | | | | | | | | | | | | | | | | | | | | Logging in with a remote signer and reloading left the app showing your avatar and profile while signing everything with the wallet's own key — so chat messages arrived under a different npub with a stranger's punk on them. identity() had a fallback: when the login signer wasn't available it quietly used the wallet key instead. That fallback is gone. If you logged in as a nostr account, the app speaks as that account or it refuses and says so. Refusing is only reasonable if the signer usually comes back, so a login now persists what it takes to be that identity again. A remote signer stores its NIP-46 client key and pointer — the signer authorised that client key, not one page load, so rebuilding from it resumes the session without prompting. A pasted key stores the key, which is already the seed of everything here since the wallet's mnemonic derives from it. Resume is single-flight with a pause after failure, because identity() is asked on every send and a signer that isn't answering must not mean a relay connection per keystroke. Existing remote-signer logins predate the stored session, so they need one more sign-in to become resumable. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Onboarding: the nostr mark heads the nostr stepsAdam Soltys2026-08-031-0/+2
| | | | | | | | | 72px, centered above the question (and the sign-in step), with a purple glow and a slow float — the one place in the flow that isn't the coinos brand, so it should look like nostr. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Tab carousel, login avatar fix, signer callback hintAdam Soltys2026-08-021-0/+4
| | | | | | | | | | | | | | | | | | | | | | Tab changes no longer flash the whole page: only the content pane below the tab strip slides sideways with a soft cross-fade — swipe direction when a gesture caused it, index order for taps — while the balance card and strip stay planted. Full-page entrances remain for real page changes (settings, chat, profile, takeovers). After a nostr sign-in the header briefly wore the wallet key's face: nostrLoginIdentity now reports a just-connected live signer immediately instead of waiting for the persisted link that only lands after the wallet opens. The nostrconnect URI carries a callbackUrl hint (NIP-55 style) so signer apps that honor it can bounce back to the PWA after approval — apps that don't will still find the session signed in when the user returns, since the handshake completes over the relay in the background. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Nostr login: connect a signer app by deep link or QR (nostrconnect://)Adam Soltys2026-08-021-3/+51
| | | | | | | | | | | | | | The client-initiated NIP-46 flow: coinos mints a nostrconnect:// URI — rendered as a tappable deep link (launches Amber & friends directly, no bunker URL to copy) and a QR for cross-device scanning — then waits on the relay; when the signer app answers with the secret, login continues by itself. One live attempt at a time, cancellable, three-minute timeout, and kind 24133 joins the relay whitelist so our own relay carries the handshake. The bunker:// paste path stays for signers that prefer it. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Motion and polish: navigation choreography, counting balance, gold ribbonAdam Soltys2026-08-021-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | The renderer rebuilds the DOM on every render, so naive CSS mount animations would replay on every background repaint. Two primitives fix that: uiChanged() gates one-shot animations to the render where the thing actually happened, and animWindow() lets a rebuild that lands mid-animation RESUME it via a negative animation-delay instead of killing or restarting it. On top of those: every real navigation rises in (screen/tab/subpage/ chat/profile — one keyed check in render()); the balance counts to its new value and breathes green when money arrives; the avatar menu pops from its corner; the move panel unfolds; the success check springs and rings once when its page lands. Micro-transitions on buttons, tabs, segs, inputs, list rows, avatars, and the settings chevrons. One chromatic signature on the monochrome brand: a slow-drifting bitcoin-gold ribbon across the top of the balance card, echoed by a faint warm wash pooling at the top of the page, plus a film-grain overlay for texture. All of it honors prefers-reduced-motion. The nostr sign-in mark also gets a proper inset ostrich and a size bump. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Sign in with Nostr: mark fills its circle, icon truly centeredAdam Soltys2026-08-021-3/+7
| | | | | | | | | | | | Two compounding bugs: the SVG viewBox carried 7% of artificial padding so the mark floated small inside its box, and .btn-block forces display:block, silently defeating the .row flex the button relied on — the icon was never actually flex-aligned with the text. The mark's viewBox is tight now and the flex centering lives on an inner wrapper. Icon center measures 0.0px off the button center. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Settings: surface wallet settings, add Nostr section, tuck rarities into ↵Adam Soltys2026-08-021-1/+1
| | | | | | | | | | | | | | | | | | Advanced The main Settings tab now inlines the active wallet's own cards (recovery phrase, name, public key) instead of linking to a sub-page, followed by payment address, Ark status, network, and data source. All nostr cards — address, account link, wallet connect, device sync — sit under one Nostr heading via a new nostrSettingsCards seam. Offline transfer, address rescan, and auto-logout move to an Advanced sub-page. The Ark provider picker is gone: mainnet always rides coinos (a stale stored choice no longer wins); dev networks keep their per-network defaults. The per-account screen (Accounts → ⚙) reuses the same card builders unchanged. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Nostr mark: brand gradient and room to breatheAdam Soltys2026-08-021-2/+2
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* Use the real Nostr mark on the sign-in buttonAdam Soltys2026-08-021-2/+3
| | | | | | | The hand-rolled monochrome ostrich read as a smudge at 18px. This is the brand asset from coinos-ui, purple kept, at 22px. Co-Authored-By: Claude Fable 5 <[email protected]>
* Drop npub-locked gifts; collapse Nostr login behind a buttonAdam Soltys2026-08-011-1/+13
| | | | | | | | | | | | | | Gifts are plain bearer links again: the recipient-npub field, the one-time claim code, the DM delivery, the /lg/ claim route and the NIP-07 in-place decrypt are all gone, along with the crypto that only they used. Unclaimed gifts remain reclaimable by the sender, so nothing can be stranded by the removal. The unlock screen shows a single 'Sign in with Nostr' button that reveals the login options when tapped, instead of the full card competing with create/import. Co-Authored-By: Claude Fable 5 <[email protected]>
* Get an address immediately, without waiting on a signer promptAdam Soltys2026-08-011-0/+4
| | | | | | | | | | | | | | | The passive setup path asked the browser extension to sign the claim, so it sat behind a permission popup — 20s per attempt, retried, hence the minute-long spinner ending in the manual form. The wallet's own key now makes that claim: no signer, no prompt, no wait. Tying the address to the user's real npub happens at login instead, where the signer is already live, and nominates the wallet key to manage the record after. Failures are visible now rather than hidden: the pane shows why setup failed, the watchdog is 12s instead of 60s, internal timeouts are tighter, and the registrar logs every refusal with its reason. Co-Authored-By: Claude Fable 5 <[email protected]>
* Never spin on a claim that can only failAdam Soltys2026-08-011-0/+15
| | | | | | | | | | | | After a reload the extension signer is gone, so claiming the user's real npub name was signed by the wallet key — which the anti-squatting rule rightly refuses, and the retry loop then span forever. An installed extension is now silently re-attached when possible (keeping the address tied to the real identity across reloads), refusals are treated as permanent rather than retried, and anything unresumable falls back to the wallet's own npub so an address always appears. Co-Authored-By: Claude Fable 5 <[email protected]>