Commit Graph

159 Commits

Author SHA1 Message Date
null c91c97ef41 feat(tracker): live cross-query invalidation for the app shell (X3)
The app never called invalidateQueries; a Tracker mutation only refetch()'d
the one tracker query. So the sidebar overdue badge (['overdue-count'],
2-min staleTime), drift report, and bills list stayed stale after pay/skip/
edit — you could clear your last overdue bill and still see '3' for minutes.
Added useInvalidateTrackerData() (tracker + overdue-count + drift-report +
bills) and wired it into rows, BillModal.onSave, bank-sync, reorder, and the
payment/late-attribution handlers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:19:30 -05:00
null 836dbdb9ae fix(snowball): surface projection errors + polish (Snowball #2,#3,#8)
- The payoff projection panel swallowed fetch errors silently; now shows a
  "Couldn't load … Try again" state (no projection) and a subtle "showing the
  last result" retry banner when a refresh fails.
- loadProjection() now uses the currently-typed extra payment (via a ref that
  mirrors the input), consistent with the debounced live preview, so refreshing
  after a balance edit never drops an in-progress extra.
- Copy: extra-payment validation says "non-negative" (0 is valid); the capped
  banner now reads "one or more debts won't pay off at this rate" (accurate for
  the unpayable-debt case from the #1 fix, not just >50 years).

(#9 unsaved-preview hint was unnecessary — the input already auto-saves on blur.)
Build clean; client suite pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:08:41 -05:00
null d53a64b604 feat(data): "Erase my data" danger zone (Batch 5)
New services/userDataService.js eraseUserData() permanently wipes a user's
financial + imported data in one transaction (child → parent order for FK
safety): bills (+ cascading payments/monthly_bill_state/bill_history_ranges),
transactions/accounts/data_sources, categories/groups, templates, snowball,
spending rules/budgets, merchant rules, imports, and per-user hint tables. It
PRESERVES the account, sessions, 2FA/WebAuthn, login history and preferences —
this resets your data, not your account — then re-seeds default categories and
writes an audit row to import_history.

- POST /api/user/erase-data — rate-limited (demoDataLimiter), requires a
  type-to-confirm token ("ERASE"), structured errors.
- UI: EraseDataSection danger-zone card (Export & backups pane) — red-accented,
  "download a backup first" nudge, type-to-confirm AlertDialog, toasts; on
  success DataPage reloads all state.

Tests: tests/eraseUserData.test.js — wipes user A only, preserves user B +
account + session, re-seeds categories, audited. Server 139 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:21:07 -05:00
null bd1eee00b0 feat(import): OFX/QFX transaction import (Batch 3)
New services/ofxImportService.js parses OFX 1.x (SGML, unclosed leaf tags),
OFX 2.x (XML) and QFX (+ Intuit tags ignored) into the same normalized shape the
CSV path produces, then writes through the SAME shared primitives (session table,
(user_id, data_source_id, provider_transaction_id) dedupe, import_history) — now
exported from csvTransactionImportService (additive; CSV tests still pass).

- Routes POST /api/import/ofx/{preview,commit} mirror the CSV two-step (raw
  upload → structured commit; no column mapping since OFX is structured).
- UI: ImportOfxSection (upload → preview list → import) in the Import pane;
  amounts shown via formatCentsUSD; toasts on preview/commit/malformed.
- Gap handling: signed TRNAMT → signed cents; DTPOSTED → YYYY-MM-DD; FITID →
  stable provider id (hash fallback); non-OFX / empty files rejected clearly.

Tests: tests/ofxImportService.test.js (SGML + XML/QFX parse, entity decode,
signed cents, preview→commit, re-import dedupe, import_history). Server 129 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:11:59 -05:00
null c7b110cd68 feat(data): actionable badges, health dots, at-a-glance stats, palette links (Batch 2)
- Transactions nav shows a live "N to review" badge (unmatched count from the
  bank-ledger summary, limit:1 so it's cheap; refreshes on sync/import).
- Bank sync nav shows a green/amber/grey health dot (connected / needs-attention
  / off), mirroring the hero tone.
- Connection hero connected line now shows the transaction count at a glance
  ("SimpleFIN · 1,159 transactions · synced 2h ago · syncs automatically").
- Command palette gains Data section deep-links (Bank sync / Transactions /
  Import / Export) via ?section=.
- Count/stat fetch is non-blocking (.catch → 0), never blocks the page.

Build clean; client suite 46 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:05:25 -05:00
null 6a1b2f62b2 feat(data): two-pane shell + connection hero + deep-linking (Batch 1)
Rewrite the Data page shell into a settings-style two-pane layout (sticky goal
-nav on desktop, segmented on mobile) with:
- ConnectionHero — 5 states so a network blip is never mistaken for "not
  connected" and a server without SimpleFIN never shows a dead Connect button
  (loading / disabled / error+retry / not-connected / connected±needs-attention);
  Sync-now handles partial errors, 429, and failure with toasts.
- DataNav — <nav> landmark, aria-current, keyboard, responsive.
- ?section= deep-linking via useSearchParams (URL source of truth → localStorage
  → default; migrates the old 3-tab key), so refresh/back-button work.
- Goal-based regroup into 4 panes with plain-language titles/subtitles/icons
  passed via cardProps (every section component reused unchanged).
- Lazy panes: ImportSpreadsheet/ImportMyData code-split (own chunks) + only the
  active pane mounts; framer-motion cross-fade (reduced-motion aware);
  focus-to-heading on switch.
- Repoint BankTransactions "Open Data" → ?section=bank-sync; add /data to the
  authed axe sweep.

Build clean (heavy panes split into their own chunks); client suite 46 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:02:36 -05:00
null aace5a4356 feat(bills): "Recently deleted" restore view for the 30-day window (IMP-UX-01)
Bills soft-delete and are retained 30 days, but the only way back was the
transient "Undo" toast — dismiss it and a bill deleted an hour ago was
unrecoverable from the UI (even though the API and retention kept it).

- GET /api/bills/deleted lists soft-deleted bills still inside the recovery
  window, newest first, with days_left (declared before /:id). User-scoped.
- BillsPage shows a "Recently deleted (N)" button when any exist, opening a
  dialog to restore each one; restoring refreshes the active list too.
- The list fetch is non-blocking (never blanks the page); restore is
  try/catch + toast; dialog has empty and per-row busy states.

Tests: tests/billsDeletedRoute.test.js (window filter, ordering, days_left,
money serialization, user isolation). Server 116 pass; client 46; build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:56:45 -05:00
null a15f00c568 refactor(client): single source of truth for money formatting (IMP-CODE-01)
The client had ~15 hand-rolled currency formatters (local `fmt`/`money`/
`fmtFull`/`fmtDollars`/…) plus a canonical `fmt` in lib/utils used at ~190
sites — same rules copy-pasted, with inconsistent handling of negatives,
whole-dollar, cents vs dollars, and blank input.

Add client/lib/money.js as the one implementation:
  - formatUSD(dollars)      — "$1,234.56" (whole/dash options)
  - formatUSDWhole(dollars) — "$1,235"
  - formatCentsUSD(cents)   — from integer cents; signed "+/-" and dash options
Inputs are coerced so null/''/NaN never render as "$NaN", and -0 is
normalized so it never shows as "-$0.00".

lib/utils.fmt now delegates to formatUSD (byte-identical — the existing
utils.test.js fmt suite is the regression guard), and the 15 local
formatters delegate to money.js. No display currency formatting remains
outside money.js; the /100 conversions left behind are calculations
(form prefill), not display.

Tests: client/lib/money.test.js (13). Full client suite 46 pass; build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:46:22 -05:00
null 2963d11d1b fix(qa): version check is opt-out-able (QA-B16-01)
- updateCheckService: gate the external request on `update_check_enabled`
  (default on); when off, no network call, returns { disabled: true }
- aboutAdmin: GET/PUT /update-check-setting (admin-only) to toggle it
- StatusPage: a Switch on the admin System Status card to enable/disable
- privacy.js: state that an admin can disable it (was called "optional" with
  no actual opt-out)
- tests/updateCheckOptOut.test.js: proves no external fetch when disabled
- docs: archive QA-B16-01, B16 

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:05:37 -05:00
null 98c8fab176 fix(qa): resolve a11y nested-interactive on Categories & Snowball rows
- CategoriesPage: category rows are now a plain container with a dedicated
  chevron toggle button, instead of role=button rows nesting action buttons
- PlanStatusBanner: split the collapsible header into a name/progress toggle,
  sibling action buttons, and a chevron toggle (actions no longer nested in the
  trigger button)
- add e2e/categories.spec.js expand regression; all 8 authed pages now pass axe
- docs: archive QA-B14-02 to HISTORY v0.41.0; QA plan status/cycle-log

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:02:15 -05:00
null 029c227685 fix(qa): seed demo data amounts, bill amount validation, negative USD format, a11y aria-labels, Playwright E2E setup (batch 0.41.0 QA) 2026-07-02 20:36:09 -05:00
null 35e5d185de feat(spending): category groups, YNAB-style spending page overhaul, 3-month averages, cover overspending (batch 0.41.0) 2026-06-14 19:21:34 -05:00
null 81ddcb5fc1 feat(banking): bank transactions page with merchant/store matching, transaction matching refactor, bank sync improvements (batch 0.40.0) 2026-06-14 15:15:31 -05:00
null ee7026872c feat(banking): bank transactions ledger page with route, sidebar link, and API endpoint 2026-06-12 03:59:42 -05:00
null d9a441dff6 feat(settings): auto-save preferences with live save status (batch 0.39.0)
Replace all Save buttons on the Settings page with debounced auto-save:

- useAutoSave hook: debounce with latest-payload-wins, flush() for blur,
  pending-edit flush on unmount, status machine (idle/saving/saved/error)
  with saved fading back to idle. Covered by 6 Vitest tests (fake timers).
- SaveStatus pill (framer-motion) in the page header and notification card
  headers — Saving…/Saved/Save failed.
- Timing per control: toggles/selects/channel ~150-400ms; typed inputs
  (email, URLs, grace period, drift pct) 900ms + flush on blur.
- Push token never auto-saves mid-type: saves on blur only, so a partial
  token can never overwrite a working one.
- Notification cards no longer refetch parent settings on save (would
  clobber in-flight edits under auto-save).
- Decision: no undo toast — settings are non-destructive and instantly
  re-editable; undo would add noise without safety.
- vitest include now picks up .jsx tests; jsdom + @testing-library/react
  added as devDependencies.
2026-06-12 02:08:42 -05:00
null 8ef794a94a feat(settings): safe-to-spend toggle, move notifications from Profile to Settings, fix dark-mode readability 2026-06-12 01:52:48 -05:00
null dc49eb9633 feat(cashflow): safe-to-spend projection with timeline, vitest setup, package upgrades 2026-06-12 01:32:28 -05:00
null d0835b86ab chore(cleanup): remove legacy/public HTML files, retire /legacy route, update docs and About page 2026-06-11 23:50:27 -05:00
null c6708982a9 fix(utils): extract localDateString to shared lib, replace .toISOString().slice() pattern across client 2026-06-11 23:40:22 -05:00
null ca514e5f26 fix(tracker): BillModal save/close race and pending badge logic
- Use controlled Dialog state (setDialogOpen) instead of immediate onClose()
  to let Radix cleanup properly before unmount
- Amber 'Pending' badge now only shows for bank-linked bills — unlinked
  bills skip the pending-cleared check and show 'Paid' directly
- TrackerPage onSave no longer nullifies edit state before BillModal can
  animate closed

(batch 0.37.4)
2026-06-08 16:33:48 -05:00
null 626459322f fix(tracker): live sync label truncation and due_day fallback on partial update
- Shorten 'Live Sync' label to 'Live' for space-constrained layouts
- Add existing bill due_day fallback in validateBillData to prevent
  spurious required-field errors during partial PATCH updates

(batch 0.37.2)
2026-06-08 12:24:51 -05:00
null 426b0fd932 fix(admin): admin/profile routes and services 2026-06-07 21:18:02 -05:00
null 31be51e77f fix(bank-sync): admin config, matching, and worker updates 2026-06-07 19:41:17 -05:00
null 68aa5eff31 fix(snowball): plan history and bill modal updates 2026-06-07 19:13:16 -05:00
null 3f93a7dca2 fix(tracker): page update 2026-06-07 18:38:05 -05:00
null 9354af8cb8 fix(tracker): tracker page adjustments 2026-06-07 17:50:17 -05:00
null f2f9ad83ac fix(tracker): search filter and bucket improvements 2026-06-07 17:33:31 -05:00
null f7ad1c1ebb fix(tracker): table columns and settings improvements 2026-06-07 17:23:14 -05:00
null 71e783a799 fix(ui): calendar settings improvements 2026-06-07 16:52:50 -05:00
null 13e41aec74 feat: iCal feed for bills (Apple/Google calendar export) 2026-06-07 15:53:46 -05:00
null ec7869abbc feat: framer-motion page transitions and UI polish 2026-06-07 15:14:09 -05:00
null e1082145ab feat: tracker payment flow and mobile row improvements 2026-06-07 14:49:39 -05:00
null 6d60eebe1a chore: dependency updates and UI fixes (batch) 2026-06-07 14:23:19 -05:00
null 3b0f267ab3 fix: SubscriptionsPage polish 2026-06-07 02:06:31 -05:00
null 4f5a3d0cff feat: bank sync section, data sources route, subscription page updates, package updates 2026-06-07 02:03:00 -05:00
null d9cf499dba feat: search filter panel component, search preference persistence, page integration 2026-06-07 01:28:35 -05:00
null ab5e3fbf1f feat: profile settings UI, auth service refactor, schema migration, route tests 2026-06-07 01:17:49 -05:00
null 1ebb2da50a feat: tracker bucket rollover logic, utils cleanup, HISTORY update 2026-06-07 00:41:07 -05:00
null 8c2ecdb313 fix: subscription service error handling, SubscriptionsPage cleanup 2026-06-07 00:11:00 -05:00
null be95910ac2 feat: admin UX cleanup, bills page reordering, subscriptions page cadence sort + in-place edits, summary polish 2026-06-06 23:53:53 -05:00
null 88cb9d5340 feat: summary overdue highlighting, tracker row visual polish, bill table cleanup 2026-06-06 23:29:34 -05:00
null bb04966bbc fix: historical import loading state, remove stray catalog export 2026-06-06 23:17:08 -05:00
null 4dd01c13c4 feat: live transaction search in merchant rules, link-import preference toggle, tracker row tweaks 2026-06-06 23:04:53 -05:00
null 12d869d400 feat(subscriptions): cadence sort toggle, in-place bill edits after save 2026-06-06 22:25:58 -05:00
null 83e6afa9e6 feat(subscriptions): simplified SubscriptionsPage, inline actions, improved matching card, Service Catalog route
- Extracted known-service catalog to dedicated /subscriptions/catalog route
- Simplified main Subscriptions page to focus on tracked services + bank-backed recommendations
- Replaced inline Pause/Resume with Edit + MoreHorizontal dropdown on subscription rows
- Added 'Improve Matching' card linking to Service Catalog
- Vite proxy respects API_PORT env var for dev flexibility
- Added top_200_us_subscriptions_researched dataset
- Updated HISTORY.md with v0.35.0 changes
2026-06-06 22:09:34 -05:00
null a1e6a308cf feat: existing bill matching in recommendations, feedback tracking, broad-merchant rejection, annual price detection 2026-06-06 21:15:08 -05:00
null 422d8550bb feat: recommendation detail dialog with evidence, ambiguity badges, transaction list 2026-06-06 21:05:01 -05:00
null b2f8f5ef66 feat: dedicated subscription catalog page, evidence badges, price display in recommendations 2026-06-06 20:44:54 -05:00
null 3a034ddeb7 feat: subscription catalog with bank descriptors, custom per-user descriptors, catalog→bill linking 2026-06-06 20:02:13 -05:00
null 04f5f922b7 chore: remove stale tracked backup/DB files; add /bills.db to gitignore 2026-06-06 17:01:06 -05:00