feat(wallet): app icons, mobile layout, and a funding mode on the Lightning modal
Demo images / Build & push demo images (push) Successful in 3m18s

Icons: each node choice now shows its app icon (lnd.png; Core Lightning's is
vendored from the Umbrel gallery as core-lightning.svg). Vendored rather than
hotlinked on purpose — these nodes run offline/airgapped, and a remote image
would both break there and leak a request to a third-party host on every
render. A missing asset falls back to a neutral bolt glyph so a row can never
render a broken-image box.

Mobile: the choice row keeps icon + name + blurb together and drops the action
to its own full-width line under 26rem, instead of squeezing the description
into a two-word column next to a button.

Funding mode: a node that is running but has no funds / no inbound liquidity
is neither "install one" nor "start it", so the same modal gains a third mode
that explains it and routes to the run-lightning-node goal, where funding and
channel-opening already live — reusing that flow rather than duplicating it.

It fires where the user actually meets the problem: on a failed attempt.
handleLightningFailure() maps a running node's send/receive failure onto the
funding modal, matched on message text because LND surfaces "no route", "no
channels" and "insufficient balance" as plain strings with no distinct code —
and all three mean the same thing to a user: fund me.

Verified: 5 gate tests; npm run build clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-02 07:43:24 -04:00
co-authored by Claude Opus 5
parent fa26c5fc56
commit 700947ea3c
5 changed files with 199 additions and 9 deletions
@@ -665,6 +665,8 @@ async function send() {
// Success pane (or the token pane for ecash mints) takes over the modal.
confirming.value = false
} catch (err: unknown) {
// Running node with nothing to pay with -> funding modal, not a raw string.
if (effectiveMethod.value === 'lightning' && lightning.handleLightningFailure(err)) return
error.value = err instanceof Error ? err.message : t('web5.sendFailed')
} finally {
processing.value = false