As files convert to TypeScript they must keep the react-hooks enforcement.
Added a .ts/.tsx config block using the typescript-eslint parser with the same
rules-of-hooks/exhaustive-deps/react-refresh rules; swapped core no-undef +
no-unused-vars (type-blind) for TS-aware equivalents. Verified 'npm run lint'
traverses .ts and flags issues there; 0 errors on the existing .ts files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adopting TypeScript incrementally (the move off plain JS). Upgraded jsconfig ->
tsconfig with strict + noUncheckedIndexedAccess, but allowJs + checkJs:false so
every existing .js/.jsx keeps resolving and running untouched — only .ts/.tsx
get type-checked. Added 'npm run typecheck' (tsc --noEmit) and wired it into
'npm run ci'. Installed typescript 6 + @types/react/react-dom 19. Client-scoped
(Vite resolves the '@/' paths); the CommonJS server is a separate TS story.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Added babel-plugin-react-compiler to the Vite React plugin. The compiler
auto-memoizes components/hooks at build time, so manual useMemo/useCallback
become largely unnecessary going forward (existing ones are left in place —
harmless, and the compiler adds the rest). Safe here because the codebase is
rules-of-hooks clean (enforced by eslint-plugin-react-hooks).
Validated: production build succeeds and the e2e probe passes 17/17 with the
compiler on — every authed page renders axe-clean and Tracker/Summary/Analytics
reconciliation holds. (Build time ~2.2s -> ~6.2s from the compiler pass; the
runtime win is automatic memoization.) The compiler ESLint rule needs
eslint-plugin-react-hooks v6 — a future upgrade.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ESLint surfaced 6 errors, incl. real bugs invisible before:
- ImportTransactionCsvSection called importErrorState() without importing it —
its own error handler would throw ReferenceError on a failed CSV import.
- client/components/MobileTrackerRow.jsx was a dead duplicate (unused; the live
one is tracker/MobileTrackerRow.jsx) with undefined-setter refs → deleted.
- StatusPage dbOk had a dead '?? true' (constant boolean LHS) — restored the
intended default-true-when-unknown.
- MobileBillRow redundant !! in a ternary condition.
Lint is now 0 errors; wired 'npm run lint' into 'npm run ci'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was no linting at all — nothing enforced rules-of-hooks (conditional-hook
crashes) or exhaustive-deps (stale closures) across 125 client components/pages.
Added an ESLint flat config (eslint.config.mjs) scoped to client/, an 'npm run
lint' script, and the devDeps. First run: 0 rules-of-hooks violations (good),
6 errors + 13 exhaustive-deps warnings to work through next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/prod-smoke.js + prod-smoke.sh: build, boot `node server.js` serving
dist/ against a scratch DB, and drive the real artifact with Playwright
(login + lazy routes) to confirm the vendor-chunk split loads in production
- npm run smoke:prod; passes green
- docs: B15 harness command + status
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
- 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)
- Removed hasBoth, compact prop, 2xl:min-w-[700px], 2xl:hidden Last Month,
narrowed columns — back to original single-column tracker layout
- Removed 2xl:max-w-[2000px] from Layout/Sidebar/AdminShell/footer/nav
- Kept S badge in all 4 locations and bucket remaining/Done header
- Added mkdocs/ to .gitignore
- Bump v0.33.8.6 -> v0.33.8.7
- Layout, AdminShell, Sidebar, footer, mobile nav: 2xl:max-w-[2000px]
- Content area grows from ~1436px to ~1936px, enough room for
dual-bucket tracker at 2xl+
- Bump v0.33.8.5 -> v0.33.8.6
- 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
- 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
- Removed 'Georgia' from --font-serif stack, replaced with ui-serif
- Added 'GeorgiaDigits' to --font-mono for snowball extra payment input
- Georgia now only enters via GeorgiaDigits @font-face with unicode-range
- Bump v0.33.8.1 -> v0.33.8.2
- 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
- Table now uses table-fixed + colgroup for fixed column widths
- Long transaction text can no longer push action buttons off-screen
- Action buttons are compact icon-only with aria-label/title
- Long matched bill names are truncated with truncate class
- Bump v0.33.7.2 -> v0.33.7.3
- 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
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
- 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
- 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