diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 13 | ||||
| -rw-r--r-- | src/ark/manager.js | 11 | ||||
| -rw-r--r-- | src/features/ark.js | 6 |
3 files changed, 22 insertions, 8 deletions
@@ -229,11 +229,7 @@ function footer() { h('a', { href: 'https://adamsoltys.com', target: '_blank', rel: 'noopener' }, 'Adam Soltys'), h('span', { class: 'faint' }, ' · '), t('footerSourceOn') + ' ', - h('a', { href: 'https://github.com/coinos/coinosv3', target: '_blank', rel: 'noopener' }, 'GitHub'), - // The commit stamp settles "is my PWA fresh?" AND "what code is this?" - // in one glance — filled at build time; the hash links to GitHub. - h('span', { class: 'faint' }, ' · Running '), - h('a', { class: 'faint', href: 'https://github.com/coinos/coinosv3/commit/{{COMMIT}}', target: '_blank', rel: 'noopener' }, '{{COMMIT}}') + h('a', { href: 'https://github.com/coinos/coinosv3', target: '_blank', rel: 'noopener' }, 'GitHub') ), h( 'div', @@ -250,7 +246,12 @@ function footer() { h('span', { class: 'faint' }, ' · '), h('button', { class: 'linklike', style: 'font-weight:400', onClick: toggleTheme }, resolvedTheme() === 'dark' ? t('lightMode') : t('darkMode')) ), - h('div', { style: 'margin-top:8px' }, languagePicker()) + h('div', { style: 'margin-top:8px' }, languagePicker()), + // The commit stamp settles "is my PWA fresh?" AND "what code is this?" in + // one glance — its own last line, so it never wraps the credits on mobile. + h('div', { class: 'faint', style: 'margin-top:6px' }, + 'Running git commit ', + h('a', { class: 'faint', href: 'https://github.com/coinos/coinosv3/commit/{{COMMIT}}', target: '_blank', rel: 'noopener' }, '{{COMMIT}}')) ); } diff --git a/src/ark/manager.js b/src/ark/manager.js index eb23b51..99ae2c5 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -298,7 +298,16 @@ export class ArkManager { const ourKeys = [hex.encode(this._key(0).pubkey)]; const tip = await this._tipMemo(); for (const v of m.vtxos) { - if (this._vtxo(v.id)) continue; + if (this._vtxo(v.id)) { + // The vtxo beat the message here (a mirror or snapshot merge carried + // it) — the money is present but the story is missing. Backfill the + // receive movement so history matches the balance. + if (!this.state.movements.some((x) => x.vtxoId === v.id)) { + this._movement({ type: 'receive', amountSat: v.amountSat, status: 'complete', vtxoId: v.id, ts: this._estimateReceiveTs(this._decoded(this._vtxo(v.id)), tip) }); + changed = true; + } + continue; + } try { await validateVtxo(v, { serverPubkey: this.serverPub, chain: this.chain, expectPubkeys: ourKeys }); } catch (e) { diff --git a/src/features/ark.js b/src/features/ark.js index bff84a9..66e4e44 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -183,7 +183,11 @@ export function mergeArkStates(a, b) { else { lg.claimed = lg.claimed || rg.claimed; lg.revoked = lg.revoked || rg.revoked; } } out.gifts = [...gifts.values()]; - out.mailboxCheckpoint = Math.max(a.mailboxCheckpoint || 0, b.mailboxCheckpoint || 0); + // The checkpoint is READING PROGRESS, not state: adopting a remote one + // skips every message this device never processed — balance appears (the + // vtxo unions in) but its receive movement and celebration never happen. + // Keep the local checkpoint; re-read messages dedupe harmlessly. + out.mailboxCheckpoint = a.mailboxCheckpoint || 0; out.nextKeyIndex = Math.max(a.nextKeyIndex || 1, b.nextKeyIndex || 1); out.nextLnRecvIndex = Math.max(a.nextLnRecvIndex || 0, b.nextLnRecvIndex || 0); out.receiveAckTs = Math.max(a.receiveAckTs || 0, b.receiveAckTs || 0); |
