fix(wallet): a seen receipt stays seen across refreshes
Demo images / Build & push demo images (push) Failing after 2m6s
Demo images / Build & push demo images (push) Failing after 2m6s
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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
603291008b
commit
59440ef1ac
@@ -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
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-if="props.sats == null"
|
||||
v-if="!known"
|
||||
class="balance-pixels"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
|
||||
@@ -24,6 +24,18 @@ describe('BalanceAmount', () => {
|
||||
expect(w.find('.balance-pixels').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('never prints NaN at a person', () => {
|
||||
// Arithmetic over a missing field produces NaN, which is not caught by a
|
||||
// null check and renders as the literal text "NaN sats" — worse than the
|
||||
// zero this component exists to prevent, because at least a zero looks
|
||||
// like a number.
|
||||
for (const bad of [NaN, Infinity, -Infinity]) {
|
||||
const w = mount(BalanceAmount, { props: { sats: bad } })
|
||||
expect(w.find('.balance-pixels').exists()).toBe(true)
|
||||
expect(w.text()).toBe('')
|
||||
}
|
||||
})
|
||||
|
||||
it('shows a genuine zero as a figure, not as loading', () => {
|
||||
// The inverse mistake: a node that really has no coins must be told so
|
||||
// plainly, not left shimmering forever.
|
||||
|
||||
Reference in New Issue
Block a user