Commit Graph

212 Commits

Author SHA1 Message Date
null 562da73a76 Polish product artwork and branding 2026-07-10 19:10:42 -05:00
null 522eaaf101 chore(deps)!: tailwind 3 -> 4 via @tailwindcss/vite (4i)
Official codemod (run in a clean worktree; docker-owned data/ blocks it
in-tree): tailwind.config.js inlined into client/index.css as CSS-first
@theme/@utility (config file deleted — v4 auto-detects content, which
also retires the .tsx-content-glob failure class from the TS migration),
33 template files' class renames applied, tailwindcss-animate loads via
the v4 @plugin bridge. Build runs through @tailwindcss/vite; postcss
config is now plugin-free. tailwind-merge -> 3 (v4 class model).

Visual-regression gate: only diffs were a ~2px global preflight shift +
the version string (baseline predated 0.41.1); re-baselined both login
screenshots in this commit after review. webauthn.probe confined to the
probe project (it enables WebAuthn for the seeded user, racing the
parallel UI projects' password logins).

e2e 27 passed, probe 18/18, PROD SMOKE PASS, typecheck/lint/tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:03:15 -05:00
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