B1: React Query deeper adoption (P1–P3) #86
No reviewers
Labels
No Label
architecture
backend
bug
feature
frontend
priority:critical
priority:high
priority:low
priority:medium
priority:nice-to-have
ux
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: null/BillTracker#86
Loading…
Reference in New Issue
No description provided.
Delete Branch "branch/b1-react-query"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Batch 1 of 3 splitting the v0.41.0 work-in-progress into separate, reviewable PRs.
Stack: B1 → B2 (
branch/b2-typescript) → B3 (branch/b3-docs-refresh).6 commits:
2793927feat(query): global background-refetch error toast (P1)e941f05feat(tracker): prefetch adjacent month on nav hover (P2)ad9fcbdrefactor(tracker): shared useQuickPay mutation hook (P3)c86acd7docs(history): React Query deeper adoptionMerges into
mainfirst. Then B2 stacks on top, then B3 on top of B2.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- 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- notificationService: `module.exports._push = {...}` was set BEFORE the final `module.exports = {...}`, which wiped it, so routes/notifications.js got `_push || {}` → sendTestPush undefined → POST /api/notifications/test-push always threw "Push service not initialised". Scheduled reminders were fine (in-scope calls). Moved the _push assignment after the reassignment. - add tests/notificationDelivery.test.js (7 tests: ntfy/gotify/discord payloads, dispatch, error handling, unknown channel, no token leak in the body) - docs: archive QA-B10-01 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>- notificationService buildEmailHtml: the message line interpolated bill.name raw (`<strong>${bill.name}</strong> is due…`) while the detail table escaped it; a `<img src=x onerror=…>` name landed unescaped in the email HTML. Now escaped everywhere. (self-XSS — reminders go to the bill's owner — but a clear inconsistent-escaping defect) - expose buildEmailHtml via _email; add an escaping test across all 4 email types - docs: archive QA-B14-04 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>- mark every batch B0→B15 + B-UI ✅ (automatable scope run, green, findings archived) - cycle log: Phase 2 complete (14 fixed) + clean automated re-run (0 new) - document non-blocking external-infra residuals carried to Cycle 2 - exit criteria all checked Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>- 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>Expand the QA plan beyond correctness to also hunt for improvements, per three lenses woven into the execution model: - Code health & consolidation (B17): duplication, dead code, overlapping modules, oversized files, one canonical path per concern. - UX (B18): core-flow friction, empty/loading/error states, feedback. - Information architecture / menus (B18): discoverability, surfacing actions into sensible menus, nav grouping. Adds an Improvement Backlog (§2.1) for IMP proposals (separate from the bug log; non-gating), detailed B17/B18 playbooks, and batch-table rows. Seeded the backlog with 6 concrete, code-verified candidates (client money-format consolidation, db/database.js split, match-service overlap, Data/menu IA, recently-deleted restore view, empty/loading/error-state audit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>db/database.js carried a ~1,740-line inline `const migrations = [...]` inside runMigrations(). Moved it to db/migrations/versionedMigrations.js as a factory, buildVersionedMigrations(deps), injected with the live `db` connection and the few schema helpers the migration bodies close over (isValidColumnName, isValidSqlDefinition, ensureTransactionFoundationSchema, and the four run*Migration helpers). Behavior is identical — the run/check closures resolve the same bindings, just passed in rather than captured. Fixed the two path references that broke by moving one directory deeper: the inline require('../services/...') calls and the __dirname docs JSON require now use ../../. database.js: 3,859 → 2,119 lines. Verified: full server suite 122 pass; a fresh DB applies all 79 migrations and is idempotent on a second boot; the real prod DB copy (v1.06) migrates as a clean no-op with data intact and no version-sync drift between the runMigrations and reconcileLegacyMigrations version lists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>- 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>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>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>- 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>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>- Each bucket header gains a 'Pay all due (N)' action: one bulkPay for every unpaid gated bill in the bucket, behind a confirm (count + total) and a single Undo toast that deletes the created payments. - Quick-pay and mark-paid now show a specific toast ('Rent - $1,200 paid'); quick-pay gained an Undo action to match un-pay. - Per-bill snooze already existed in the Overdue Command Center. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Pull request closed