From 37821c49d2f4c4702dd7a9ee1fcf166298ab6c35 Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Mon, 13 Jul 2026 14:04:51 -0700 Subject: [PATCH] surface bank record and extra json in ledger details, add payload dump to diagnostic --- scripts/diagnose-sync.ts | 22 +++++- src/routes/(app)/ledger/+page.server.ts | 20 +++++- src/routes/(app)/ledger/+page.svelte | 89 ++++++++++++++++++++----- 3 files changed, 113 insertions(+), 18 deletions(-) diff --git a/scripts/diagnose-sync.ts b/scripts/diagnose-sync.ts index f7dd6bf..ac736f5 100644 --- a/scripts/diagnose-sync.ts +++ b/scripts/diagnose-sync.ts @@ -3,6 +3,9 @@ // // Usage: // deno run -A scripts/diagnose-sync.ts [path-to-quantum.db] +// deno run -A scripts/diagnose-sync.ts [path] --dump +// prints the first few VERBATIM transaction objects the Bridge sent for +// matching accounts (shows whether `extra` carries payee/memo detail). // // Against a deployed container, copy the file out first: // podman cp :/data/quantum.db ./quantum.db @@ -10,7 +13,10 @@ import { DatabaseSync } from 'node:sqlite'; -const path = Deno.args[0] ?? './data/quantum.db'; +const args = [...Deno.args]; +const dumpIndex = args.indexOf('--dump'); +const dumpFilter = dumpIndex >= 0 ? (args.splice(dumpIndex, 2)[1] ?? '').toLowerCase() : null; +const path = args[0] ?? './data/quantum.db'; const db = new DatabaseSync(path); const fmtDate = (unix: number | null) => @@ -83,6 +89,20 @@ for (const r of rows) { ); } +if (dumpFilter) { + console.log(`\n=== Verbatim transactions for accounts matching "${dumpFilter}" ===`); + for (const account of parsed.accounts ?? []) { + const label = `${account.id} ${account.name}`.toLowerCase(); + if (!label.includes(dumpFilter)) continue; + console.log(`\n--- ${account.name} (${account.id}) ---`); + const txns = (account.transactions ?? []) as Record[]; + for (const txn of txns.slice(0, 5)) { + console.log(JSON.stringify(txn, null, 2)); + } + if (txns.length > 5) console.log(`… and ${txns.length - 5} more`); + } +} + console.log(`\nReading the result: - Account has txns in payload but 0 in db -> Quantum bug; please report. - Account has 0 txns in payload -> the provider isn't delivering diff --git a/src/routes/(app)/ledger/+page.server.ts b/src/routes/(app)/ledger/+page.server.ts index 6cf82c2..0d3423c 100644 --- a/src/routes/(app)/ledger/+page.server.ts +++ b/src/routes/(app)/ledger/+page.server.ts @@ -22,6 +22,24 @@ export const load: PageServerLoad = ({ url }) => { else if (pending === '0') filters.pending = false; const historyId = url.searchParams.get('history'); + let history: { + id: number; + events: ReturnType; + bankRecord: { description: string; sfinId: string; extra: string | null } | null; + } | null = null; + if (historyId) { + const id = Number(historyId); + const txn = db + .prepare('SELECT description, sfin_id, extra FROM transactions WHERE id = ?') + .get(id) as { description: string; sfin_id: string; extra: string | null } | undefined; + history = { + id, + events: listEvents(db, id), + bankRecord: txn + ? { description: txn.description, sfinId: txn.sfin_id, extra: txn.extra } + : null + }; + } return { rows: listLedger(db, filters), @@ -30,7 +48,7 @@ export const load: PageServerLoad = ({ url }) => { .filter((a) => a.state !== 'HIDDEN') .map(({ id, name, displayName }) => ({ id, label: displayName ?? name })), categories: listCategories(db, { activeOnly: true }), - history: historyId ? { id: Number(historyId), events: listEvents(db, Number(historyId)) } : null, + history, filters: { account: account ?? '', category: category ?? '', diff --git a/src/routes/(app)/ledger/+page.svelte b/src/routes/(app)/ledger/+page.svelte index bbfbc07..8aa797b 100644 --- a/src/routes/(app)/ledger/+page.svelte +++ b/src/routes/(app)/ledger/+page.svelte @@ -22,6 +22,22 @@ return row.provenanceActorHandle ?? 'someone'; } + function extraEntries(extra: string | null): [string, string][] { + if (!extra) return []; + try { + const parsed = JSON.parse(extra); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + return Object.entries(parsed).map(([k, v]) => [ + k, + typeof v === 'string' ? v : JSON.stringify(v) + ]); + } + return [['extra', extra]]; + } catch { + return [['extra', extra]]; + } + } + const timeFmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }); @@ -117,6 +133,12 @@ {provenanceLabel(row)} + {:else} + … {/if} @@ -125,24 +147,38 @@
- History + Details close
-
    - {#each data.history.events as event (event.id)} -
  1. - {timeFmt.format(new Date(event.createdAt))} — - {#if event.source === 'rule'} - rule {event.rulePattern} - {:else if event.source === 'reconciliation'} - carried from a pending transaction - {:else} - {event.actorHandle ?? event.actorDid} - {/if} - set {event.categoryName ?? 'uncategorized'} -
  2. - {/each} -
+ {#if data.history.bankRecord} +
+
Bank description
+
{data.history.bankRecord.description}
+ {#each extraEntries(data.history.bankRecord.extra) as [key, value] (key)} +
{key}
+
{value}
+ {/each} +
+ {/if} + {#if data.history.events.length > 0} +
    + {#each data.history.events as event (event.id)} +
  1. + {timeFmt.format(new Date(event.createdAt))} — + {#if event.source === 'rule'} + rule {event.rulePattern} + {:else if event.source === 'reconciliation'} + carried from a pending transaction + {:else} + {event.actorHandle ?? event.actorDid} + {/if} + set {event.categoryName ?? 'uncategorized'} +
  2. + {/each} +
+ {:else} +

No categorization yet.

+ {/if}
@@ -269,6 +305,27 @@ color: var(--text-muted); } + .bank-record { + display: grid; + grid-template-columns: max-content 1fr; + gap: 2px var(--space-4); + margin: 0; + } + + .bank-record dt { + color: var(--text-faint); + } + + .bank-record dd { + margin: 0; + font-family: var(--font-mono); + overflow-wrap: anywhere; + } + + .no-events { + color: var(--text-faint); + } + @media (max-width: 879px) { .desc { max-width: 140px; -- 2.51.2