diff options
| author | Adam Soltys <[email protected]> | 2026-10-08 22:18:34 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-08 22:18:34 -0700 |
| commit | 36205bbd6a8be6ad76488e58862fb16798a6cfd6 (patch) | |
| tree | 4188bb1d21864dbdc727d0e24149f587192b419c | |
| parent | 3a11c993baa2fc3a3b0bb754e19d783f5b684912 (diff) | |
| download | coinosv3-36205bbd6a8be6ad76488e58862fb16798a6cfd6.tar.gz coinosv3-36205bbd6a8be6ad76488e58862fb16798a6cfd6.zip | |
A tapped lightning:lnurl (NFC at a till) waits on the paying shell for the feature that pays it instead of sitting as raw text in the recipient field; Paste and Scan stay one line tall as a long address grows the field
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/app.js | 42 | ||||
| -rw-r--r-- | src/style.css | 3 |
2 files changed, 28 insertions, 17 deletions
@@ -4944,6 +4944,12 @@ function sendTab() { ); } if (ui.sendResult) return sendResultView(); + // an NFC/intent payment waiting for its feature: the tap flow's paying shell + if (ui.intentWait) { + return h('div', { class: 'card col autopay-card', style: 'align-items:center;text-align:center;gap:14px;padding:48px 20px' }, + h('div', { class: 'check-badge autopay-badge' }, '\u26a1'), + h('div', { class: 'small muted autopay-foot' }, '')); + } if (ui.broadcastTx) { sendFocused = true; return broadcastConfirmView(); } const featView = featureHook('sendView'); if (featView) { sendFocused = true; return featView; } @@ -6236,6 +6242,7 @@ document.addEventListener('visibilitychange', () => { // boot can put that well after load. handleScanned routes it exactly like a // scanned QR: bolt11s and lnurls through the feature chain, bitcoin: through // BIP-21 (nostr profile intents already rerouted onto the /npub deep link). +const LN_INTENT = /^(lightning:|lnurl|lnbc|lntb|lntbs|lnbcrt|lno1|noffer1)|^[^\s:@/]+@[^\s@/]+\.[a-z]{2,}$/i; if (INTENT_URI) { const started = Date.now(); const tick = setInterval(() => { @@ -6243,25 +6250,26 @@ if (INTENT_URI) { if (!activeAccount() || !wallet.loaded || ui.screen !== 'wallet') return; clearInterval(tick); ui.tab = 'send'; + // A lightning:lnurl… (or ln-address, bolt11, offer) intent — an NFC tap + // at a till — often lands BEFORE the feature that pays it is ready (ark + // connects lazily, so zaps' matchSendText declines). Hold it on the same + // paying shell the tap flow shows, never the raw URI in the recipient + // field, and hand it over the moment a feature claims it; only after a + // long wait does it fall back to the form like a scan. + if (featureMatchSend(INTENT_URI)) return render(); + if (!LN_INTENT.test(INTENT_URI)) { render(); return handleScanned(INTENT_URI); } + ui.intentWait = true; render(); - handleScanned(INTENT_URI); - // A lightning:lnurl… (or ln-address) intent often lands BEFORE the - // feature that pays it is ready — ark connects lazily, so zaps' - // matchSendText declines and the raw URI falls through into the - // recipient field. Keep re-offering the text to the feature chain for a - // while; when a feature finally claims it (the zap amount screen takes - // over), sweep the leftover raw text out of the recipient. - let tries = 0; + const waitStart = Date.now(); const retry = setInterval(() => { - const s = ui.send; - const pending = s && s.recipients.length === 1 && s.recipients[0].address === INTENT_URI; - if (!pending || ++tries > 20) return clearInterval(retry); - if (featureMatchSend(INTENT_URI)) { - clearInterval(retry); - s.recipients[0].address = ''; - render(); - } - }, 1000); + const claimed = ui.intentWait && featureMatchSend(INTENT_URI); + if (!claimed && ui.intentWait && Date.now() - waitStart < 20_000) return; + clearInterval(retry); + const wasWaiting = ui.intentWait; + ui.intentWait = false; + if (!claimed && wasWaiting) handleScanned(INTENT_URI); + render(); + }, 150); }, 500); } diff --git a/src/style.css b/src/style.css index 28990fa..db6e00e 100644 --- a/src/style.css +++ b/src/style.css @@ -277,6 +277,9 @@ textarea.send-dest { /* the hint in the page's own face: narrower than the address font, so it fits the one line beside Paste and Scan on a phone */ textarea.send-dest::placeholder { font-family: var(--sans); } +/* Paste and Scan stay one line tall at the top as a long address grows the + field (a flex row stretches them to its full height otherwise) */ +.input-group:has(> textarea.send-dest) > button { align-self: flex-start; height: calc(1.4rem + 24px); } /* unit toggle */ .seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 10px; overflow: hidden; } |
