diff --git a/neode-ui/src/components/ReceiveBitcoinModal.vue b/neode-ui/src/components/ReceiveBitcoinModal.vue index 085133e1..63cc4bcc 100644 --- a/neode-ui/src/components/ReceiveBitcoinModal.vue +++ b/neode-ui/src/components/ReceiveBitcoinModal.vue @@ -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() } }) diff --git a/neode-ui/src/components/SendBitcoinModal.vue b/neode-ui/src/components/SendBitcoinModal.vue index 52b0720d..e5fb240e 100644 --- a/neode-ui/src/components/SendBitcoinModal.vue +++ b/neode-ui/src/components/SendBitcoinModal.vue @@ -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). diff --git a/neode-ui/src/components/WalletScanModal.vue b/neode-ui/src/components/WalletScanModal.vue index 2107ec1d..cb41c2e6 100644 --- a/neode-ui/src/components/WalletScanModal.vue +++ b/neode-ui/src/components/WalletScanModal.vue @@ -51,10 +51,16 @@
How do you want to read the QR?
-The Lightning, Bitcoin, Electrum and mesh screens work again behind the login gate. Since the gate went up, those screens would load their frame and then show every number as unreachable. The gate was deliberately hiding your login from the apps it protects — right for third-party apps, wrong for the node's own screens, which need that login to fetch your data. The gate now removes only its own credential and the node's own screens explicitly receive yours. The same mistake was also quietly signing you out of apps with their own logins — Vaultwarden, Nextcloud, Gitea — on every single request; that stops too.
+IndeeHub heals itself. Three separate faults: its database helper was recreated with permissions too tight to read its own files (it had crashed and restarted about ten thousand times on one node); on another node two of its seven parts could never be recreated at all because of how the node asked for their storage — the node would remove the old part and then fail to build its replacement, leaving the app half-missing forever; and a regenerated password could lock the app out of a database that keeps the original. All three are fixed, and the storage fault fixes the same trap for every future multi-part app.
+Send and Receive open clean every time. Whatever you typed last — an address, an amount, and above all an armed "send all funds" toggle — no longer quietly carries over into the next payment. And choosing "send all funds" now shows the amount being swept instead of a confusing 0.
+The camera scanner option no longer vanishes on desktop. Browsers only allow the live camera on secure (HTTPS) pages, and the scan window used to silently hide the camera choice on plain connections, which read as "the scanner is gone". The option now stays visible and explains itself, and the photo and paste routes always work. The companion app's built-in scanner is untouched.
+Also: the app login page uses the Archipelago mark and stays centred on phones with the keyboard open, app icons in the install window are no longer cropped, and when the node fails to build a container it now records the actual reason instead of a one-line stub that hid the cause of the IndeeHub fault for days.
+