Promoted BankTransactionsPage's local Tx/Account/Ledger/... interfaces
into @/types (BankLedgerTransaction extends BankTransaction so amounts
stay branded Cents) and wired bankTransactionsLedger/match/unmatch/
ignore/unignore/apply-merchant-match/auto-categorize with get<T>/post<T>.
useBankLedger data is now typed; all 9 as {...} casts in the page removed.
Dropped a dead Cents import. typecheck/lint/build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pins that Tracker, Analytics, Bills, and Summary agree on a month's
expected/paid totals — the 'same number, different truth' bug class
(QA-B5/B9). Hand-seeds an off-month annual bill, an occurring quarterly
bill, and monthly bills w/ full+partial payments; asserts in integer
cents that Analytics.expected / Bills-gated-sum / Summary.expense_total
all == Tracker.total_expected, Analytics.actual == Tracker.total_paid,
and the off-month annual inflates no surface. All reconcile today; the
harness makes future gating drift fail loudly. Suite 193 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The deliberate manual POST /payments had no dedupe guard (double-submit
made a second payment), but silently deduping it like the auto paths
would lose a legitimately-identical same-day payment — a money bug. So
the server returns 409 DUPLICATE_SUSPECTED (existing payment attached),
and a shared client helper (client/lib/paymentActions.ts, used by the
tracker inline cell, partial-payment dialog, and bill modal) turns it
into an 'add anyway?' toast that resends with allow_duplicate. Automated
one-click paths keep deduping silently (a repeat there is a retry).
Dropped a now-unused api import in PaymentLedgerDialog (Track E sweep).
Server suite 188 green; typecheck/lint/build clean; probe 17/17.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quick/bulk-pay were already atomic (X1); the audit found the row write +
applyBalanceDelta split across two un-transactional statements on manual
POST /payments, autopay-confirm, DELETE (unpay), restore, and PUT (edit,
where the bill-balance write and the payment UPDATE weren't atomic). A
failure between the two could leave a payment without its balance
adjustment (or a balance reversed without the row deleted). Each is now a
single db.transaction(). Behavior-preserving.
tests/paymentsRoute.test.js pins the create→delete→restore→edit balance
round-trip + double-restore no-op. Full suite 186 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The living QA doc's copy-pasteable coverage-recon greps pointed at
client/App.jsx, Sidebar.jsx, <Page>.jsx and the B-UI primitive census
listed button.jsx/input.jsx/etc — all now .tsx, so the commands errored
('No such file'). Retargeted to .tsx (+ useAutoSave.test.ts) and noted
that recon greps target .tsx and 'npm run typecheck' is now a guard.
Behavior-only migration: client 42/42 + e2e probe 17/17 stay green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the final 5-page batch (Snowball, BankTransactions, Spending,
Bills, Subscriptions) and the dead-total-state find TypeScript surfaced.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bill list/reorder/history-visibility page typed: Bill[] from useBills,
BillFilters/DisplayPrefs/ModalState/HistoryRange local types, drag +
move-controls typed via DragProps/MoveControls, makeBillDraft(...) as
Partial<Bill> at the modal call sites, setQueryData<Bill[]> for
optimistic list edits, errMessage(unknown) for strict catches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
YNAB-style spending view typed: CatEntry (category_id number|'other'|
null) with a RealCatEntry narrowing for the budget-indexed paths,
SpendingSummary/SpendingTx/IncomeTx/SpendingRule/CategoryGroup local
types, budgets as Record<string,number|null>, React Query
setQueryData<T> for optimistic budget/categorize edits, cast-at-boundary
for api.* results. Dropped a pre-existing dead 'total' state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cents-based bank ledger typed end-to-end: local Tx interface (amount:
Cents, index sig for BankTransaction compat), Ledger/Account/Summary
types, React Query setQueryData<Ledger> for optimistic row patches,
cast-at-boundary for api.* results, Promise.allSettled bulk handlers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tracker: URL-driven month/filters/sort, React Query data (TrackerResponse),
bank-projection banner, summary cards, safe-to-spend, overdue center, drift
insights, drag-reorder buckets, and all row/modal orchestration. TrackerSummary.trend
is typed unknown in @/types so it's cast at the TrendCard boundary.
ProfilePage (profile summary + login history + edit + notifications + push +
2FA + privacy) — exports asSettings/NotificationPreferences/PushNotifications
now typed, so SettingsPage picks up real types. Widened useAuth setUser to
Dispatch<SetStateAction<User|null|undefined>> to allow the updater form.
TS flagged a dead prop: onSaved was passed to NotificationPreferences (which only
takes settings) — removed it. Notification components imported from ProfilePage
(still .jsx) type as any until that page converts.
Annotated makeBillDraft's return as SourceBill (index-signatured) and exported it,
so its draft output stays typed; BillModal keeps initialBill: Partial<Bill> and
callers cast makeBillDraft(...) as Partial<Bill> at the boundary. typecheck 0.
Typed MerchantRule/Conflict/Suggestion + generic useDebounce; billId widened to
Id (number|string) to match the api layer; asserted billId at the BillModal call
site (rendered only for existing bills). Dropped unused RuleChip billId prop.
typecheck 0.
Also removed a stale PayoffChart.jsx duplicate (its .tsx was already committed).
Branded Dollars on income transactions/bank-tracking balances. typecheck 0.
The XLSX importer (workbook parse → per-row decision tree → bulk actions →
bill-history grouped import). Typed PreviewRow/Decision/BillGroup/ImportDetail
and the merged BillImportResult accumulator; fixed Date subtraction in the
duplicate-date sort to use getTime(). Dropped two unused imports (CountPill/fmt)
the original .jsx never used. Trimmed unused eslint-disable directives.
This completes the entire client/components/data/ directory (14 files) → .tsx.
typecheck 0, lint 0 errors, build green.
ImportMyData (SQLite), ImportTransactionCsv, TransactionMatching sections.
Branded BankTransaction/Bill/Category flow through the matching workbench;
relaxed BillModal initialBill to Partial<Bill> (it's a pre-filled draft).
TypeScript caught a real bug: ImportTransactionCsvSection used <CountPill> 6×
but never imported it — a ReferenceError that crashed the CSV preview/results
views. Added the missing import.
typecheck 0, build green.
Completes the entire client/components/admin/ directory conversion to .tsx:
OidcForm/AuthConfigData/OidcTest types for the OIDC config form; Backup/
BackupSettings types for the backup manager. errMessage() on all strict
catch(unknown) sites. typecheck 0, lint 0 errors, build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
adminShared.tsx (SectionHeading/FieldRow/Toggle/formatDateTime/BackupTypeBadge —
shared by the admin cards) and AddUserCard.tsx. Deleted PrivacyAdminCard.jsx:
TypeScript surfaced that it's imported nowhere AND calls non-existent
api.privacySettings/setPrivacySettings (the geolocation toggle actually lives on
/profile/settings) — dead code with broken calls, so removed rather than
converted. typecheck 0, build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First two pages — confirms the page pattern (self-contained, untyped .jsx
feature-children accept any props). AdminPage: User/boolean|null/unknown[] state
+ API-response casts. SubscriptionCatalogPage: consumes the now-typed BillModal
(Bill/Category), casts the partial fallback bill. typecheck 0, build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The app's largest, most-central component (1092 ln, the add/edit form reached
from 9 entry points). Typed ~40 useState vars (Bill/Payment/BankTransaction/
BulkUnmatchState/Date), the useActionState save (isNew narrows `bill` non-null
via the aliased `!bill` const), FormErrors = Record<string,string>, and every
API response cast (billPayments/billTransactions/syncBillSimplefinPayments/
verifyAutopay/billMerchantRules). Its now-typed sub-components type-check the
props BillModal passes them. cycle_day coerced to string for Radix Select;
Bill index-sig fields (autopay_verified_at, source_bill_id) cast at use.
client/components/bill-modal/ + BillModal are 100% .tsx. typecheck 0, lint 0
errors (35 warns), build green, 48 tests, 17/17 e2e probe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 7 BillModal sub-components + transactionDisplay helper: TemplateSection,
AutopayTrustIndicator, PaymentFormFields, PaymentHistoryList, DebtDetailsSection,
LinkedTransactionsSection, UnmatchDialogs. New @/types.BankTransaction — raw bank
amounts stay in CENTS on the wire, so branded `Cents` (formatCentsUSD, not
formatUSD), the complement to the Dollars brand. BulkUnmatchState typed with
null-safe setState callbacks. typecheck 0, lint 0 errors, build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SummaryCard (top-level; CardType/CardDef, value: Dollars) and ReleaseNotesDialog
(the version-ack modal we hardened; typed useAuth, HTMLElement-cast for
activeElement focus). typecheck 0, build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>