Commit Graph

189 Commits

Author SHA1 Message Date
null 60848964b6 fix(money): make every payment balance-mutation atomic (Track A)
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>
2026-07-05 11:44:45 -05:00
null 73631ab812 fix(tracker): route error handling + autopay write atomicity (T2)
routes/tracker.js had no try/catch and returned a plain {error} shape; the
three GET handlers now wrap in try/catch + standardizeError (including the
invalid-month path). applyAutopaySuggestions ran INSERT + applyBalanceDelta
as two un-transactional steps on a GET — wrapped both in one db.transaction.

Tests: autopay creates one payment + drops balance (idempotent), route
returns standardized error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:21:28 -05:00
null d689ff6e68 fix(tracker): quick-pay duplicate guard + atomic balance write (X1)
POST /payments/quick had no dedupe and non-atomic INSERT+applyBalanceDelta,
unlike /payments/bulk. A double-click/retry/two-tab pay created a second
payment and dropped the balance twice; a mid-way failure left a payment with
no balance adjustment. Now checks the bill_id+paid_date+amount composite key
(idempotent 200) and wraps the write in db.transaction.

Test: tests/paymentsQuickRoute.test.js

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:05:19 -05:00
null 2b315f5d18 refactor(snowball): consolidate plan endpoints, standardize errors, fix N+1 (Snowball #4,#6,#7)
- The four plan-lifecycle routes (pause/resume/complete/abandon) were near
  -duplicate copies returning a plain {error} shape; folded into one
  transitionPlan(req,res,{allowedFrom,setSql,action,past}) helper that returns
  standardizeError {message, code}, keeps the state guards and ownership scoping.
- Standardized the remaining plan endpoints' error responses (start/list/active/
  patch) to standardizeError too.
- enrichPlanWithProgress fetched each snapshot bill one-by-one and wasn't user
  -scoped; now a single WHERE id IN (…) AND user_id = ? batch.

Test: tests/snowballPlanRoute.test.js (transitions, INVALID_PLAN_STATE guard,
ownership 404, dollar-denominated current_debts). Server 154 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:04:31 -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 314e4ff45e feat(export): JSON export + date-range/format payments export (Batch 4)
- GET /api/export now accepts a date range (?from=&to= on paid_date) in addition
  to ?year=, for CSV or JSON; filename derived from the range. Validates the
  range (both bounds, from<=to).
- New GET /api/export/user-json — full portable JSON of the user's data, reusing
  the same getUserExportData assembly as the SQLite/Excel exports (money via
  fromCents).
- UI (DownloadMyDataSection): a JSON export card plus a "Payments export" with
  From/To dates and a CSV/JSON toggle; shared blob-download helper; toasts and
  client-side range validation.

Tests: tests/exportRicher.test.js (JSON assembly in dollars, year vs range
filtering, CSV filename, bad-range rejection). Server 134 pass; build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:15:36 -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 fa2432265c refactor(match): one canonical writer for transaction match state (IMP-CODE-03)
match_status, matched_bill_id and ignored must move together, but they were
updated by copy-pasted inline UPDATEs across six routes/services — exactly how
they drift apart (QA-B5-04 left match_status='matched' with a NULL bill).

Add services/transactionMatchState.js (markMatched / markUnmatched / markIgnored,
each ownership-scoped, returning rows changed) and route the six single-
transaction transitions through it: matchTransactionToBill, unmatchTransaction,
ignoreTransaction, unignoreTransaction (transactionMatchService), the match/
unmatch handlers (routes/matches), and unmatch-on-payment-delete (routes/
transactions, routes/payments).

Guarded bulk auto-match sweeps (subscription tracking, merchant-rule matching,
historical import) and the retention purge intentionally keep their own queries
— their WHERE clauses carry idempotency guards (AND match_status='unmatched')
the simple helper must not silently drop.

Test: tests/transactionMatchState.test.js (transitions + ownership scoping).
transactionMatchService/subscriptionService regression suites still pass;
server 122 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 13:02:10 -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 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 5ffe2db85a test(qa): export→import round-trip preserves money (B9 data integrity)
- extract buildUserDbExportFile() from routes/export.js so the SQLite user-DB
  export is testable (route behavior unchanged)
- tests/exportImportRoundTrip.test.js: export user A (bill/payment/override) →
  import into fresh user B → assert all money survives exactly in cents. Confirms
  the export(fromCents)/import(toCents) conversion is symmetric — no 100x drift —
  and guards it from regressing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 22:26:49 -05:00
null 819cfdfa9f fix(qa): bank-tracking unpaid_this_month gates by occurrence (QA-B5-02)
- routes/summary buildBankTracking: fetch unpaid candidates and filter by
  resolveDueDate in JS so annual / off-month quarterly bills don't inflate the
  SimpleFIN "unpaid this month" metric (completes the occurrence-gating family)
- add tests/summaryBankTracking.test.js (isolated route test)
- docs: archive QA-B5-02; Active Findings Log now empty (0 open)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:41:33 -05:00
null a15ff056b3 fix(qa): Summary excludes bills not due in the month (reconciles with Tracker)
- routes/summary: filter the expense list by resolveDueDate so annual and
  off-month quarterly bills no longer inflate the monthly total / "monthly
  result" — the Summary now agrees with the Tracker for the same month (QA-B5-01)
- add a Tracker<->Summary reconciliation guard in e2e/api.probe.spec.js
- docs: archive QA-B5-01; track QA-B5-02 (SimpleFIN unpaid_this_month residual)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:19:35 -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 d6639f1385 feat(money): cents migration stage 2 — schema flip to integer cents (batch 0.38.4) 2026-06-11 20:12:31 -05:00
null bf66ab1ee6 feat(money): migrate services to cent-exact money.js helpers (batch 0.38.3) 2026-06-10 20:14:13 -05:00
null 947fa3bdf8 feat(auth): add per-username rate limiter, migrate dates to local-time utils (batch 0.38.1) 2026-06-10 19:42:51 -05:00
null 38c8bbd472 feat(server): add trust proxy, CSRF HTTPS detection, error formatting, dates util (batch 0.38.0) 2026-06-10 19:37:19 -05:00
null f3bcf6cdec fix(api): transaction matching logic improvements (batch 0.37.5) 2026-06-08 21:07:42 -05:00
null 426b0fd932 fix(admin): admin/profile routes and services 2026-06-07 21:18:02 -05:00
null 79b51b1c9a fix(bank-sync): transaction matching, services, and worker updates 2026-06-07 20:07:27 -05:00
null 31be51e77f fix(bank-sync): admin config, matching, and worker updates 2026-06-07 19:41:17 -05:00
null 13e41aec74 feat: iCal feed for bills (Apple/Google calendar export) 2026-06-07 15:53:46 -05:00
null e1082145ab feat: tracker payment flow and mobile row improvements 2026-06-07 14:49:39 -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 ab5e3fbf1f feat: profile settings UI, auth service refactor, schema migration, route tests 2026-06-07 01:17:49 -05:00
null 6811eb8be5 feat: payment accounting service, SQL schema + migration, backend route refactor, test updates 2026-06-07 01:05:48 -05:00
null 88cb9d5340 feat: summary overdue highlighting, tracker row visual polish, bill table cleanup 2026-06-06 23:29:34 -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 a1e6a308cf feat: existing bill matching in recommendations, feedback tracking, broad-merchant rejection, annual price detection 2026-06-06 21:15:08 -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 fc3709337c feat: bulk unmatch — review similar payees, batch-undo with optional rule removal 2026-06-06 19:15:06 -05:00
null 7455dff5b8 feat: v0.37.0 — auto-learn merchant rules, ambiguous match protection, session hashing, geolocation opt-in 2026-06-06 18:30:21 -05:00
null b4779c9eda feat: auto-match review panel with undo — last 7d provider_sync payments 2026-06-06 17:34:09 -05:00
null 9a2a7ecdee feat: v0.94 — session token hashing, geolocation opt-in privacy setting 2026-06-06 17:00:22 -05:00
null 840620efe2 feat: v0.93 — stable provider keys, per-payment interest tracking with once-per-month gating 2026-06-06 16:34:20 -05:00
null a2ac241cd3 refactor(sync): centralize sync constants in bankSyncConfigService, wire through config/UI 2026-06-06 15:51:56 -05:00
null a73d0afe07 feat(encryption): support TOKEN_ENCRYPTION_KEY env var with startup migration 2026-06-06 15:27:45 -05:00
null 80b5d56010 feat(sync): rate limit sync/backfill endpoints to 10 per 15 minutes 2026-06-06 15:08:33 -05:00
null 99abca9868 security: WebAuthn / FIDO2 hardware security key 2FA 2026-06-05 22:05:23 -05:00
null 2c9cc37593 feat: copy last month budgets, monthly income section on summary page 2026-06-04 21:57:42 -05:00
null 3a19303d4d fix: SQLite timestamp timezone ambiguity — convert to proper UTC ISO strings 2026-06-04 21:42:34 -05:00
null df9d6fbf6d fix: backup enabled setting key mismatch 2026-06-04 21:35:02 -05:00
null 3623cadcf6 feat: income breakdown modal with ignore/restore, summary chart click, includeIgnored query param 2026-06-04 21:19:25 -05:00
null 910febae63 feat: bill rules manager page, merchant re-normalization, match suggestion scoring fix, cleanup pruning 2026-06-04 20:45:11 -05:00
null 743379fc94 feat: spending toggle per category, empty state, income query, auto-enable on rule creation 2026-06-04 20:01:51 -05:00
null 3f0078b930 feat: spending income section, rules manager, error handling improvements 2026-06-04 19:53:38 -05:00
null ac5d6c6625 feat: spending tracking page with category breakdowns 2026-06-04 04:31:25 -05:00
null 653dd72e12 feat: TOTP 2FA for login & profile setup flow 2026-06-04 04:10:14 -05:00
null a6b2e8bb87 fix: login mode card update, OIDC service improvements, auth middleware refinements 2026-06-04 03:53:38 -05:00
null 26b6fb13e5 feat: login history with geolocation, encryption, new device alerts, session detection 2026-06-04 03:38:32 -05:00
null 3745ef79b7 docs: update HISTORY.md with bank tracking, merchant rules, and UI changes 2026-06-04 02:48:32 -05:00
null 5689fc95c2 feat: historical import batch selection UI, backend endpoint, DB migration v0.83 2026-06-04 02:36:36 -05:00
null 19421c06fc fix: historical import backend routes and service adjustments 2026-06-04 02:20:30 -05:00
null d2d3045afe feat: historical payment import dialog for bank merchant rules 2026-06-04 02:05:15 -05:00
null ccdd16a626 fix: summary route and trackerService refinements 2026-06-04 01:27:00 -05:00
null da4642dbd0 feat: late-attribution prompt for bank payments that crossed month boundary 2026-06-04 00:06:16 -05:00
null 278521a612 fix: bank matching returns bill names, reactive Sync button in BillModal, error handling in merchant rule service 2026-06-03 23:29:30 -05:00
null b81b41d302 security: rename LIVE constant to SQL_NOT_DELETED with injection safety documentation 2026-06-03 22:28:46 -05:00
null ff7ae8b3ab perf: optimize bills list query, add merchant rule composite index (v0.81) 2026-06-03 22:25:30 -05:00
null 4188a2059d fix: mortgage and housing categories auto-detected as debt in snowball 2026-06-03 22:19:58 -05:00
null b7c855e570 fix: imported payments now update debt balance via balance_delta and current_balance 2026-06-03 22:16:51 -05:00
null 37cf24f5a0 feat: Sync Bank button on tracker that runs merchant rule matching on all connected sources 2026-06-03 21:59:50 -05:00
null c353dd9f40 fix: remove client-side snowball projection, delegate to server with ?extra=N
- Delete 86-line computeLiveProjection() — drift risk eliminated
- GET /api/snowball/projection now accepts ?extra=N for unsaved amount preview
- Client uses debounced useEffect calling server instead of useMemo duplicate
2026-06-03 21:50:29 -05:00
null 36f7191289 feat: push notification channels (ntfy/Gotify/Discord/Telegram) and cash flow projection
- Wire four push channels into runNotifications() with urgency mapping
- push_url and push_token encrypted at rest via AES-256-GCM
- Profile page Push card with master toggle, channel picker, test button
- Calendar CashFlowCard with period/month projections and negative alert
- Tracker card shows projected amount when cashflow data available
2026-06-03 21:43:54 -05:00
null a0fe7880df feat: bill bank matching rules with pattern preview, conflict detection, retroactive payment sync
- Merchant rules link bills to bank transaction patterns for auto-import
- Live preview badge shows match count as user types merchant name
- Inline conflict warning if another bill already owns that pattern
- Retroactive sync on save — imports historical payments immediately
- Green Bank chip on bill list items with active rules
- New endpoints: GET/POST/DELETE merchant-rules + GET preview
2026-06-03 21:21:38 -05:00
null 690a86611a feat: SimpleFIN bank budget tracking with live balance, pending payments, bank tracking mode
- Opt-in Bank Budget Tracking mode replaces manual starting amounts with live bank balance
- Calendar money map shows Balance / Pending / Unpaid Bills / After Bills in bank mode
- Pending badge (amber) on tracker rows within configurable pending window (0-7 days)
- New GET /api/data-sources/accounts/all endpoint for account picker
- Tracker starting-amounts card shows account name and live balance hint
2026-06-03 21:09:26 -05:00
null 44320a7613 fix: validate all snowball order rows upfront, reject invalid ones with 400
PATCH /api/snowball/order silently skipped rows with bad ids or invalid
snowball_order values via bare 'continue' — no feedback, partial updates.
Now validates every item before touching the DB, returning 400 on the first
bad entry. Also adds deleted_at IS NULL filter so soft-deleted bills are
skipped instead of updated silently.
2026-06-03 20:48:24 -05:00
null e41f413f61 fix: pass ramseyMode explicitly to getDebtBills to avoid stale DB reads 2026-06-03 20:46:32 -05:00
null 36a65156e3 feat: merge pipeline workflow into bill-tracker (batch v0.36.0)
- Copy pipeline-report.py from Pipeline project into scripts/
- Update TOOLS.md and MEMORY.md to reflect workflow consolidation
- (includes all uncommitted v0.36.0 changes from prior session)
2026-06-03 20:28:37 -05:00
null e4f1f58730 feat: Roadmap pulls from Forgejo issues (v0.35.1) 2026-05-31 19:37:01 -05:00
null 9f27775da9 oidc error correction 2026-05-31 16:08:24 -05:00
null 67ce59db50 v0.35.0 2026-05-31 15:52:50 -05:00
null 31bafb0e55 0.34.3 2026-05-31 15:06:10 -05:00
null 90cfed035b feat: Payoff Custom mode, Summary reordering, unifed billing schedule, SimpleFIN + backup fixes (batch v0.34.1.3) 2026-05-30 21:20:51 -05:00
null c23cae1107 feat: reordering across management pages (Bills, Subscriptions, Categories, Snowball) — batch v0.34.1.2 2026-05-30 20:04:50 -05:00
null 35d0cbf8be chore: reset tracked db file 2026-05-30 17:27:15 -05:00
null 5449427b86 Add persistent bill reordering 2026-05-30 16:13:37 -05:00
null 5182754e0f feat(tracker): price-change drift detection with amber insight panel
Detects when a bill's recent payments have diverged from its configured
expected amount for 2+ consecutive months and surfaces it in a new
collapsible amber panel on the Tracker page.

- Migration v0.71: adds `drift_snoozed_until` to bills and
  `notify_amount_change` to users
- New `driftService.getDriftReport()`: computes per-bill payment median
  over last 3 months, flags drift above a user-configurable threshold
  (default 5%, minimum $1 delta)
- New `GET /api/bills/drift-report` and `POST /api/bills/:id/snooze-drift`
  routes (registered before `/:id` to avoid routing conflict)
- `runDriftNotifications()` added to daily worker — sends amber digest
  email per user listing all changed bills with old → new amounts
- `notify_amount_change` wired through profile and notifications routes
- `DriftInsightPanel`: collapsible amber panel with per-bill
  strikethrough old → new amount, ±% badge, TrendingUp/TrendingDown
  icons, "Update to $X.XX" (with undo toast) and "Dismiss" (30 days)
  actions; teal palette for price decreases
- `drift_threshold_pct` setting added to SettingsPage Billing Behavior
- "Notify on price changes" toggle added to ProfilePage notifications

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 14:33:55 -05:00
null 3978507572 feat(tracker): overdue command center with snooze/skip/pay + sidebar badge
- migration v0.70 adds snoozed_until TEXT to monthly_bill_state
- trackerService: snoozed_until in monthly state fetch + getOverdueCount()
- GET /api/tracker/overdue-count endpoint
- PUT /bills/:id/monthly-state validates snoozed_until
- OverdueCommandCenter component: collapsible, per-bill actions, hides snoozed
- useOverdueCount hook (2-min stale, 5-min poll, tab-only)
- Sidebar/nav uses overdue count badge on Tracker menu item
- Bump v0.33.8.7 → v0.34.0
2026-05-30 13:19:09 -05:00
null a15436b637 feat: 90-day backfill + auto-seed + status page redesign (batch 0.33.8.4)
- New backfillDataSource export and POST route for 89-day history pull
- Auto-seed 89 days on first connect, 30 days for routine syncs
- sinceEpoch() replaced with sinceEpochDays(days) with explicit param
- Status page errorRow now filters AND status = 'error'
- Full status page redesign: colored top borders, card icons, section labels,
  health banner with glowing dot, consistent spacing
- Bump v0.33.8.3 -> v0.33.8.4
2026-05-29 19:58:52 -05:00
null 0f9f48e255 feat: subscription badges + status improvements + dailyWorker fix (batch 0.33.8.3)
- Sub badge (indigo) in all 4 locations, toggleable in Bills prefs
- SimpleFIN Sync card on Status page
- dailyWorker.start() now called on startup
- tracker.overdue_count uses real SQL query
- Status page accuracy: dynamic headers, Degraded state, worker last_error check
- Removed SimpleFIN prefix from Recommendations title
- Bump v0.33.8.2 -> v0.33.8.3
2026-05-29 19:21:46 -05:00
null b34e21d1ba feat: advisory non-bill transaction filter system (batch 0.33.8.0)
- Migration v0.68: seeds advisory_non_bill_filters (5k+ patterns) and
  advisory_bill_like_overrides (83 override terms) on first startup.
  Idempotent — skips if already seeded.
- advisoryFilterService.js: lazy in-memory cache checks override terms
  first, then scans patterns. Returns null | {confidence, category, rationale}.
- Transaction list: each row gets advisory_filter from the server.
- High-confidence unmatched transactions: show 'Probably not a bill'
  italic text instead of 'No bill linked'.
- MatchBillDialog high confidence: 'Create Bill' replaced with
  'Probably not a bill · create anyway' text link for manual override.
- MatchBillDialog medium confidence: Create Bill button renders muted.
- Same logic in empty-state CTA when search returns no results.
- BillModal onSave now returns the saved bill so callers can auto-match.
- Bump v0.33.7.3 -> v0.33.8.0
2026-05-29 18:06:12 -05:00
null 32f1568515 feat: SimpleFIN payment backfill button on subscription bills (v0.33.7.2) 2026-05-29 04:19:20 -05:00
null 6b30ee4eb7 feat: merchant rules, auto-match on sync, duplicate API fix
- Removed duplicate unmatchTransaction API entry in api.js
- Unmonitored accounts: no chevron, click-to-expand disabled, tx panel hidden
- matched_bill_name included via LEFT JOIN bills in accounts query
- BillPickerDialog resets search/selection on open
- Link to bill: marks historical txs matched, stores merchant rule,
  applyMerchantRules catches other unmatched txs from same merchant
- Track (new subscription): creates bill with is_subscription=1, stores
  merchant rule for ongoing tracking
- SimpleFIN sync: applyMerchantRules runs after tx insert, auto-matches
  by merchant rule with payment_source='auto_match'
- Auto-match payments have transaction_id set, treated same as manual matches
- New services/billMerchantRuleService.js for rule storage and matching
- Migration for bill_merchant_rules table
2026-05-29 03:38:48 -05:00
null eeb26ccab1 feat: manual match/unmatch transactions to bills
Backend:
- POST /api/matches/confirm — atomic payment creation + transaction match
- POST /api/matches/:transactionId/unmatch — soft-delete payment, reset transaction
- Account transactions include matched_bill_id and matched_bill_name

Frontend:
- Unmatched transactions show + match pill button
- BillPickerDialog with transaction details + searchable bill list
- Confirm creates payment and updates row immediately
- Matched transactions show Unlink icon to remove match
- Toast on success with bill name and date
2026-05-29 03:02:36 -05:00
null c43c476ae9 fix: subscription recommendation dedup and amount-bucket grouping
- Amount-bucket grouping ensures consistent charges are grouped together
- Catalog lookup names and boosts the result
- Deduplication ensures one recommendation per known service
- Removed catalog-first rewrite
2026-05-29 02:51:30 -05:00
null 1d8ae4f511 fix: sync_days hard-clamped to 90 (SimpleFIN Bridge limit)
- bankSyncConfigService: SYNC_DAYS_MAX=90, getBankSyncConfig clamps on read,
  setSyncDays rejects >90 with explanation
- bankSyncService: every sync requests full sync_days window, dedup handles
  already-seen transactions
- dataSources status endpoint returns sync_days alongside enabled
- BankSyncAdminCard: input max 90, live clamp, description cites Bridge limit
- BankSyncSection: third stat tile showing History window X days
2026-05-29 02:23:19 -05:00
null 7682758aa8 fix: sync_days now reads from DB config, admin UI controls it
- bankSyncService: removed local syncDaysBack() reading env directly;
  sinceEpoch() now calls getBankSyncConfig().sync_days
- bankSyncConfigService: added setSyncDays() with 1-730 day validation
- routes/admin: PUT accepts sync_days alongside enabled/sync_interval_hours
- BankSyncAdminCard: Transaction history days input, loaded from config,
  defaults 90, dirty-checked on save
2026-05-29 01:33:54 -05:00
null 262d7789db feat: account monitoring, expanded sync UI, match filtering, error toasts
Backend:
- v0.64 migration: monitored column on financial_accounts
- GET/PUT data-sources accounts endpoints for monitored toggle + tx listing
- matchSuggestionService: excludes unmonitored accounts from match scoring

Frontend:
- BankSyncSection rebuild: accounts panel with monitored switch, expand for
  last 50 transactions, match status badges, optimistic toggle
- TransactionMatchingSection: toast on bills load failure
- DataPage: toast on import history load failure
- ProfilePage: toast on both login history fetch failures
2026-05-29 01:06:20 -05:00
null 542ab5e382 feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
  (DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both

#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
  score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch

#4 Encryption note in BankSyncAdminCard below worker status panel

Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
null 7a58d69c70 feat: hybrid subscription tracker
Added subscription metadata to bills: is_subscription, type, reminder_days, source, detected_at
Backend subscription API (routes/subscriptions.js)
SimpleFIN recommendation logic (services/subscriptionService.js)
New /subscriptions page (client/pages/SubscriptionsPage.jsx)
Track-as-subscription controls in BillModal.jsx
Navigation under Tracker menu
Accepting a recommendation creates a subscription-backed bill + links detected transactions
2026-05-28 22:54:07 -05:00
null 22df64e5e7 feat: auto-sync worker for SimpleFIN bank sync
New:
  services/bankSyncWorker.js — interval-based worker running every 4h (configurable via SIMPLEFIN_SYNC_INTERVAL_HOURS)
    - Checks bank sync enabled, fetches oldest-synced sources, skips <1h old
    - Staggers syncs 3s apart, writes last_error on failure, timer.unref() for clean shutdown

Modified:
  server.js — starts worker inside app.listen callback
  routes/admin.js — GET bank-sync-config includes worker status (running, interval, last/next run)
  client/components/admin/BankSyncAdminCard.jsx — shows auto-sync worker status panel when enabled
  .env.example — SIMPLEFIN_SYNC_INTERVAL_HOURS
2026-05-28 22:32:33 -05:00
null 88a4b64924 feat: DB-first bank sync config, admin toggle, extracted BankSyncSection
New:
  services/bankSyncConfigService.js — bank_sync_enabled from settings table, env fallback
  client/components/admin/BankSyncAdminCard.jsx — single toggle + encryption key status
  client/components/data/BankSyncSection.jsx — full connection management extracted from SettingsPage

Modified:
  routes/dataSources.js — per-request getBankSyncConfig() instead of module-level env check
  routes/admin.js — GET/PUT /api/admin/bank-sync-config
  AdminPage.jsx — renders BankSyncAdminCard after EmailNotifCard
  SettingsPage.jsx — BankSyncSection removed, 580->352 lines
  DataPage.jsx — BankSyncSection first, passes simplefinConn to TransactionMatchingSection
  TransactionMatchingSection.jsx — compact sync bar with green dot + Sync Now
  Layout.jsx — SimplefinBadge shows muted dot when enabled
  client/api.js — bankSyncConfig API calls
2026-05-28 22:06:15 -05:00
null 42abb12497 feat: SimpleFin bank sync with encrypted token storage
New services:
  services/encryptionService.js — AES-256-GCM with SHA-256 derived key
  services/simplefinService.js — protocol layer: claim token, fetch accounts/transactions, normalize to DB shapes
  services/bankSyncService.js — orchestration: connect, sync, disconnect with encrypted access URL storage

Modified:
  routes/dataSources.js — status, connect, sync, disconnect endpoints (gate on BANK_SYNC_ENABLED=true)
  client/api.js — simplefinStatus, connectSimplefin, syncDataSource, deleteDataSource, dataSources
  client/pages/SettingsPage.jsx — BankSyncSection with connected account info, sync/disconnect actions, setup token input
  .env.example — BANK_SYNC_ENABLED, TOKEN_ENCRYPTION_KEY, SIMPLEFIN_APP_NAME
2026-05-28 21:30:20 -05:00
null 8cab248959 security fixes 2026-05-28 03:59:35 -05:00
null 55837b8b25 docs: update engineering reference manual to v0.28.01
- Add sections 5.15-5.21 (Data Sources, Transactions, CSV Import, Match Suggestions)
- Add v0.47-v0.64 migrations to database reference
- Add data_sources, financial_accounts, transactions table schemas
- Add payment_source and transaction_id to payments table
- Update version header to 0.28.01, date to 2026-05-16
- Fix section numbering
2026-05-16 21:41:13 -05:00
null 060c8dc2f4 chore: version bump to 0.28.01 and update HISTORY format 2026-05-16 21:36:04 -05:00