fix(wallet): WebLN zap routing + offline banner heartbeat + SW dev gate#405
Merged
spe1020 merged 4 commits intoMay 29, 2026
Merged
Conversation
sw.js is a network-first / cache-first asset cache for production offline support. In dev it intercepts Vite's HMR module URLs and serves stale cached JS across reloads, putting the page into a state that hangs the tab — to the point where DevTools can't be opened to recover. Reproduced across multiple regular-tab sessions while debugging wallet flows; incognito (no SW) always worked. Gate the registration on !import.meta.env.DEV so dev never registers the SW. Production builds and Vercel previews still register normally.
WebLN wallets are deliberately not registered in the $wallets store — WalletPanel.svelte strips kind=1 entries on mount, so the WebLN state lives only in $weblnConnected. ZapModal's hasInAppWallet check looked at $activeWallet.kind, which is null for a WebLN-only user, so the zap took the submitWithExternalWallet path → closed ZapModal → launched the Bitcoin Connect payment dialog. BC has no NWC attached for a WebLN-only user, so it rendered "no wallet connected" or hung on "Creating Invoice…". Two changes wire the right path end-to-end: 1. ZapModal.hasInAppWallet now reads $weblnConnected directly (in addition to NWC kind=3 / Spark kind=4 via $activeWallet), and submitWithInAppWallet no longer fails its !$activeWallet guard when WebLN is connected. 2. walletManager.sendPayment falls back to payWeblnInvoice() when getActiveWallet() returns null but isWeblnConnected() is true, instead of bailing with "No wallet connected". End-to-end: WebLN connected → tap zap → invoice created → routed to window.webln.sendPayment(invoice) → done. No more BC bounce.
The 30 s heartbeat HEAD-fetched /favicon.ico, but the static asset is favicon.svg — the request permanently 404'd on Vercel and pinned the app into "offline", covering the menu button on mobile. Even with the right path, the fetch can fail in any environment where a service worker intercepts and errors, with the same effect. navigator.onLine is set by the browser based on network configuration and is updated synchronously when the native online / offline events fire (already wired in startMonitoring). That signal alone is what this app needs; the extra HTTP round-trip never added information. heartbeatCheck now returns navigator.onLine (true on web builds, the same on Capacitor as before). The setInterval-based polling still runs but is effectively a no-op until navigator.onLine actually flips.
The previous SW dev gate only prevents new registrations; service workers that were already installed from a prior prod-mode session (or earlier dev sessions before the gate landed) persist across reloads and continue to serve stale Vite HMR modules. The user had to manually unregister each one via DevTools every time. In dev, iterate every existing registration via navigator.serviceWorker.getRegistrations() and unregister each one. This self-heals on first dev load — subsequent sessions stay clean because the registration branch is still gated by !import.meta.env.DEV. Production behaviour is unchanged: dev-only branch is selected by import.meta.env.DEV; prod builds run the registration branch as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three independent fixes surfaced while testing the wallet flow on a Vercel preview:
1. WebLN zaps were bouncing to the Bitcoin Connect modal
WebLN wallets are deliberately not registered in the
$walletsstore —WalletPanel.sveltestrips kind=1 entries on mount, so the WebLN state lives only in$weblnConnected.ZapModal'shasInAppWalletcheck looked at$activeWallet.kind, which is null for a WebLN-only user, so the zap took thesubmitWithExternalWalletpath → closed ZapModal → launched BC, which had no NWC attached and rendered "no wallet connected".ZapModal.hasInAppWalletnow reads$weblnConnecteddirectly, andwalletManager.sendPaymentfalls back topayWeblnInvoice()whengetActiveWallet()is null butisWeblnConnected()is true. End-to-end: WebLN connected → tap zap → invoice →window.webln.sendPayment(invoice)→ done.2. Persistent "Offline" banner covering the menu button
The 30 s heartbeat HEAD-fetched
/favicon.ico, but the static asset isfavicon.svg— the request permanently 404'd on Vercel and pinned the app into "offline", whose mobile indicator (top-2 left-2 right-2) covered the hamburger menu.heartbeatChecknow just trustsnavigator.onLine. The browser'sonline/offlineevents are already wired up; the extra HTTP round-trip never added information and only introduced failure modes (404, SW interception, dev-server timeouts).3. Service worker hangs the regular tab in dev
sw.jsis a network-first / cache-first asset cache for production offline support. In dev it intercepts Vite's HMR module URLs and serves stale cached JS across reloads, putting the page into a state that hangs the tab — to the point where DevTools can't be opened. Incognito always worked. Gate the registration on!import.meta.env.DEV. Production builds and Vercel previews still register normally.Test plan
localhost:5173without freezing across hot reloads (SW skipped)onlineevent)🤖 Generated with Claude Code