Skip to content

feat(persistence): SQLite layer, persistent budgets/goals, and UI edit retention#7

Open
pranaymodukuru wants to merge 3 commits into
mainfrom
claude/suspicious-khayyam-c1cfce
Open

feat(persistence): SQLite layer, persistent budgets/goals, and UI edit retention#7
pranaymodukuru wants to merge 3 commits into
mainfrom
claude/suspicious-khayyam-c1cfce

Conversation

@pranaymodukuru

Copy link
Copy Markdown
Owner

Summary

  • SQLite persistence layer (src/db.py): 4-table schema — transactions, user_edits, budgets, goals. Content-addressed transaction IDs (SHA256 of identity columns) ensure idempotent upserts. The user_edits overlay is applied at read time, keeping base rows immutable.
  • Smart upsert logic (src/db.py, src/cleaner.py): Uses INSERT ... ON CONFLICT DO UPDATE SET ... WHERE <fields differ> so unchanged transactions are a true no-op — imported_at is preserved across re-cleans.
  • Cleaner writes to SQLite (src/cleaner.py): After writing CSV, clean() now also calls _write_to_db() so the DB stays in sync with every /clean-data run.
  • Dashboard reads from SQLite (scripts/gen-dashboard-data.py): gen-dashboard-data.py now prefers SQLite (with user_edits applied) and falls back to CSV when no DB exists.
  • Persistent UI state (dashboard/src/):
    • Added useLocalStorage hook to helpers.jsx
    • Inline category/subcategory edits in the Transactions page now survive page reloads (pf_tx_edits in localStorage)
    • Budget sliders persist across reloads (pf_budgets)
    • Goals are fully CRUD-able in the browser with a GoalForm component — icon picker, colour picker, target/progress/deadline fields (pf_goals)
  • Dependencies (pyproject.toml): Added fastapi and uvicorn for the upcoming Phase 2 API layer.

Architecture notes

The user_edits pattern keeps source data clean while allowing UI-driven corrections. When Phase 2 (FastAPI) lands, edits will write directly to SQLite via PATCH /transactions/:id rather than localStorage, fixing the current array-index staleness risk.

Test plan

  • Run /clean-data twice — verify imported_at is unchanged on second run for unmodified transactions
  • Edit a transaction's category in the Transactions page, reload the browser — verify edit persists
  • Adjust a budget slider, reload — verify the limit is retained
  • Create/edit/delete a savings goal — verify changes survive reload
  • Run python scripts/gen-dashboard-data.py with a populated data/ledger.db — verify DB path is used

🤖 Generated with Claude Code

pranaymodukuru and others added 3 commits May 13, 2026 10:55
…n dashboard

Introduces Phase 1 of the personal finance platform upgrade — giving the
app a persistent memory so nothing is lost between sessions.

- src/db.py: new SQLite schema (transactions, user_edits, budgets, goals)
  with full CRUD helpers, stable content-addressed tx_id, and a context
  manager that auto-commits/rollbacks
- src/cleaner.py: after writing cleaned CSV, upsert all rows to SQLite;
  db_rows_written added to cleaning report
- scripts/gen-dashboard-data.py: reads from SQLite first (with user_edits
  applied), falls back to CSV if DB not yet populated
- dashboard/pages-1.jsx: useLocalStorage hook persists budget limits and
  goals across page reloads; goals now fully CRUD (add, edit, delete) with
  icon/colour picker
- pyproject.toml: add fastapi + uvicorn for upcoming API layer (Phase 2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaced INSERT OR REPLACE (which always deleted+reinserted) with an
INSERT ... ON CONFLICT DO UPDATE WHERE clause that only writes when
receiver, amount, category, subcategory, or description actually differ.

Unchanged rows keep their original imported_at. User edits in the
user_edits table are never touched by upsert_transactions regardless.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Transaction category/subcategory edits made via the inline dropdowns in
the Transactions view now survive page reloads (stored in localStorage
under pf_tx_edits). Previously they were plain useState and evaporated
on every reload.

Also brings the Vite app (src/) up to parity with the fix that
previously only landed on the legacy Babel file (pages-1.jsx):
- Budgets: slider limits persist via useLocalStorage('pf_budgets')
- Goals: full CRUD (add/edit/delete) persisted via useLocalStorage('pf_goals');
  icon + colour picker; replaces the hardcoded static array

useLocalStorage hook added to src/helpers.jsx and exported for reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant