Add income tracking (Phase 1) - #43
Merged
Merged
Conversation
Adds an income table, save_income/get_income tools, and a GET /income endpoint, mirroring the existing expense feature but without split/flag/edit/delete. The SYSTEM prompt now classifies pasted bank-statement credit lines as income vs expense, with a closed INCOME_CATEGORIES list (Salary, Interest, Rebate, Reimbursement, Transfer, Gift, Other). Reimbursement is a category only for now — linking a reimbursement to the expense it repays is Phase 2. Also closes a couple of gaps an adversarial review pass found: income and expense ids can collide, so the prompt now explicitly forbids routing income ids into update_expense/delete_expense; the pasted-text dedup instruction now covers income lines, not just expenses; and the Reimbursement vs Transfer examples no longer overlap.
Adds an Expenses/Income segmented toggle to ExpenseTable's toolbar, backed by a new fetchIncome() in App.jsx that mirrors fetchExpenses(). The income view is read-only for this phase: no add/edit/delete, no filters — just a chronological list (mobile cards + desktop table) with a running total, refetched after every chat turn so a chat-driven save_income shows up without a reload. Also adds the missing /income entry to the Vite dev proxy — the backend commit added GET /income but never wired the dev server to proxy it, which would have made the feature silently non-functional outside of production.
Seeds two income rows (Salary/Payroll Deposit, Rebate/Cashback Reward) for the e2e test user, and adds income.spec.js covering the Expenses/Income toggle, the income total, and that expense-only filter controls stay hidden in the income view.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds income tracking as a parallel feature to expenses, so pasted bank-statement credit lines (payroll, interest, cashback, e-transfers) can be logged via chat instead of only being ignored or miscategorized as expenses.
This is Phase 1: add-via-chat + read-only list. Reimbursement is a category, not a linked relationship yet — matching a reimbursement to the specific expense it repays (e.g. a friend paying back their share of a dinner you logged) is a separate Phase 2, deliberately out of scope here so this could ship as a focused, reviewable slice.
incometable (mirrorsexpenses, minus split/flag/edit/delete),save_income/get_incometools,GET /income, a closedINCOME_CATEGORIESlist (Salary, Interest, Rebate, Reimbursement, Transfer, Gift, Other), and SYSTEM prompt rules for classifying pasted credit/deposit lines as income vs expense.save_incomeshows up without a reload.Review
Ran this repo's adversarial code-review pass (8 independent finder angles + verification) against both the backend and frontend diffs before committing. Confirmed and fixed:
income.idandexpenses.idare separate sequences that can collide, and nothing stopped the model from routing an income id intodelete_expense/update_expense. The SYSTEM prompt now explicitly forbids this and tells the model to say edit/delete isn't supported for income yet.descriptiontrigram index onincome(mirrors the existing expense index;get_income'sdescription_containsfilter needs it)./income, which would have made the feature silently non-functional in local dev.One angle (
get_incomeduplicatingget_expenses's query-builder almost line-for-line) and one (user_idinjection via a hardcoded tuple inagent/main.py) were deliberately left as-is — both match a pre-existing pattern already used elsewhere in this codebase, and fixing them would mean refactoring working, already-tested expense code beyond this feature's scope.Test plan
uv run pytest tests/— 130 passednpm run lint— cleannpm run build— cleannpx playwright test e2e/income.spec.js— 2/2 (desktop + mobile)npx playwright test e2e/expenses.spec.js e2e/insights.spec.js— 18/18, no regressionsRoadmap / future work
linked_expense_id), net-reimbursed badge on the expense row, and a net cash-flow (income − expenses) summary.CATEGORY_ICONS/CATEGORY_COLORSonly cover expense categories — cosmetic, not blocking, worth a follow-up if income-specific styling is wanted.Generated by Claude Code