feat(receive): redesign receive flow with improved sheets and toasts#480
feat(receive): redesign receive flow with improved sheets and toasts#480sahilc0 wants to merge 41 commits intowt-navbar-root-only-20260325from
Conversation
- Skip amount screen, show QR immediately on receive - Custom SVG QR with circular dots, rounded finder patterns, Arkade logo center - Logo uses brand color (--logo-color), adapts to light/dark theme - QR dots animate in with staggered ripple on value change - Buttons inline with content (not fixed footer) — scrollable layout - Amount entry and copy address via bottom sheet modals - SheetModal accounts for pill navbar clearance - Content noFade prop to disable scroll mask on receive screen - Fetch addresses on mount (merged from Amount screen)
- Sheet modal: iOS-inspired with shadow, handle bar, rounded close button - Sheet z-index above navbar (z-index: 200), backdrop dismiss enabled - Haptics on sheet close - Amount keyboard bypasses sheet on save — goes straight to QR - Button label: "Add amount" / "Edit amount" instead of sats number - Clear amount option in sheet to reset to no-amount QR - "500 sats min for Lightning" hint tightly coupled under QR - "Requesting X sats" text positioned closer to QR
Receive flow layout: - Center QR vertically in available space, pin actions to bottom - Add breathing room with 1.5rem bottom padding on actions - Reorder copy addresses: Unified > Lightning > Arkade > Bitcoin - Rename BIP21 to "Unified", BTC address to "Bitcoin address" Sheet modal (Vaul-inspired): - Move visual styling to Ionic CSS custom properties (--background, --box-shadow, --overflow, --border-width) to escape shadow DOM overflow clipping that was hiding shadows on inner elements - Remove close X button, use handle bar + backdrop dismiss - Wider pill handle (40px), softer shadow, 16px border-radius - Dark mode: stronger shadow + light border for elevation contrast Toast (Sonner-inspired): - New custom Toast component replacing useIonToast across 6 files - Dark bg on light mode, slightly lighter gray on dark mode - Top-positioned, 250ms ease-out enter/exit, full reduced-motion support - 44px accessible copy buttons with aria-labels and touch-action Accessibility: - Copy icon buttons: div -> button with aria-label, 44px min tap target - Focus highlight: :focus -> :focus-visible (no purple flash on tap) Dev auto-init: - Set authState to 'authenticated' directly instead of persisting encrypted key to localStorage (keeps dev NSEC in-memory only)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Deploying wallet-bitcoin with
|
| Latest commit: |
f5ef1b2
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ff98fc5b.wallet-bitcoin.pages.dev |
| Branch Preview URL: | https://wt-receive-on-navbar-2026032.wallet-bitcoin.pages.dev |
Deploying wallet-mutinynet with
|
| Latest commit: |
f5ef1b2
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f8a9b660.arkade-wallet.pages.dev |
| Branch Preview URL: | https://wt-receive-on-navbar-2026032.arkade-wallet.pages.dev |
* Strip icon field from asset metadata before persisting to localStorage Asset metadata icons are base64-encoded image strings that can be tens or hundreds of KB each. Persisting them fills up the ~5MB localStorage quota unnecessarily since they are re-fetched on demand. The hasIcon flag is preserved so the UI still knows an icon exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Evict expired entries from asset metadata cache on save Entries older than 24h (ASSET_METADATA_TTL_MS) are now dropped when persisting the cache to localStorage, preventing unbounded growth. The TTL constant is shared between storage.ts and wallet.tsx. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert icon stripping from asset metadata cache Keep icons persisted in localStorage; only rely on TTL-based eviction to control cache size. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix code split issue in service worker * Upgrade ts-sdk 0.4.12 - boltz-swap 0.3.12
* style(ui): soften header divider and redraw back/close icons - Header divider: replace hardcoded #444 border with var(--dark10) for a subtler, theme-aware separator across all sub-pages - Back icon: redraw as a Heroicons-style left arrow (shaft + head) at a slightly larger size within the 32x32 viewbox - Close icon: redraw as a clean stroke-based X mark, replacing the filled path with fillOpacity 0.5 to match the back icon's currentColor stroke treatment * style(close-icon): fix prettier formatting --------- Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Replace the SVG loading bar across all loading screens with the bounce/morph pixel logo animation (Arcade → Invader → Heart → loop). - Extract shared SVG rendering into PixelLogoSvg component (DRY) - Create useBounceMorph hook with checkpoint-style graceful stop - Create LoadingLogo component with three exit modes: - fly-to-target: boot animation flies to LogoIcon position - fly-up: operation screens fly up before navigating - none: gate patterns unmount naturally - Boot animation persists Loading → Wallet, white overlay during loop - Wallet LogoIcon anchor moved outside stagger tree for correct measurement - Migrate ~20 usage sites (gate patterns + operation patterns with exit) - Delete old Loading.tsx and LoadingBar.tsx - Cherry-pick VITE_DEV_NSEC auto-init for dev testing
…omplete - Hide wallet header LogoIcon while boot animation is active (visibility: hidden) so the fly-to-target doesn't show two logos - Hold wallet stagger animation until boot animation completes, preventing the stagger from playing invisibly behind the white overlay - Switch fly-to-target easing from ease-out-quint to ease-in-out-quint per Emil's guideline: on-screen movement uses ease-in-out - Add EASE_IN_OUT_QUINT constant to animations.ts - Tighten stagger duration from 400ms to 300ms
- Remove useEffect-based stagger start; drive motion state directly from hold prop to avoid one-paint-late blank frame - Sync bootAnimActive to external store synchronously (before React re-render) so Wallet reads correct value on same render that unmounts LoadingLogo - Retry getLogoAnchor() up to 10 rAFs before falling back to fly-up if header anchor isn't mounted yet - Replace dead setShowBackground state with plain const - Use useRef to capture isInitialLoad at mount (satisfies react/hook-use-state lint rule)
…ot anim API - Extract duplicated fly-up animation into a local helper function - Name the anchor retry count (ANCHOR_RETRY_FRAMES = 10) - Rename updateBootAnimActive → updateBootAnim for clarity - Revert lock file changes (dev environment artifacts)
- Remove rollup-darwin-x64 optionalDep (pnpm handles native binaries) - Reset bootAnimDone/bootExitMode before starting new loading cycle to prevent stale state if app re-enters Pages.Loading - Use measured anchor size instead of hardcoded 35px for fly-to-target scale, so it adapts to CSS/breakpoint changes - Reset bounce loop state (stopRequested, activeShape) when reducedMotion changes to prevent frozen intermediate shapes - Don't block cancel button on Init/Connect — only show opaque background when connectDone is true
Unlock.tsx still imported the deleted Loading component after rebase conflict resolution. Since the boot animation in App.tsx covers the unlock loading state, render nothing while unlocking instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…260325 feat(ui): replace loading bar with bounce/morph logo animation
* Create one VtxoManager and reuse it * Use VtxoManager from SW * Use vtxoManager functions were possible * Preserve Sentry error capture for settlement errors * restored settleVtxos and renewCoins to address risky behavioral changes * Import Sentry in Vtxos.tsx * Reduce e2e tests timeout to 30 min * Error handling, Missing dependency, Stale response cancellation * Do not throw
* prevent duplicate funding of vhtlc * Upgrade ts-sdk 0.4.12 - boltz-swap 0.3.12 * Use ContractManager to fetch vtxos --------- Co-authored-by: Pietro Grandi <dev@pietro.uno>
…#487) * fix: unregister stuck service worker after exhausting retries When the service worker is permanently unresponsive (browser stopped it and MessageBus.start() fails silently on wake), all 5 retry attempts time out. The PR #421 one-time reload then reuses the same broken SW registration, leaving the app stuck on Loading forever. Unregistering the SW after retries are exhausted ensures the reload gets a fresh registration with a clean MessageBus. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: increase MessageBus init timeout to 30s for cold starts The INITIALIZE_MESSAGE_BUS handler in the SDK calls buildServices() which creates Wallet.create() — a network call to the ARK server. The SDK intentionally skips wrapping this with a timeout because it "performs network calls that legitimately exceed message timeout." After long idle periods (days), the cold connection to the ARK server (DNS, TLS, server wake-up) easily exceeds the previous 5s timeout. Each of the 5 retries sent a NEW INITIALIZE_MESSAGE_BUS, restarting the network calls from scratch every time, never letting them finish. - Increase messageBusTimeoutMs from 5s to 30s (matches SDK default) - Reduce maxRetries from 5 to 2 (30s × 2 retries is sufficient) - Keep serviceWorkerActivationTimeoutMs at 5s (no network involved) - Keep SW unregistration as fallback if retries are still exhausted Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: detect and replace zombie service workers before initialization Some browsers (Vivaldi) keep the SW registered as "activated" after long idle periods but never actually wake the worker thread on postMessage. The 30s timeout and retry logic can't help because the SW is not processing messages at all — it's a zombie. Before calling ServiceWorkerWallet.setup(), ping the existing SW via a MessageChannel. If it doesn't respond within 2s, unregister it so setup() gets a fresh registration that actually works. - Add PING/PONG handler as the first thing in the SW script (works even if subsequent MessageBus initialization fails) - Add pre-flight ping in initSvcWorkerWallet before setup() - Rebuild wallet-service-worker.mjs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: untrack built wallet-service-worker.mjs This file was intentionally removed from the repo in #437. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: speed up cold start by pre-registering SW and parallelizing init Pre-register the service worker on page load so activation runs in parallel with React bootstrap, ASP fetch and auth check instead of blocking inside ServiceWorkerWallet.setup(). Also run the zombie-SW detection (timeout reduced from 2s to 500ms) concurrently with the IndexedDB getWalletState() warmup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: harden zombie SW ping against race and unexpected messages Capture registration.active into a local before the null check so it can't become null between the guard and postMessage. Only treat the response as alive when event.data.type is the expected 'PONG'. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only render the commit hash when showBackground is true (full-screen boot animation), avoiding overlap with page content like the Cancel button on the Connect screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nly" This reverts commit ceaa8f9.
* Move DFX Buy with Fiat from wallet button to Apps page - Remove Buy with Fiat button from wallet dashboard - Add DFX as an app with iframe integration, auth signing, loading state and error handling - Use official DFX logo mark with theme-aware colors - Use headless mode and hardcoded sign message (no extra API call) * Address PR review feedback from bordalix - Fix app description: "Ark" → "Arkade" - Move DFX card to alphabetical position (after Boltz) - Remove unused className='dfx-iframe' from iframe - Use hard navigate for back button to avoid iframe history issues * Fix prettier formatting for DFX iframe * Fix Loading component rename to LoadingLogo Apply upstream change: Loading component was renamed to LoadingLogo
* fix: include swap ID in error log for swap processing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: show friendly error messages for swap refund failures Map known boltz-swap error patterns to user-readable explanations: - Locktime not passed: show the date when funds become recoverable - VHTLC already spent: explain the swap was already refunded/claimed - VHTLC not found: explain no funds at the swap address Raw errors remain in the console log for debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* integrate branta for zk send side only * use branta payment type * Update src/screens/Wallet/Send/Form.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Keith <74844722+keith-gardner@users.noreply.github.com> * Update payment type in QR code handler. Signed-off-by: Keith <74844722+keith-gardner@users.noreply.github.com> * URL Sanitation. * Payment type. * check branta url for https * bump @branta-ops/branta version to 0.0.9 * lock @branta-ops/branta to version 0.0.9 * fix: address review comments on branta integration - Use `Payment | null` type instead of `any` for brantaPayment state - Clear loading state on early returns (empty rawScanData, non-ZK code) - Log Branta API errors via consoleError before clearing state * chore: regenerate pnpm-lock.yaml after rebase --------- Signed-off-by: Keith <74844722+keith-gardner@users.noreply.github.com> Co-authored-by: Kyle McCullen <kmccullen@protonmail.com> Co-authored-by: Keith <74844722+keith-gardner@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: keith <keith@keiths-iMac.local>
* feat: add Docker image build and publish to ghcr Add Dockerfile (multi-stage node build + nginx serve), nginx config with SPA routing fallback, and GitHub Actions workflow to build and push to ghcr.io/arkade-os/wallet:latest on master pushes. * feat: add runtime env var substitution for Docker image Build with placeholder values for VITE_* env vars, then substitute them at container startup via docker-entrypoint.sh. This allows a single image to serve multiple environments by passing env vars at runtime (e.g. docker run -e VITE_ARK_SERVER=https://...). * docs: add Docker image usage to README * fix(docker): run nginx as non-root and escape sed substitution values Address CodeRabbit review: - Add USER nginx directive so the container no longer runs as root - Escape &, |, and \ in env values before sed replacement to prevent injection/breakage in URLs and DSNs
* feat: show loading status messages during wallet initialization
Surface contextual status text below the boot animation logo so users
know what the app is doing while loading (e.g. connecting to service
worker, fetching transactions, restoring swaps).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show git commit hash at bottom of loading screen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hold loading screen until dataReady to prevent Init page flash
On slow networks, there was a window between initialized=true and
dataReady=true where the page fell through to the stale screen value
(Pages.Init), briefly showing the landing page before wallet.tsx's
dataReady effect navigated to Wallet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show git commit hash at bottom of boot loading screen only
Only render the commit hash when showBackground is true (full-screen
boot animation), avoiding overlap with page content like the Cancel
button on the Connect screen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase e2e timeout
* fix: prevent loading screen deadlock when reloadWallet fails
If the first reloadWallet call threw, dataReady was never set to true,
leaving shouldHoldOnLoading permanently true and trapping the user on
the boot loading screen. Move the dataReady/clearLoadingStatus logic
into a finally block so the loading gate always resolves.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: recover from init failures instead of deadlocking
- unlockWallet: reset authState to locked when initWallet throws so the
user returns to the password screen instead of being stranded on the
boot loader
- Unlock: surface a "Connection failed" error for non-password failures
- Connect: add cancel guard so initWallet won't start after unmount
- send.test.ts: align waitForSelector timeout with pay() helper (60s)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove cosmetic Cancel button from Connect screen
Init is fire-and-forget once the screen mounts — setPrivateKey and
initWallet run immediately with no abort path. Instead of building
cancellation machinery around an irreversible operation, remove the
Cancel button and back header so the UI matches the actual contract.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show retry prompt when initial wallet data load fails
Instead of silently falling through to an empty wallet when the first
reloadWallet fails, surface a BootError screen with Retry and Continue
buttons so the user can recover without reloading the page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): handle BootError overlay in wallet readiness checks
The boot flow now holds the loading screen until dataReady and shows
a BootError overlay on reloadWallet failure. E2e tests were waiting
only for "Send" text, which never appears behind the overlay.
Add waitForWalletPage() helper that waits for either "Send" or
"Continue anyway", dismisses the error if shown, then waits for
the wallet page. Timeout raised to 60s to account for the extra
data-load wait.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): increase waitForPaymentReceived timeout to 60s
The default 30s was borderline on CI even on master. With the new
boot flow waiting for dataReady, the overall test timing is tighter
and Boltz reverse-swap claims consistently exceed 30s on slow CI
runners.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): prevent loading screen from unmounting Connect during init flow
shouldHoldOnLoading (which waits for dataReady) was unmounting the
Connect component before swap recovery could complete during wallet
creation/restore. Skip the hold during the init flow so Connect stays
mounted. Also switch fire-and-forget exec('payinvoice') to awaited
execAsync and increase test timeouts for slower operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The boot animation overlay (z-index 9 white background) was intentionally kept alive when page transitioned to Unlock, but this made the password input completely invisible. For passwordless wallets the page never reaches Unlock (Loading → Wallet), so the exclusion was dead code — except when the passwordless auto-boot fails (e.g. Vivaldi SW timeout sets authState to 'locked'), where it caused a permanent deadlock. Dismiss the overlay for any non-Loading page, including Unlock. Also fix two pre-existing broken tests that looked for removed 'Loading...' text. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change the default currency display from "Show both" (sats + fiat) to "Fiat only" for new users. Existing users with saved preferences are unaffected. Users can still switch to "Show both" or "Sats only" in settings. Also fix responsive spacing in the Balance component — reduce bottom margin when only a single balance is shown, eliminating empty whitespace where the second row would be. - src/providers/config.tsx: default CurrencyDisplay.Both → CurrencyDisplay.Fiat - src/components/Balance.tsx: dynamic margin based on showBoth flag Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
showBoth was referenced on line 34 but never defined, causing a ReferenceError at runtime after merging #473. Define it from config.currencyDisplay and reuse it for the conditional render.
* Filter Google Translate widget errors from Sentry Users with browser translation enabled trigger noisy errors from translate.google.com and translate.googleapis.com (including stack overflows). These are not actionable bugs in our code. Add ignoreErrors, denyUrls, and beforeSend filters to drop them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix formatting in Sentry init Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Narrow Sentry stack overflow filter to Google Translate origin The blanket ignoreErrors for "Maximum call stack size exceeded" could hide real bugs. Gate it behind translate-origin detection so only stack overflows caused by translate.google(apis).com are dropped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Simplify beforeSend to just check isTranslateOrigin The a[je] check is already covered by ignoreErrors, and the stack overflow clause was redundant since isTranslateOrigin alone was already a prior branch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* add lock tests * add lock tests
PR #473 changed the default currency display from "Show both" to "Fiat only", which broke e2e tests that assert on SATS amounts in the balance display. Pre-set currency to "Show both" in the Playwright test fixture via addInitScript, and update the wallet unit test to expect USD instead of SATS.
* fix: guard serviceWorker access, add error boundary, fix notification and refresher errors - Move navigator.serviceWorker.controller access inside the 'serviceWorker' in navigator guard to prevent TypeError on browsers where the API is undefined (ARKADE-WALLET-3V, 36 users) - Add React ErrorBoundary that captures component stacks to Sentry, preventing white-screen crashes and enabling diagnosis of Error #31 issues (ARKADE-WALLET-2M/2E, 31 users) - Check Notification.permission before calling showNotification and handle the promise rejection to prevent unhandled errors (ARKADE-WALLET-2Y, 8 users) - Wrap Refresher pull-to-refresh handler in try/catch to prevent unhandled errors when MessageBus times out (ARKADE-WALLET-3D, 11 users) * fix: add user-friendly explanation to ErrorBoundary fallback UI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Pietro Grandi <dev@pietro.uno> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: run prettier on all source files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add LNURL support for amountless Lightning receives
When the user skips the amount on the receive screen and
VITE_LNURL_SERVER_URL is configured, the wallet opens an SSE
session with the lnurl-server and displays the resulting LNURL
in the QR code. When a payer scans and chooses an amount, the
wallet creates a reverse swap on-the-fly and returns the bolt11.
- LNURL embedded in BIP21 URI via lightning= parameter
- LNURL shown in expanded address list
- Session auto-closes when navigating away
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address review feedback: harden SSE parsing and validate amounts
- Wrap JSON.parse in try/catch to prevent SSE stream crash on
malformed payloads
- Validate amountMsat is a positive number before creating swap
- Strip trailing slash from LNURL server URL
- Add missing isAmountlessLnurl to useEffect dependency array
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix import alias for lnurlServerUrl
The replace_all swapped the alias direction — import the
correct export name from constants.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address CodeRabbit review: error handling, swap readiness, amount validation
- postInvoice now checks response.ok and throws on failure
- Clear LNURL state on invoice request failure and in finally block
- Gate LNURL session on arkadeSwaps being initialized and connected
- Add validLnSwap check before creating reverse swap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix SSE parsing: persist eventType across chunks
The eventType variable was declared inside the while loop,
causing it to reset on each chunk read. When the event: and
data: lines arrive in separate chunks (common in browsers),
the data line was silently ignored. Move eventType outside
the loop so it persists across chunk boundaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Send errors back to lnurl-server instead of killing session
When the wallet can't create a swap (e.g. amount outside limits),
post { error: "reason" } back to the server so the payer gets an
immediate error instead of waiting for timeout. Remove validLnSwap
pre-check since the server enforces min/max via LNURL spec and
createReverseSwap will fail naturally with a descriptive error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use auth token for invoice endpoint requests
Store the token from session_created SSE event and include it
as Authorization: Bearer header on all invoice POST requests.
Prevents unauthorized parties from submitting fake invoices
even if they discover the session ID.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix race condition and error handling in useLnurlSession
- Capture sessionId in local const before awaiting to prevent
race between concurrent events
- Pass abort signal to postInvoice/postError so in-flight POSTs
cancel on teardown
- Send error back to server on invalid amountMsat instead of
silently dropping (which left payer waiting until timeout)
* fix: check response.ok in postError to surface server-side failures
postError silently ignored non-2xx HTTP responses, unlike postInvoice
which throws on failures. Now logs the status and response text when
the server rejects the error notification.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Keep master's boot animation, error boundary, LNURL session - Keep PR's redesigned receive UI with sheets and toasts - Integrate LNURL amountless receive into new QR-first flow - Replace removed Loading component with LoadingLogo
The noUserDefinedPassword() check races with the dev auto-init, overriding authState to 'locked' and blocking the boot flow.
The fast auto-init from VITE_DEV_NSEC races with the boot animation timing, causing the overlay to never dismiss.
🔍 Review —
|
| Area | Status |
|---|---|
| LNURL security | ✅ Token auth, validation, abort handling |
| SW recovery | ✅ Zombie detection, retry limits, timeout tuning |
| Error handling | ✅ ErrorBoundary, BootError, Sentry filtering |
| Receive UX | ✅ QR-first, sheet modals, accessibility |
| State management | |
| package-lock.json |
Verdict: Well-structured feature PR with good security practices on the LNURL flow. Two minor issues flagged. The npm lockfile should be removed before merge.
Summary
useIonToastacross all call sites — dark bg, top-positioned, 250ms ease-out animations, reduced-motion compliant:focus-visibleinstead of:focus(no purple flash on tap), semantic<button>elements,touch-action: manipulationDependency
This PR is blocked by Kukks' LNURL implementation. The current receive flow shows the QR immediately without an amount, which means Lightning is unavailable until the user sets an amount (500+ sats). Once LNURL lands, we'll have zero-invoice Lightning support and the "show QR first" UX will work end-to-end.
Do not merge until LNURL is available.
Base branch
This PR is based on
wt-navbar-root-only-20260325(#474 — navbar only on root pages) to avoid navbar interference with the receive sheet modals.Test plan