From 59440ef1acd2dac9209e86fcd63f0fbe09ca9cd2 Mon Sep 17 00:00:00 2001 From: archipelago Date: Mon, 17 Aug 2026 09:30:57 -0400 Subject: [PATCH] fix(wallet): a seen receipt stays seen across refreshes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fc98c1d8 replaced the five-minute timer with "stays until seen", but kept "seen" in component state — so every page load forgot it and the entire ecash history came back as new. That is worse than the timer it replaced: the old behaviour at least let receipts go, this one resurrected them on every refresh. Reported from the node, and correctly. Acknowledgement now lives in localStorage, capped at 300 keys. That opens the opposite trap: on a browser with nothing stored, treating the whole history as unseen is the same wall of old receipts from the other direction. So a first run seeds everything older than five minutes as already seen — the window survives as a first-run heuristic, not as an expiry. Unreadable storage takes the same path, because reading a corrupt value as "nothing acknowledged" is the refresh bug wearing a hat. Also guards the balance readout against NaN. `sats == null` does not catch it, and arithmetic over a missing field produces it, so it would have rendered as the literal text "NaN sats" — worse than the zero the component exists to prevent, since a zero at least looks like a number. Frontend: 1000 tests green. Co-Authored-By: Claude Opus 5 (1M context) --- neode-ui/src/components/BalanceAmount.vue | 16 ++++- .../__tests__/BalanceAmount.test.ts | 12 ++++ .../views/__tests__/incomingPayments.test.ts | 55 +++++++++++++++-- neode-ui/src/views/home/HomeWalletCard.vue | 60 ++++++++++++++++++- 4 files changed, 134 insertions(+), 9 deletions(-) diff --git a/neode-ui/src/components/BalanceAmount.vue b/neode-ui/src/components/BalanceAmount.vue index caaf3cb2..7a037e1e 100644 --- a/neode-ui/src/components/BalanceAmount.vue +++ b/neode-ui/src/components/BalanceAmount.vue @@ -52,16 +52,26 @@ function cellDelay(i: number): string { * space before the unit cannot be eaten by Vue's whitespace condensing — and * so a test reading `.text()` sees exactly what a person reads on screen. */ +/** + * Is there a figure to show at all? + * + * `null`/`undefined` mean "not known yet" — but so does a NaN or an Infinity, + * which is what arithmetic on a missing field quietly produces. Those render + * as the literal text "NaN sats", which is worse than the zero this component + * exists to prevent: at least a zero looks like a number. + */ +const known = computed(() => props.sats != null && Number.isFinite(props.sats)) + const display = computed(() => { - if (props.sats == null) return '' - const figure = props.sats.toLocaleString() + if (!known.value) return '' + const figure = (props.sats as number).toLocaleString() return props.suffix ? `${figure} ${props.suffix}` : figure })