merge: bring main (v1.7.125 + .126 work) into phase-13 branch pre-deploy

63 main commits since the fork point — gate cookie-strip fix, named-volume
create fix, appgate catalog classification, RNode error surfacing — merged
so 13-14/13-15 on-device verification runs against current production code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-06 09:33:40 -04:00
co-authored by Claude Fable 5
117 changed files with 5120 additions and 632 deletions
@@ -2,11 +2,15 @@
<BaseModal :show="show" title="" max-width="max-w-lg" @close="emit('close')">
<!-- Header: app icon + "Install Bitcoin Knots/Core" -->
<div class="flex items-center gap-4 mb-5 -mt-2">
<!-- object-contain, not the default fill: app icons are not all square
(bitcoin-knots is not), so a fixed 56x56 box distorted or cropped the
mark against the rounded corners. Contain plus a dark plate shows the
whole icon whatever its aspect ratio. -->
<img
v-if="app?.icon"
:src="app.icon"
:alt="app?.title || ''"
class="w-14 h-14 rounded-xl shadow-lg shrink-0"
class="w-14 h-14 rounded-xl shadow-lg shrink-0 object-contain bg-black/40 p-1"
/>
<div v-else class="w-14 h-14 rounded-xl bg-white/10 flex items-center justify-center shrink-0">
<svg class="w-7 h-7 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -107,7 +107,21 @@ const props = defineProps<{
const emit = defineEmits<{ close: []; received: []; scan: [] }>()
watch(() => props.show, (open) => {
if (open && props.autoGenerate && receiveMethod.value === 'onchain' && !onchainAddress.value) {
if (!open) return
// Blank slate on every open: a leftover amount/memo/token or a previous
// invoice quietly carrying into a new receive flow is exactly the stale-
// state class the operator flagged on the send modal (2026-08-05).
receiveMethod.value = 'onchain'
invoiceAmount.value = 0
invoiceMemo.value = ''
invoiceResult.value = ''
onchainAddress.value = ''
arkAddress.value = ''
ecashToken.value = ''
ecashResult.value = ''
error.value = ''
processing.value = false
if (props.autoGenerate && receiveMethod.value === 'onchain') {
void receive()
}
})
+47 -5
View File
@@ -327,15 +327,57 @@ const isSweep = computed(() => sendMethod.value === 'onchain' && sendAll.value)
function toggleSendAll() {
sendAll.value = !sendAll.value
if (sendAll.value && onchainBalance.value === null) {
rpcClient.call<{ balance_sats: number }>({ method: 'lnd.getinfo', timeout: 5000 })
.then((res) => { onchainBalance.value = res.balance_sats || 0 })
.catch(() => { /* balance hint is best-effort */ })
if (!sendAll.value) {
// Disarming clears the field — a swept-balance figure left behind reads
// as a typed amount.
amount.value = 0
return
}
// Arming shows the swept balance IN the (disabled) amount field — a field
// stuck at 0 while "send all" is lit read as "sending nothing" (operator
// feedback 2026-08-05). Refresh the figure on every arm.
const applyBalance = () => {
if (sendAll.value && onchainBalance.value !== null) amount.value = onchainBalance.value
}
applyBalance()
rpcClient.call<{ balance_sats: number }>({ method: 'lnd.getinfo', timeout: 5000 })
.then((res) => { onchainBalance.value = res.balance_sats || 0; applyBalance() })
.catch(() => { /* balance hint is best-effort */ })
}
// Leaving the on-chain tab disarms the sweep so it can never apply elsewhere
watch(sendMethod, (m) => { if (m !== 'onchain') sendAll.value = false })
// (and drops the swept-balance figure it wrote into the amount field).
watch(sendMethod, (m) => {
if (m !== 'onchain' && sendAll.value) {
sendAll.value = false
amount.value = 0
}
})
// Every open starts from a blank slate. Stale state from the previous send —
// destination, amount, and above all an armed "send all funds" toggle — is
// dangerous to inherit invisibly (operator feedback 2026-08-05).
watch(() => props.show, (shown) => {
if (!shown) return
sendMethod.value = 'lightning'
amountUnit.value = 'sats'
amountEntry.value = 0
dest.value = ''
error.value = ''
successInfo.value = null
ecashToken.value = ''
sendAll.value = false
onchainBalance.value = null
feePreset.value = 'standard'
customConfTarget.value = null
customSatPerVbyte.value = null
resolvedFeeParams.value = {}
feeEstimate.value = null
confirming.value = false
confirmBalance.value = null
invoiceAmountSats.value = null
processing.value = false
})
// --- On-chain network fee: presets map to LND confirmation targets; custom
// --- takes a block target or an explicit sat/vB rate (rate wins).
+8 -2
View File
@@ -51,10 +51,16 @@
</svg>
<p class="text-sm text-white/60 text-center">How do you want to read the QR?</p>
<!-- hasNativeQr: on the companion (plain http, no getUserMedia)
the native bridge still provides a live camera -->
<button v-if="!liveCameraUnavailable || hasNativeQr" @click="chooseCamera" class="glass-button w-full px-4 py-2.5 rounded-lg text-sm font-medium">
the native bridge still provides a live camera. On plain-http
desktop the button stays VISIBLE hiding it read as "the
scanner is gone" (operator, 2026-08-05); choosing it surfaces
the browser's HTTPS requirement with the fallbacks instead. -->
<button @click="chooseCamera" class="glass-button w-full px-4 py-2.5 rounded-lg text-sm font-medium">
Scan with camera
</button>
<p v-if="liveCameraUnavailable && !hasNativeQr" class="text-[11px] text-white/40 text-center -mt-1">
Your browser only allows live camera on HTTPS pages — the photo and paste options below always work.
</p>
<button @click="photoInput?.click()" class="glass-button w-full px-4 py-2.5 rounded-lg text-sm font-medium">
Upload / take a photo of the QR
</button>