aboutsummaryrefslogtreecommitdiffstats
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
* Park the send field above the phone keyboard; wizard whenever walletlessAdam Soltys2026-08-042-6/+27
| | | | | | | | | | | | The recipient candidate list was hiding under the on-screen keyboard: scroll the field to the top on focus and when fresh candidates land, give the list scroll room to get there, and let the keyboard resize the viewport (interactive-widget) so the scroll has somewhere to go. The wizard now shows whenever the device has no wallet — the one-time onboarded flag is gone; deleting every wallet brings the wizard back. Co-Authored-By: Claude Fable 5 <[email protected]>
* Drop the BOLT 12 explainer blurbAdam Soltys2026-08-042-2/+0
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* A refresh mid-wizard resumes the step it was onAdam Soltys2026-08-042-5/+19
| | | | | | | | | | The wizard mints the wallet on its first step, so 'an account exists' stopped meaning 'wizard done' — reloading dropped you into the half-set-up wallet. Persist the current step until the wizard finishes. Also trims the balance coach-mark copy. Co-Authored-By: Claude Fable 5 <[email protected]>
* Keep the punks carousel where you left it, and on the pageAdam Soltys2026-08-042-2/+17
| | | | | | | | | A background repaint rebuilt the strip at scrollLeft 0 mid-swipe; render() now carries scroll positions across the swap the same way it carries focus. The onboarding column also sized itself to the strip's intrinsic width (auto margins defeat flex stretch), overflowing phones below ~450px. Co-Authored-By: Claude Fable 5 <[email protected]>
* Sign avatar uploads — nostr.build stopped taking them bareAdam Soltys2026-08-041-1/+11
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* Paste lives in the field; the footer fits; claiming a name comes firstAdam Soltys2026-08-043-24/+57
| | | | | | | | | | | | | | | | | | | | | | | | | | Paste, on the import and load-seed screens, was a bordered box floating beside a small label — heavier than the label, detached from the field it fills. It now sits inside the textarea, and only while that textarea is empty: the only time it's wanted, and the only time it can't cover what you typed. It's toggled on the node rather than through render(), since re-rendering a textarea per keystroke rebuilds it under the caret. Beside a one-line input (Send) the old placement was right and is untouched. The footer measured 100dvh, which on a phone can resolve to the tall viewport at first paint and leave the language picker just below the fold. svh is the one that fits with the address bar showing. Short screens also tighten their padding, which buys back the ~50px the create/import screen was overflowing at 375x553. And the wizard no longer asks every new user whether they have an old coinos.io account before letting them have a name. Claiming the name is the step; the people who do have an old account know it, and say so with a link. That screen is a statement now, not a question they just answered. The npub-shaped default address is no longer offered as a button — a plain skip goes past without showing anyone that. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Put the ostrich on the sign-in buttonAdam Soltys2026-08-041-1/+5
| | | | | | | | | Same construction as the sign-in button on the unlock screen — .btn-block forces display:block, so the flex centering lives on an inner wrapper rather than the button. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* One question fewer on the way in, and an 'or' that means itAdam Soltys2026-08-044-28/+27
| | | | | | | | | | | | | | | | | | | | | 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
* Offer a password the first time you lockAdam Soltys2026-08-042-9/+41
| | | | | | | | | | | | | | | | | | | | | Locking with no password locks nothing — the wallets sit on the device under an empty one and reopen with a tap. So the first deliberate lock now asks whether to set one, explaining plainly what happens if you don't. 'Not now' is a real answer, recorded, and never asked again: a wallet that nags is a wallet people stop reading. The offer rides the deliberate lock only. lock() is also where the idle timer, the last-wallet-removed path and boot-after-expiry land, and a password form appearing because you went to lunch is a different app than the one you left. Two subtleties worth naming: the offer is set after lock()'s ui reset block, which clears ui.pw and had been swallowing it; and taking the offer re-enters lock(), which now finds a password and lands on the unlock screen instead of the wallet list. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Call it 'Lock wallet', because that is what it doesAdam Soltys2026-08-0321-40/+21
| | | | | | | | | | | | | | | | 'Logout' promises a return to a fresh app with nothing of yours left on it. This closes the open wallet and keeps everything saved, which is why it read as interchangeable with 'Clear all' — the one that really does send you back to the beginning. Renamed via a new key rather than rewording the old one: nineteen locale tables carry their own word for 'Logout' and t() prefers the locale's value, so reusing the key would have kept the wrong meaning in every language but English. The dead key is dropped from those files; they fall back to English until translated. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Logout shouldn't ask for a password nobody setAdam Soltys2026-08-031-4/+16
| | | | | | | | | | | | | | | | | | | | | | | Wallets save themselves to this device as soon as you open one, encrypted under an empty password unless you choose one. Logging out still sent you to 'Unlock saved wallets — enter your password', which asks for something that doesn't exist and makes logout look like it did something drastic. It didn't: logout only drops the keys from memory, and 'Clear all' is the one that deletes. With no password, logout now returns to the wallet list — honest, since nothing is protecting those wallets and a blank prompt protects nothing. With a password set it prompts exactly as before. Fixed underneath: mergeVaultList derived the comparison xpub with accountXpubFor's mainnet default, so on any other network it never matched the stored one and every unlock re-added the same wallet as a fresh entry — visible as the same wallet listed twice, once watch-only. The seed-mismatch checks in 'load recovery phrase' had the same default and would have rejected the correct seed on testnet. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* The unread dot holds stillAdam Soltys2026-08-031-2/+4
| | | | | | | | | | | | | | | It looked like the dot was flickering on and off after a refresh. It wasn't: measured over the first twelve seconds it appeared once and never went away, while its entrance animation started seven separate times. render() rebuilds the whole DOM, so the pop replayed on every repaint. A dot that means 'someone is waiting' has nothing to animate about, so the animation is gone rather than gated. tools/unread-dot-test.js counts animation starts against real appear/disappear transitions, so a future entrance effect here fails instead of shipping as a flicker. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* The chat window fits the screen, and the header stops asking questionsAdam Soltys2026-08-033-22/+48
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The message log had a fixed 62vh height, so the card was head + panels + log + composer tall and the input fell off the bottom of the screen. The card is now sized to the viewport and the log is the only part that flexes: opening the invite or member panel shrinks the scroll area instead of pushing the composer out of reach. Three header changes, all the same idea — don't show a control for a choice nobody is making: '+' becomes 'Add person', because a plus sign doesn't say what it adds. A lone channel isn't named at all. '#general' next to a composer that says 'Message #general' points at a choice of one; several channels become a select, which is what a choice looks like. The new-channel field hides behind a 'New channel' button. Sitting open right after you create a community, it reads as something you're supposed to fill in. tools/chat-layout-test.js holds the line at three viewport sizes with both panels open, and covers the header behaviour through actually adding a second channel. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Drop the 'signer reconnected' toastAdam Soltys2026-08-033-5/+4
| | | | | | | | | | 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-033-19/+44
| | | | | | | | | | | | | | | | | | | | | 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
* Offer the signer back where you'd notice it's missingAdam Soltys2026-08-033-2/+30
| | | | | | | | | | | | | A remote signer dropped by a reload now says so above the composer, with a button that reconnects in place — rather than letting someone write a message and only then be told it can't be sent as them. Failing that it opens the nostr settings, where logging in again is the fallback. Every remote-signer login made before sessions were persisted lands in exactly this state once. 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-034-18/+92
| | | | | | | | | | | | | | | | | | | | | | | | | | 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
* Only people you know get to buzz your phoneAdam Soltys2026-08-033-1/+166
| | | | | | | | | | | | | | | | | | | | | | | | | | | | A NIP-17 wrap is signed by a throwaway key with the sender sealed under two layers of nip44, so no server can filter DMs by who sent them. The notifier now ships the wrap inside the push payload and the service worker opens it locally: a message from someone you follow arrives with their name on it, a stranger's is dropped, and your own sent-copy — every DM is wrapped to the sender too — no longer notifies you about what you just typed. That needs the nostr identity key in the worker's IndexedDB, which dm-inbox.js states plainly: for a seed-derived identity it is a second copy of a key localStorage's seed already yields, and it exists only to keep strangers out. A remote signer never hands its key over, so those wallets store no secret and keep getting unfiltered notifications rather than silence. Silence is always the riskier failure here, so the rule leans the other way whenever it isn't sure: a wrap we can't open notifies, an account with no contact list published notifies (otherwise a new user's welcome message would vanish), and anyone you already have a thread with counts as known whether or not you follow them. The server-side DM cooldown drops to a second — with the device discarding pushes, a throttle there would let a discarded one swallow the message behind it. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Messages in the header, and a room you can tell is aliveAdam Soltys2026-08-035-8/+300
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The header carries a message button again, left of the wallet selector, with a red dot when a conversation is waiting. Unread is a per-thread watermark on the newest message from someone else, kept in the rumor's own created_at so it means the same thing on every device it syncs to. Rooms subscribe at startup now, or the dot could not speak for a community nobody was listening to. Communities no longer buzz a phone by default: push registration only carries the stream keys of communities you have opted in, per community, from a bell in the room header. DMs and payments are unchanged. Presence and typing ride the channel stream as 21059 wraps, so who is around stays inside the community's encryption rather than becoming a public status. Measured against relay.coinos.io and nos.lol, neither actually drops NIP-01 ephemeral kinds, so beats carry a NIP-40 expiration and relay.coinos.io sweeps kind 21059 hourly — and presence is dated by the beat, never by arrival, or a replayed beat would light up the whole member list. The member list follows Telegram: online, then last seen in minutes and hours, then a vague 'recently' rather than advertising how long someone has been gone. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Gift records belong to one wallet, not to the browserAdam Soltys2026-08-032-0/+22
| | | | | | | | | | | | | | | | giftRecords() caches the parsed blob on the wallet object, but the app runs a single Wallet for its lifetime and load() never dropped that cache. Switching wallets carried the old wallet's gift links into the new one, where they all rendered as claimed — none of their outpoints are live in a wallet that never issued them — and the first save wrote them back under the new wallet's key. Clear the cache on load alongside the other two gift caches, and forget records whose funding tx isn't in this wallet's own history, to clean up what earlier builds already misfiled. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Profile: no editable lightning addressAdam Soltys2026-08-032-4/+8
| | | | | | | | | | | Editing it here reads like renaming your coinos account, and for someone using an address from elsewhere it invites breaking their own zaps. The field is gone; an existing address is preserved untouched, and one is filled in from the coinos payment address only when the profile has none, so other clients can still zap them. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* A freshly imported wallet shows history, not a celebrationAdam Soltys2026-08-031-1/+15
| | | | | | | | | | | | | | | | Importing a seed on a second device replayed "Payment received!" for a payment that had arrived on the first one. The two-hour recency guard couldn't catch it: a movement is stamped with the moment THIS device discovered it, so on a fresh device the entire history looks like it just landed — a week-old payment would have celebrated too. A wallet whose ark state didn't exist on this device now treats its first catch-up as history and acknowledges it, so only what turns up afterwards is news. Reopening an existing wallet is unaffected, and a payment that lands while you're watching still celebrates. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Auto-withdraw runs with the app closed; fix the dead Back on the legacy stepAdam Soltys2026-08-037-5/+155
| | | | | | | | | | | | | | | | | | | | | The service worker can't watch a relay, so it doesn't try: the app resolves the destination to something concrete (ark address, LNURL endpoint, or on-chain script) and mirrors it beside the keys the background NWC payer already uses. On a push wake-up — which is exactly when money has just landed — the worker re-checks the same gate and sends, writing the outcome back into the mirror for the app to absorb on next open. A partial on-chain forward still waits for the app, since carving an exact vtxo needs more round trips than a worker is promised. writeBgMirror now carries forward the NWC connections and offer when another caller refreshes the mirror, instead of blanking them. And onboarding's legacy step loses its Back: the wallet exists by then, so returning to the nostr question just bounced straight forward again and the link looked broken. Its copy no longer promises a rename either. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Move money: shorter amount placeholder, row wraps on narrow phonesAdam Soltys2026-08-033-2/+6
| | | | | | | | | | The placeholder repeated a minimum the line below already states, and it was clipped anyway — the amount field had 14px of usable width at 360px because "Move to Spending" crushed it. The row wraps to two full-width lines when it's tight and stays on one line when there's room. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Auto-withdraw: forward Spending onward past a thresholdAdam Soltys2026-08-032-1/+199
| | | | | | | | | | | | | | | | | | | | Settings → Payments takes a destination, a threshold, and an optional amount to keep behind. When Spending crosses the threshold the wallet sends the rest on by itself, resolving the destination the same way the send form does — an ark address goes native and free, a payment name prefers its ark instruction before falling back to lightning, an npub tries its ark advert then its lightning address, and an on-chain address becomes a cooperative offboard (carving out an exact-amount vtxo first when it isn't sending everything, since offboards move whole vtxos). Money moving without a human watching deserves guardrails: one transfer at a time, never while another ark action is in flight, a fifteen-minute backoff after a failure with the reason kept for the card, destinations validated before it can be switched on, and dust amounts skipped. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Drop the gold ribbon from the balance cardAdam Soltys2026-08-031-11/+1
| | | | | Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Save wallets by default; reveal the recovery phrase in two stepsAdam Soltys2026-08-032-9/+38
| | | | | | | | | | | | | | | | A seed that only lives in this tab is one refresh from gone, and that surprise costs people money — so a wallet now saves itself to the device when it's opened or committed, no button required. No password is set (Settings still offers one), and a locked existing vault is left to the manual Save button since writing it needs their password. The recovery card gains a middle step: the first click opens the grid with the words masked — the warning and the shape of what's coming are on screen before anything readable is — and a second click shows them. Copy only appears once they're visible. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Balance card: drop the descriptor lineAdam Soltys2026-08-033-7/+1
| | | | | | | | | | "Instant · free to send" and "On-chain · mining fees apply" were one line that swapped with the account; the card is just the balance now. Strings, styles and stale translations removed with it, and the face's reserved height comes down to match. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Receive: the BOLT 12 section takes over the cardAdam Soltys2026-08-031-2/+4
| | | | | | | | | Opening it hides the payment address and its QR — two payment codes on one screen is a good way to have someone scan the wrong one. "Hide" becomes "Back", which is what it now does. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Balance card: a real button to change account, not two dotsAdam Soltys2026-08-033-7/+20
| | | | | | | | | | | The dots were an indicator pressed into service as a control — fine under a thumb that can just swipe, too small for a mouse. They're replaced by a pill in the header row naming the account it takes you to (⇄ Savings / ⇄ Spending), which is both a proper click target and a clearer answer to "what's the other one?". Swiping the card still works. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Onboarding: no waiting state before the migration questionAdam Soltys2026-08-032-12/+25
| | | | | | | | | | The step needed this wallet's payment address for the migrate link, and the background claim often hasn't landed yet when the step appears — so it flashed a spinner first. The page renders complete now; if someone taps before the address exists, the wait happens on the button instead. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Onboarding: a step for migrating a legacy coinos.io accountAdam Soltys2026-08-033-1/+61
| | | | | | | | | | | | | | | | | | | Between "do you have a nostr account" and claiming a username, new users are asked whether they already have an account on the original coinos. Yes hands off to coinos.io/migrate with their fresh v3 payment address and a return URL — they're already signed in there, so no password or key ever travels. That page sweeps the balance over Lightning and renames the old account, releasing the username. Coming back, coinos.io returns ?migrated=<username>; v3 claims that name on the registrar (retrying for a minute, since the registrar's "is this a legacy user?" guard caches), and their lightning address now resolves to the v3 wallet. The legacy page ships in coinos-ui and is NOT deployed yet. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Zap: resolve inside a skeleton of the card that's comingAdam Soltys2026-08-034-16/+35
| | | | | | | | | | | | | | The inline-progress attempt still read as a flash — the send form was visible behind it with the raw npub, its verification block and the gift button, then all of that was replaced. Now both resolution phases (ark advert, then lightning) render the SHAPE of the destination card: same title, same recipient chip, a spinner where the amount form will be, and a working Back. The card fills in instead of the screen changing twice, and the two flows share one title so it never re-titles at the handoff. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* BOLT 12 offers with a custom memo on the Spending receive pageAdam Soltys2026-08-032-3/+83
| | | | | | | | | | | | | | | | | | Mining pools (Ocean) and payroll pay to a reusable offer rather than a lightning address, and the memo is how the payer labels it. A disclosure under the payment address mints one: the registrar's new NIP-98'd /offer endpoint asks the ASP's CLN for an `amount: any` offer carrying the memo, keyed to the caller's name (issuer = their address, so two users' identical memos can't collapse onto one CLN offer). Up to ten each, identical memos reuse the existing offer, and GET lists them. Settled offer payments already had a home: the registrar's settle loop now resolves a custom offer id to its owner and hands it to the same forwarder the default offer uses — an instant, free arkoor send into the user's Spending balance. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Pay screens: bigger recipient chip, no loading-screen flashesAdam Soltys2026-08-035-9/+29
| | | | | | | | | | | | Resolving a recipient (ark advert → BIP-353 → lightning) no longer takes over the screen twice on the way: both steps keep the send form in place and report progress as one inline line, so picking someone goes straight to something actionable. The recipient chip on the zap card gets a large variant — a 30px avatar and full-size name, since it's who the money is going to. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Profile page: hide machine-noise fields, real Back buttonAdam Soltys2026-08-031-5/+14
| | | | | | | | | | | | An npub-shaped lightning address (npub1…@npub.cash) duplicated the npub box below it and overflowed the card — it's hidden now, as is a lud16 identical to the nip05. A one-character "about" (fiatjaf's is literally "~") is noise rather than a bio, so it's skipped. Both remaining identity lines wrap. The inline ‹ chevron becomes a full-width Back button under the actions, matching every other page. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Unilateral exit button: drop the coin countAdam Soltys2026-08-032-2/+2
| | | | | | | | | "Move to Savings without the server" — the vtxo count was internal detail in a button most users will never press. It still gates whether the button appears at all. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Move money: Max on both directions, inline in the amount fieldAdam Soltys2026-08-032-8/+44
| | | | | | | | | | | | Into Spending gains a Max (fee-aware — it asks the tx builder what a sweep would actually deliver, so the mining fee can't make the board fail), and both directions now carry Max as a small affordance inside the input's right edge instead of a button eating a third of the row. It only appears when there's a balance to max. Balance face height trimmed to match its shorter content. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Balance card: one account at a time, swipe or dots to flipAdam Soltys2026-08-033-27/+59
| | | | | | | | | | | | | | | | | | Replaces the segmented toggle and the always-visible second balance. The card now shows the account you're in at full size with a plain-word descriptor ("Instant · free to send" / "On-chain · mining fees apply"), two dots to flip, and a sideways slide when it changes. A swipe that starts on the card switches account instead of tab; swipes anywhere else still move between Receive/Send/History. Fixes a real mobile bug found while testing: a touch sequence still fires a compatibility click at the release point, so every swipe was ALSO pressing whatever sat under the finger (which is what kept bouncing the test wallet back to the unlock screen). One click is now swallowed after a recognized swipe. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Fix the service-worker ReferenceError; stop dialing dead relaysAdam Soltys2026-08-031-5/+47
| | | | | | | | | | | | | | | | | | | | | | | sw.js threw "NWC_WAKE_FALLBACK is not defined" on every load: the template substitution used String.replace with a string pattern, which replaces only the FIRST match — and that was the mention inside the comment above, leaving the real placeholder literal in the output (parsed as a block plus a bare identifier). replaceAll fixes it, the comment no longer spells the token, and {{VERSION}} got the same treatment. Both build profiles now emit a service worker that parses and activates cleanly. The relay noise: a browser logs failed WebSocket connections itself and JS cannot suppress that line, so the fix is to stop making the doomed connection. nostr.js now probes each new relay once, remembers unreachable ones for ten minutes, and filters them out of every subscribe/query/publish (never down to an empty set). Publish rejections that look like connection failures mark the relay sick rather than logging a scary "refused" line. relay.damus.io — down all week — also leaves the profile relay list. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Onboarding: "Use <addr> for now", address in muted monoAdam Soltys2026-08-033-2/+7
| | | | | Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Onboarding: the nostr mark heads the nostr stepsAdam Soltys2026-08-033-0/+10
| | | | | | | | | 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
* Settings → Advanced: replay the tourAdam Soltys2026-08-032-0/+13
| | | | | Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Zap screens show the recipient, cap amounts to Spending, faster searchAdam Soltys2026-08-034-11/+34
| | | | | | | | | | | | | | | | | | | The zap flows (ark-direct and Lightning) show the recipient's avatar chip instead of a bare name, and their LNURL ceiling is now capped by our own Spending balance — when Spending can't cover their minimum the amount field is replaced by a plain "not enough in Spending" notice rather than an impossible "min 1 · max 0" range. The confirm path enforces the same cap. Recipient search feels instant now: the first short query fires after 120ms (the wait users actually notice) and longer mid-word queries wait 500ms to skip characters still being typed; cached prefixes stay instant. Onboarding success screen: check below the title and larger. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Self-host the punks; skipping avatar still publishes your punkAdam Soltys2026-08-033-6/+12
| | | | | | | | | | | | | coinos.io serves 502 for a third of the punk set (both the numbered and hash endpoints), which is why a default avatar could come up blank. The 64 webps now ship with the app (53 salvaged, 11 gaps filled with donors) and every reference is same-origin. Skipping the avatar step publishes the deterministic punk as the kind-0 picture — your face is the same on every nostr client. Success screen: checkmark now sits below the title, bigger. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Onboarding: swap a server-broken punk, self-heal the stripAdam Soltys2026-08-031-1/+2
| | | | | | | | | punks/55 (and a few others) 502 on coinos.io right now; 36 verified good takes its place, and any picker image that fails to load removes itself instead of showing a broken glyph. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* First-run onboarding: a five-step wizard and a coach-mark tourAdam Soltys2026-08-025-2/+253
| | | | | | | | | | | | | | | | | | | Fresh devices land in a full-screen flow instead of the unlock screen: welcome → "do you have a nostr account?" (sign in brings wallet, name, and profile along; No mints a wallet on the spot) → claim a username (the background npub-default only surfaces a Keep button; a custom claim — or a restored login's recovered name — advances by itself) → pick a punk or upload a photo (nostr.build), published as the kind-0 name+picture → confetti. "Enter coinos" drops into a three-stop tour: the avatar menu, the Spending/Savings toggle, and the tabs with the swipe tip — each highlighted by a measured gold ring over a dimmed page. One time only (localStorage flag), with an "I already have a wallet" escape to the classic screen. New seams: names.namesClaimForm, messages.publishProfile. 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-024-15/+34
| | | | | | | | | | | | | | | | | | | | | | 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-023-4/+86
| | | | | | | | | | | | | | 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