diff options
| author | Adam Soltys <[email protected]> | 2026-08-18 11:49:56 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-18 11:49:56 -0700 |
| commit | c553ea40de4aecd9453272599d450310d6e306b0 (patch) | |
| tree | 5c1b4f3c919826333c4f836b632fa47dc48b650d /src | |
| parent | e1d4f0e0dcf8e13a79ed94b6c696805e8568318d (diff) | |
| download | coinosv3-c553ea40de4aecd9453272599d450310d6e306b0.tar.gz coinosv3-c553ea40de4aecd9453272599d450310d6e306b0.zip | |
Money stops arriving without a story: the mailbox checkpoint stays local reading progress (a merged one skipped every unprocessed message), vtxos that beat their message here get a backfilled receive, and the commit stamp takes its own footer line
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
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); |
