Commit Graph

210 Commits

Author SHA1 Message Date
null f587d09d4a style: apply Prettier across client + server (no behavior change)
One-time repo-wide `prettier --write` (isolated commit so future diffs
stay reviewable). Mechanical formatting only.

Verified unchanged: typecheck + typecheck:server + check:server clean;
server tests 232/232; client tests 50/50; Vite build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:23:53 -05:00
null c41f1a8a7b feat(tracker): status row border-left indicators, refined metric cards, search filter, and CSS polish 2026-07-06 13:47:34 -05:00
null df1f61d6cc feat(brand): render the real product glyph SVGs (not inline approximations)
BrandGlyph drew hardcoded monochrome shapes from an inline switch and
ignored the actual /brand/glyphs/*.svg files. Render the real files via a
robust <img> (fail-soft onError, intrinsic width/height so no layout
shift, alt for a11y parity) so every existing placement — PageHeader
(Tracker/About/Release), Command Palette, Release Notes, Onboarding,
Login — shows the full-color brand tiles. Deletes the ~85-line dead
BrandGlyphShape switch and the now-unused useId import.

Senior-review while-here:
- Guard test (client/lib/brand.test.ts): asserts every BrandGlyphName
  asset — and every /brand asset in brand.ts — resolves to a real,
  non-empty file on disk. Catches the "image added but path wrong /
  renamed" regression TS can't see.
- Widen vitest include to {js,jsx,ts,tsx}: TypeScript client tests were
  silently never run (client/hooks/useAutoSave.test.ts). Now 6 files / 50
  tests run (was 4 / 42).
- Remove dead brand.ts entries: legacyLogo (pointed at the OLD pre-brand
  /img/logo.png) and the unused ADMIN_GLYPH const.
- PageHeader glyph class inline-grid -> inline-block for the <img>.

Verified: typecheck + lint clean; test:client 50/50; build (glyphs land
in dist/brand/glyphs, served 200 as image/svg+xml); e2e probe 17/17;
login visual baseline unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:32:37 -05:00
null 1238ac5b14 feat(demo): full "take it for a ride" seed + surgical, correct removal
The demo seeder created bills + categories only, so nearly every page
rendered empty; clear-demo had a data-loss bug and error-protection gaps.

Seed now populates every user-facing surface: bills with autopay states,
3-6 months of payment history (with drift + debt paydown), monthly
skips/overrides/snoozes/notes, a demo bank source + accounts + matched/
unmatched/subscription transactions, categorised spending + budgets +
rules, planning income/starting-amounts, category groups, an active
snowball plan (+ extra-payment), merchant rules, and a calendar feed —
so a new user can explore Tracker (incl. overdue/drift), Analytics,
Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions,
Banking and Calendar with realistic data.

Correct removal (the emphasis):
- Migration v1.07 adds an is_seeded marker to the 12 user-scoped,
  non-cascading demo tables (bill-children cascade with their seeded bill).
- clearSeededDemoData() (new, in userDataService alongside eraseUserData)
  removes ONLY seeded rows in one transaction, child->parent, and keeps a
  seeded category if a user's own bill still references it.
- Fixes the category collision: seed marks ONLY categories it creates, so
  clear no longer deletes the user's default categories / uncategorises
  their real bills.

Error protection: idempotency guard on seeded (not all) bills -> 409
instead of a fake "created 0"; whole seed wrapped in one transaction
(atomic); rate-limit + audit + standardizeError on all three routes;
seeded-status reports payments/transactions counts; dead --force removed.
Client copy/counts corrected.

Verified: typecheck/check clean; server suite 232/232 (+6 new); client
typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod
DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip
on the data-rich user restores their real data byte-for-byte with no
orphans.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:06:47 -05:00
null 23f79c31e0 feat(ui): layout refinements — sidebar, nav pill, command palette, brand block 2026-07-06 12:28:25 -05:00
null f71b433323 feat(ui): refine OnboardingWizard, LoginPage, and SettingsPage 2026-07-06 12:22:47 -05:00
null 3a14c50e08 chore(brand): update PWA icons, release notes, and About page 2026-07-06 12:13:01 -05:00
null 343cc3a36f feat(tracker): persist Overdue/Drift panel collapse + inline hide
The Overdue Command Center and Price-Change (drift) panels reset to
expanded on every load — their collapse arrows used local useState. Now
the collapsed state is remembered per-user via two new settings
(tracker_overdue_collapsed / tracker_drift_collapsed), mirroring the
search panel's useSearchPanelPreference pattern. Each panel header also
gains an inline "Hide" control (EyeOff, like the search panel) that writes
the existing tracker_show_* setting and fires an Undo toast; hidden
sections come back from Settings → Tracker Layout.

- services/userSettings.js: allowlist + defaults for the two collapse keys
- TrackerPage: read collapse state, hide handlers with Undo, wire props
- OverdueCommandCenter/DriftInsightPanel: controlled collapse + header hide

Verified: typecheck 0, lint 0 errors, build, and a live settings round-trip
(collapse persists, hide writes, Undo restores, allowlist rejects junk keys).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:34:39 -05:00
null 1457de487f feat(security): show a key fingerprint (not the key) in the encryption status
The encryption key must never be retrievable through the app (that would undo
the whole point of TOKEN_ENCRYPTION_KEY — a stolen session/XSS could then read
the master key). Instead surface a non-reversible fingerprint so operators can:
- verify which key is currently active,
- confirm a running instance matches the key they backed up, and
- spot an accidental key change (which would make existing secrets unrecoverable).

- encryptionService.keyFingerprint(): domain-separated SHA-256 prefix of the
  active key (env if set, else the stored DB key); never force-creates a key
  (returns null when none exists), shares nothing with the cipher derivation.
- admin bank-sync-config exposes key_fingerprint alongside encryption_key_source.
- Admin Bank Sync card renders it with backup guidance.
- Test: stable per key, differs by key, not reversible / not the raw key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:15:32 -05:00
null 4638d24a1d fix(payoff): correctness, data-integrity & clarity of the payoff simulator
Data integrity (A):
- "Apply … to my budget" overwrote the bill's expected_amount (its real
  recurring amount, used in budgets + payment recording) with the simulated
  payoff payment. Replace with "Save APR to this bill" — persists interest_rate,
  leaves expected_amount untouched. Fixes the corruption AND the interest-free
  root cause (future runs use the saved APR). BillModal debt section also opens
  when a bill has an interest_rate.

Error handling (B):
- Undo now has try/catch + error toast (was an unhandled rejection that left the
  wrong value applied); Apply uses errMessage.
- loadData no longer snaps the selection back to the first bill after a
  save/undo (was a stale-closure over selectedId); background refreshes are
  "silent" so a transient failure no longer replaces the whole page.

Makes-sense (C):
- Distinguish "no APR" from a real 0% and show a notice (was silently interest-
  free with no indication).
- Without a real minimum, show absolute Time-to-Payoff + Total Interest instead
  of a broken "$0 savings" / "~580 mo sooner".
- Remove the arbitrary, mislabeled "Snowball plan" line (was extra applied only
  to the alphabetically-first bill) from the page + chart.
- Detect the 600-month cap (incl. 0% tiny payment) as "won't pay off".
- Correct the "set a minimum on the Snowball page" guidance (no such editor).

Clarity (D):
- Plain-language toasts on every write; targeted HelpCircle tooltips (Interest/
  Time Savings, Total Interest, Required Minimum, APR, Save-APR); a clearer page
  description; states that explain why + what to do.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:09:12 -05:00
null 1c04dfdf8c feat(brand): BrandMark/Wordmark/Lockup consume the new SVGs
Brand.tsx (101 line churn) and brand.ts (47 line churn) now expose
the new SVG-first primitives:

- BrandMark: <img> with BRAND.assets.mark (vector, scales to any size).
- BrandWordmark: text fallback if the mark is unavailable.
- BrandLockup: the combined mark + wordmark for the layout chrome,
  honours the light/dark variant via CSS variables.
- BrandGlyph: unchanged but re-uses the new accent tokens.

brand.ts adds the new asset-path constants (mark, markMono, lockup,
lockupLight, socialPreview) and a tokens helper so the lockup can
switch variants without prop drilling.

This is commit 2 of the v0.42.0 follow-up batch. Depends on
720d31b (SVG assets).
2026-07-05 17:50:04 -05:00
null ecd907e508 refactor: remove orphan StatusBadge.tsx (dead-code audit follow-up)
The top-level client/components/StatusBadge.tsx has no importers — the tracker's
components/tracker/StatusBadge.tsx (used by TrackerRow/MobileTrackerRow) is the
real one. Deferred from the previous cleanup because it had uncommitted brand-
refresh edits; those landed in b714715, and it's still dead, so it goes now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:22:49 -05:00
null 86733d457a refactor: remove verified dead code — orphan file, dead HOC, unused api wrappers
From the repo-wide dead-code audit (High-confidence, re-verified against the live
tree; 0 references each):
- Delete client/components/SummaryCard.tsx — orphan superseded by the tracker's
  components/tracker/SummaryCards.tsx (the one actually imported).
- Remove the unused withErrorBoundary HOC from ErrorBoundary.tsx (the
  ErrorBoundary component itself stays; it's used app-wide).
- Remove 17 dead api.ts client wrappers with no caller (spendingBudgets,
  upcomingBills, billAmortization, billMonthlyState, archiveBill,
  deleteBillTemplate, updateSnowballPlan, aboutAdmin, exportUrl, notifMe,
  saveNotifMe, profileExports, profileImportHistory, createManualTransaction,
  updateTransaction, deleteTransaction, transactionMerchantMatch).

Deliberately left: StatusBadge.tsx orphan (has uncommitted parallel edits — will
remove once that lands) and the WebAuthn feature (a build-or-bin product decision,
not dead-code cleanup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:21:31 -05:00
null 1807456da7 refactor(brand): adopt new primitives in admin + data + dialogs
The remaining per-file 2-4 line adjustments land here: the admin
cards (EmailNotifCard, UsersTable), the bank-sync section, and the
two cross-cutting dialogs (ReleaseNotesDialog, SearchFilterPanel)
adopt the same BrandGlyph / TonedCard / SectionHeading imports as
the rest of the app. Each file is independently buildable on top of
the previous 6 commits.

This is commit 7 of 9 in the v0.42.0 brand refresh. Depends on
0eb024d (pages).
2026-07-05 17:19:03 -05:00
null b714715ddb refactor(brand): adopt new primitives in tracker cards
CashFlowCard, OverdueCommandCenter, SummaryCards, TrackerBucket, and
the shared StatusBadge move to the new TonedCard / ToneDot / BrandGlyph
primitives. The biggest single change is SummaryCards (81 lines
churn) where the per-card tone tokens now defer to the design system.

This is commit 5 of 9 in the v0.42.0 brand refresh. Depends on
c2b6a7d (UI primitives).
2026-07-05 17:18:49 -05:00
null c2b6a7d1b4 refactor(brand): adopt new primitives in ui/{button,card,badge}
The three shadcn primitives get the small adjustments the new
design-token set calls for — shadow, ring, and border tokens that
align with the index.css overhaul, and tone helpers that defer to
the tokens the primitives export. 20 lines total, no behaviour
change: same variants, same API, same default sizes.

This is commit 4 of 9 in the v0.42.0 brand refresh. Depends on
36834e6 (layout chrome + index.css tokens).
2026-07-05 17:18:38 -05:00
null 36834e6625 refactor(brand): wire brand + new primitives through layout chrome
The layout chrome adopts the new brand module + the just-landed
app-primitives. CSS variables, the favicon, the page title, and the
vite PWA manifest all switch to the new brand assets:

- BrandBlock / NavPill / Sidebar / Layout use BrandMark / BrandWordmark
  / BRAND.name from @/lib/brand.
- ThemeContext exposes the system / dark / light options for the new
  'theme = System' setting (commit 1 of 9 in the brand refresh set;
  the actual settings UI lands in the per-page commit).
- client/index.css: 153 lines of churn — the design-token refresh
  (accent palette, neutrals, surface tokens) consumed by every
  downstream commit.
- index.html: title, favicon, apple-touch-icon, theme-color, manifest
  href all point at client/public/brand/*.
- vite.config.mjs: VitePWA manifest icons + theme/background colors
  follow the new brand.

This is commit 3 of 9 in the v0.42.0 brand refresh. Depends on
4898987 (app-primitives).
2026-07-05 17:18:31 -05:00
null 4898987a18 feat(brand): shared app-primitives UI kit
Adds the cross-cutting UI primitives the rest of the brand refresh
consumes. The 159-line file bundles the small, repeated bits every
page/dialog needs so the per-page commits can stay minimal:

- TonedCard: bordered card with neutral / good / warn / danger / info
  variants (border + bg + text per tone), consumed by the
  overdue/summary/bank-sync cards.
- ToneDot: small status dot for the new badges.
- EmptyState: lucide Inbox + heading + body used for filtered-empty
  and zero-data states.
- SectionHeading / FieldRow: small typographic primitives for the
  Settings / Profile / Admin pages.
- BrandGlyph (re-export of the Brand module) so consumers only need
  to import from '@/components/ui/app-primitives'.

This is commit 2 of 9 in the v0.42.0 brand refresh. Depends on
dfee7f9 (brand module); consumed by the layout/UI/consumer commits.
2026-07-05 17:18:23 -05:00
null dfee7f9401 feat(brand): new brand module + logo assets
Adds the canonical brand definition used by the v0.42.0 brand refresh:

- client/lib/brand.ts — single source of truth for name, tagline,
  repository URL, asset paths, navigation glyphs, and accent palette
  (BRAND_GLYPHS / accent tokens consumed by Brand.tsx + page chrome).
- client/components/brand/Brand.tsx — BrandMark (img with the
  logo asset), BrandWordmark, BrandGlyph, and a small BrandStack
  used by the layout chrome.
- client/public/brand/{logo,pwa-192,pwa-512,apple-touch-icon}.png —
  replaces the inline img-cut references in README/HISTORY with a
  canonical set; PWA manifest + favicon now point here.

This is commit 1 of 9 splitting the working-tree brand refresh into
per-layer commits. Each commit is independently buildable; the order
matches the dependency direction (lib → component → chrome → UI
primitives → page consumers → admin/dialog consumers → docs).
2026-07-05 17:18:04 -05:00
null 1f57b22ff8 feat(settings): configurable reminder send-time (plan Tier 5)
The daily reminder job ran at a hardcoded 6 AM. Make the hour configurable:
- dailyWorker: resolveReminderHour() reads the global 'reminder_hour' setting
  (clamp 0–23, default 6) as the single source for both the cron expression and
  the next-run display; the task is stored so rescheduleDailyWorker() can restart
  it live (defensively wrapped so a bad value can't take the worker down).
- notifications admin GET/PUT expose reminder_hour; PUT clamps, persists, and
  live-reschedules the worker.
- Admin Email Notifications card: a "Reminder send time" select (account-wide).

It's a global setting by design — there's one shared daily job, so a per-user
hour would require an hourly-worker refactor (out of scope) and would otherwise
be a dead setting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:10:25 -05:00
null 0fa80a77ef feat(settings): make Currency + Date-format real (plan Tier 2)
The Currency/Date-format dropdowns were decorative (nothing read them). Wire
them into the formatters, display-only (single active currency, no conversion):
- money.ts: activeCurrency/activeLocale read synchronously from localStorage at
  load (correct first paint), setMoneyFormat() write-through; formatUSD/
  formatUSDWhole/formatCentsUSD honor them. Default USD/en-US is byte-identical,
  so money.test + the probe stay green.
- utils.ts: fmtDate honors an activeDateFormat (MM/DD/YYYY | DD/MM/YYYY |
  YYYY-MM-DD) with setDateFormat() write-through.
- FormatSync (mounted in Layout) reconciles the server settings → localStorage/
  module and re-renders once only on a genuine cross-device divergence.
- Settings selects write through on change, currency list expanded (AUD/CHF/JPY/
  INR), "Regional" section, tooltips clarifying display-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:54:19 -05:00
null e2d4a9f703 feat(settings): theme "System" + reduce-motion (plan Tier 1)
- ThemeContext gains a 'system' theme that follows prefers-color-scheme and
  updates live when the OS flips; exposes resolvedTheme (consumed by Sonner) and
  keeps the <meta theme-color> in sync. Pre-bundle inline script in index.html
  prevents a light/dark flash. Both theme selectors (Settings ThemeCards +
  theme-toggle dropdown) gain the System option.
- New MotionPreferenceContext mounts framer-motion <MotionConfig>; a Reduce-motion
  toggle (localStorage device pref) forces reducedMotion="always" (else "user",
  which still respects the OS).
- Add an accessible SettingHelp tooltip (focusable button + aria-label) wrapped by
  a page-level TooltipProvider; used for the System + Reduce-motion explanations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:50:37 -05:00
null 21651b3f67 feat(tracker): a11y live regions + keyboard-shortcut hint (plan D/C5)
- aria-label on the pencil "Edit bill" buttons (were title-only), matching the
  autopay buttons' title+aria-label pattern.
- role="status" aria-live on the filter result count so filtering announces
  "N of M shown"; a visually-hidden live region announces drag/keyboard reorder
  ("Moved <bill> to position N of M").
- Header "Keyboard shortcuts" dropdown documenting the (previously invisible)
  j/k/arrows/Enter/p/Esc row shortcuts + ⌘K palette.

Deferred as low-value/optional: extra display toggles (C4) and a due-this-week
chip (C5) — avoided header/option clutter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:00:16 -05:00
null 0abe861928 feat(tracker): multi-select bulk pay/skip (plan C3)
- Add a leading selection checkbox to each desktop + mobile row, threaded from
  the page through TrackerBucket via an optional RowSelection prop (renders only
  when the page supplies a toggle handler).
- Page owns the selection Set; a sticky bottom action bar appears when ≥1 bill is
  checked: "N selected · Pay · Skip · Clear".
- Pay selected reuses the bulkPay item shape + Undo from "Pay all due".
- Skip selected fans out saveBillMonthlyState (no bulk endpoint; skip has no money
  effect) with Promise.allSettled, an aggregated "n skipped / m failed" toast, and
  an Undo that un-skips. Selection clears on month change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:56:34 -05:00
null 88874f3f3d feat(tracker): export/print the month + calendar link (plan C1/C2)
- Header "Export" menu: Export CSV (via the existing /api/export?from&to date
  range for the viewed month) and Print (window.print()). Extract the shared
  downloadFile blob helper to lib/download.ts (reused by DownloadMyDataSection).
- Add api.exportRangeUrl(from, to, fmt).
- Tracker-scoped @media print CSS: hide interactive chrome (.tracker-print-hide),
  keep the month title + summary + buckets, avoid splitting a row across pages.
- Header "Calendar" button deep-links to /calendar?year&month; CalendarPage now
  reads those params as its initial month (falls back to today).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:49:09 -05:00
null 03009e2a83 refactor(client): de-duplicate parseUtc + settingEnabled to lib/utils (plan A2/A3)
- Extract parseUtc (was copy-pasted in 4 files) and settingEnabled (3 copies,
  one named settingsBool) into client/lib/utils.ts as the single source.
- Replace all local copies with the shared import.
- Add tracker_show_safe_to_spend to the tracker's local settings defaults so it
  matches SettingsPage's list (behavior unchanged; aligns the two sources).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:38:12 -05:00
null 3973560e92 refactor(tracker): remove dead code and unreferenced files (plan A1)
- Delete unreachable in-file dialogs: MonthlyStateDialog block (setShowMbs
  never called) and inline PaymentModal in TrackerRow + MobileTrackerRow
  (setEditPayment only ever null; editing works via PaymentLedgerDialog).
- Delete fully-unreferenced files: tracker/EditableCell.tsx,
  ui/confirm-dialog.tsx, ui/separator.tsx (+ orphaned @radix-ui/react-separator).
- Simplify OverdueCommandCenter dead branch (early-returns null when empty).
- Correct stale roadmap/FUTURE docs: tracker keyboard nav is implemented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:35:13 -05:00
null dd5bf92fc1 fix(money): manual payment path flags suspected dupes (409) w/o losing legit ones (Track A)
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>
2026-07-05 11:51:06 -05:00
null 18eaec04e2 refactor(ts): SnowballPage → TypeScript (debt attack order + projections + plans)
Typed Bill[]/Projection/SnowballSettings; reused SnowballPlan and exported
ActivePlan from the plan components; React Query cache setters typed.
2026-07-04 23:12:57 -05:00
null b48a34ee33 refactor(ts): SubscriptionCatalogSection → TypeScript
Typed CatalogEntry/Descriptor/MatchedBill; branded Dollars on monthly_equivalent.
Completes all loose components/ conversions. typecheck 0, lint 0 errors, build green.
2026-07-04 22:12:49 -05:00
null 2f62d6383a refactor(ts): CommandPalette → TypeScript (typed Command + Bill results) 2026-07-04 22:09:36 -05:00
null d4eb48de92 refactor(ts): BillMerchantRules → TypeScript
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.
2026-07-04 22:08:05 -05:00
null 4d380c8da0 refactor(ts): BillHistoricalImportDialog + BillsTableInner → TypeScript
Branded Dollars on historical-import candidate amounts; typed BillPrefs +
MoveControls/DragProps on the bills table. typecheck 0.
2026-07-04 22:04:55 -05:00
null 41e847a33d refactor(ts): MobileBillRow, IncomeBreakdownModal, CalendarFeedManager → TS
Also removed a stale PayoffChart.jsx duplicate (its .tsx was already committed).
Branded Dollars on income transactions/bank-tracking balances. typecheck 0.
2026-07-04 22:01:49 -05:00
null 80fb0043b6 refactor(ts): convert ImportSpreadsheetSection to TypeScript — data/ dir complete
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.
2026-07-04 21:56:04 -05:00
null 0cc5cbd957 refactor(ts): convert BankSyncSection to TypeScript
SimpleFIN connect/sync/backfill/accounts + bank-budget-tracking + auto-categorize.
Branded Cents on account/tx amounts, Dollars on financial-account balances.
Dropped unused sourceId/bills props on AccountRow. typecheck 0, build green.
2026-07-04 21:46:55 -05:00
null 5a2e37fd61 refactor(ts): convert data/ import + matching sections to TypeScript
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.
2026-07-04 21:42:18 -05:00
null dfedb75e6d refactor(ts): convert data/ shared + small sections to TypeScript
dataShared (SectionCard/CountPill/importErrorState), DataNav, ConnectionHero,
DownloadMyData/Erase/SeedDemo/ImportHistory/AutoMatchReview/ImportOfx sections.
Branded Cents on OFX preview txns, Dollars on auto-match amounts; SectionCardProps
exported for section cardProps passthrough. typecheck 0, build green.
2026-07-04 21:33:25 -05:00
null a8496a9c64 refactor(ts): convert admin/ cards to TypeScript (AuthMethods, BankSync, Backup, ...)
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>
2026-07-04 21:26:22 -05:00
null c9b5f93569 refactor(ts): convert LoginModeCard + EmailNotifCard admin cards to TSX (B18)
LoginModeCard (AuthModeConfig; AdminUser[]) and EmailNotifCard (EmailConfig +
keyed set(k,v)). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:19:39 -05:00
null a9a58d8aa2 refactor(ts): convert CleanupPanel + UsersTable admin cards to TSX (B17)
CleanupPanel (CleanupForm/CleanupStatus/CleanupResult; keyed toggle array typed
[keyof CleanupForm,string][]). UsersTable (AdminUser type exported + reused by
AdminPage's users state; ResetForm; typed row handlers). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:17:16 -05:00
null ea531f06c7 refactor(ts): convert OnboardingWizard to TSX (B16b)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:45:53 -05:00
null 7f4ef20b7c refactor(ts): convert admin shared helpers + AddUserCard; drop dead PrivacyAdminCard (B16)
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>
2026-07-04 20:44:40 -05:00
null 9ad8cc7e8a refactor(ts): convert SearchFilterPanel, RecentlyDeletedBillsDialog, BillRulesManager to TSX (B15)
SearchFilterPanel (presentational props), RecentlyDeletedBillsDialog (Bill[],
days_left cast, formatUSD on Dollars), BillRulesManager (MerchantRule/RuleGroup,
reduce<Record<number,RuleGroup>> with `?? (acc[key]=…)` to satisfy
noUncheckedIndexedAccess). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:42:24 -05:00
null 6a9b8b3cb8 refactor(ts): convert transactions/ + snowball/ dirs to TSX (B14)
transactions/: CategoryPicker (Category[], Node-cast for outside-click),
MatchBillDialog (BankTransaction/Bill, advisory_filter cast). snowball/:
PayoffChart (TrackPoint chart geometry, asDollars for formatUSD), PlanHistory
Panel + PlanStatusBanner (SnowballPlan/ActivePlan types with Dollars-branded debt
balances; asDollars at fmt boundaries, ?? guards for optional-money comparisons).
typecheck 0, lint 0 errors, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:39:30 -05:00
null 73be95d12d refactor(ts): convert BillModal to TSX (B11) — bill-modal feature done
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>
2026-07-04 20:10:48 -05:00
null 31ff6adbd9 refactor(ts): convert bill-modal sub-components to TSX (B10)
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>
2026-07-04 20:03:09 -05:00
null 8a4b51915f refactor(ts): convert SummaryCard + ReleaseNotesDialog to TSX (B9)
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>
2026-07-04 19:54:40 -05:00
null 8937dc5e25 refactor(ts): convert app-wide common components to TSX (B8)
PageLoader, PageTransition, StatusBadge (top-level), MarkdownText (typed
renderInlineMarkdown, isValidElement for keys), and ErrorBoundary (typed class
component: ErrorBoundaryProps/State, getDerivedStateFromError, ErrorInfo;
generic withErrorBoundary<P> HOC). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:52:47 -05:00
null 609de2a946 refactor(ts): convert the app shell to TSX (B7)
App.tsx (routing + QueryClient), main.tsx (entry; index.html updated to
main.tsx), useAuth.tsx (typed AuthContextValue/User + MeResponse; uses the safe
useContext()||defaults pattern so no `never` trap), and the layout components
(Layout, Sidebar, NavPill, BrandBlock — NavItem type, LucideIcon-typed nav
config, casts for the untyped overdue/simplefin API responses). TS caught a dead
prop: App passed mainContentId to TrackerPage, which takes no params — removed.

typecheck 0, lint 0 errors (39 warns), build green, 17/17 e2e probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:49:57 -05:00