11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Agent Workflow Rules
- Commit after every task: When a task is complete, stage all changed files and create a git commit before marking the task done.
- Write tests with features: New features and bug fixes must include unit tests. Run
npm testin bothserver/andclient/before committing. - Keep documentation current: Update
CLAUDE.mdafter every task that adds, changes, or removes a feature, API endpoint, or architectural pattern. This is mandatory, not optional. UpdatePRD.mdonly if scope/design decisions changed. - Mark tasks in td:
td start <id>when beginning,td close <id>when done. - Run td commands one at a time: Never chain
tdcommands with&∨. Eachtdcall must be its own separate shell invocation. - Only the orchestrator touches td: Sub-agents must never call
tdcommands directly. Concurrenttdwrites from parallel agents corrupt the SQLite database. The orchestrator handles alltd start/td closecalls before and after delegating to sub-agents. - td approve requires a separate session:
td approvecannot be run by the implementing agent or any sub-agent spawned from the same session —tdtracks session involvement. Approval must come from a new conversation/session. Leave implemented tasks in review state (td review <id>) at the end of the session; they can be approved at the start of a fresh session.
Task Management
This project uses td (a local CLI) for task tracking. At the start of each session:
td usage --new-session # required at conversation start or after /clear
td usage -q # quick check for subsequent reads
Optional session labeling:
td session "name" # label the current session
td session --new # force a new session in the same terminal context
Task state is stored in .todos/issues.db (SQLite).
Development
Run production stack (Docker):
docker compose up
Run development stack with live reload (Docker):
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
Frontend only (Vite dev server):
cd client && npm install && npm run dev
Backend only (nodemon):
cd server && npm install && npm run dev
Testing
Unit tests are required when adding or modifying features. Both server and client use Vitest.
Run all tests:
cd server && npm test # server unit tests
cd client && npm test # client unit tests
Watch mode (re-runs on file change):
cd server && npm run test:watch
cd client && npm run test:watch
Server tests (server/src/__tests__/): Use Vitest + Supertest for route testing. The CJS server code requires mocking db.pool.query directly (replace the method on the shared pool object) rather than using vi.mock for CJS modules. Validation and pure logic functions are exported and tested directly. See bills.validation.test.js and bills.routes.test.js for patterns.
Client tests (client/src/__tests__/): Use Vitest + React Testing Library. jsdom environment is configured via client/vitest.config.js. The test setup file (client/src/test/setup.js) provides @testing-library/jest-dom matchers and polyfills like window.matchMedia. See ThemeContext.test.jsx and App.test.jsx for patterns.
When adding features:
- Add unit tests for new validation logic, utility functions, and API routes
- Add component tests for new React components or significant UI changes
- Export pure functions (validators, formatters, etc.) for direct testing
- Run
npm testin bothserver/andclient/before committing
Callback prop naming convention: React callback props follow on[Noun][Verb] (e.g., onBillPaidToggle, onPaycheckAmountSave, onPaycheckGenerate). Event handler functions in the parent component use the handle[Action] prefix (e.g., handleAmountSave, handleBillPaidToggle).
Application Structure
The default route / renders the paycheck-centric main view (client/src/pages/PaycheckView.jsx). It shows the current month's two paychecks side-by-side with bills, paid status, one-time expenses, and remaining balance. Month navigation (prev/next) fetches data via GET /api/paychecks?year=&month=.
Theming: client/src/ThemeContext.jsx provides light/dark mode via CSS custom properties on [data-theme]. Preference persists in localStorage and defaults to prefers-color-scheme. All design tokens live in client/src/index.css.
Charts: Monthly Summary and Annual Overview use Recharts (SVG-based). Monthly Summary shows a spending breakdown donut and variable-by-category bar. Annual Overview shows income vs. spending, surplus/deficit trend, and stacked variable spending by category — all driven by the single GET /api/summary/annual?year= endpoint.
Bill amount locking: When a paycheck_bill is marked paid, amount_override is set to the bill's current amount, locking in the historical value. Unmarking clears the override.
Variable amount bills: Bills with variable_amount = true require the amount to be entered each month in the paycheck view (stored as amount_override on paycheck_bills). The bill's amount field serves as an optional typical/estimated value.
Lazy paycheck generation: GET /api/paychecks returns virtual (unsaved) data with id: null when no DB record exists for the month. Paychecks are only persisted when the first interaction occurs (bill toggle, expense add, etc.). The "↺ Refresh amounts" button on the paycheck view re-runs POST /api/paychecks/generate to sync gross/net from current Settings. Individual paycheck gross/net can also be edited inline via the pencil icon.
Financing: GET/POST /api/financing, PUT/DELETE /api/financing/:id, PATCH /api/financing-payments/:id/paid. Plans track a total amount, payoff due date, and start_date. Payment per period is auto-calculated as (remaining balance) / (remaining periods). Split plans (assigned_paycheck = null) divide each period's payment across both paychecks. Plans auto-close when fully paid. Financing payments are included in the paycheck remaining balance. start_date prevents a plan from appearing on paycheck months before it was created — both virtual previews and generate respect this guard.
Migrations: SQL files in db/migrations/ are applied in filename order on server startup. Add new migrations as 00N_description.sql — they run once and are tracked in the migrations table.
Database Schema
Full DDL lives in db/migrations/. Key tables:
| Table | Description |
|---|---|
config |
Key/value store for app settings (paycheck days, gross/net amounts). |
bills |
Bill definitions: name, amount, due day, assigned paycheck, category, active flag. |
paychecks |
One row per paycheck per period (year + month + paycheck_number 1 or 2). |
paycheck_bills |
Junction between a paycheck instance and its bills; tracks paid status and amount_override. |
one_time_expenses |
Ad-hoc expenses attached to a specific paycheck instance. |
financing_plans |
Financing plans: total amount, due date, start_date, optional assigned_paycheck. |
financing_payments |
One payment record per plan per paycheck; tracks paid status. |
expense_categories |
Lookup table for variable-expense categories (Groceries, Gas, Dining, …). |
actuals |
Actual spending log entries linked to a paycheck, category, or bill. |
migrations |
Internal table tracking which migration files have been applied. |
API Endpoints
All routes are prefixed with /api.
Paychecks
| Method | Path | Description |
|---|---|---|
GET |
/paychecks?year=&month= |
Return both paychecks for a month. Returns virtual data (id: null) when no DB record exists. |
POST |
/paychecks/generate?year=&month= |
Upsert paycheck records and sync bills/financing for the month. |
GET |
/paychecks/months |
List all months that have generated paycheck records, newest first. |
PATCH |
/paychecks/:id |
Update gross and net for a specific paycheck. |
PATCH |
/paycheck-bills/:id/paid |
Toggle paid status; locks amount_override on pay. |
PATCH |
/paycheck-bills/:id/amount |
Set amount_override for a variable-amount bill. |
Bills
| Method | Path | Description |
|---|---|---|
GET |
/bills |
List all bills ordered by assigned_paycheck, name. |
POST |
/bills |
Create a bill. |
GET |
/bills/:id |
Fetch a single bill. |
PUT |
/bills/:id |
Replace a bill's fields. |
DELETE |
/bills/:id |
Hard-delete a bill. |
PATCH |
/bills/:id/toggle |
Toggle the active flag. |
Config
| Method | Path | Description |
|---|---|---|
GET |
/config |
Return all config values as a flat object with numeric values. |
PUT |
/config |
Upsert one or more config keys. Unknown keys are silently ignored. |
One-Time Expenses
| Method | Path | Description |
|---|---|---|
POST |
/one-time-expenses |
Add a one-time expense to a paycheck. |
DELETE |
/one-time-expenses/:id |
Remove a one-time expense. |
PATCH |
/one-time-expenses/:id/paid |
Toggle paid status. |
Financing
| Method | Path | Description |
|---|---|---|
GET |
/financing |
List all financing plans with enriched progress fields. |
POST |
/financing |
Create a financing plan. |
GET |
/financing/:id |
Fetch a plan with its full payment history. |
PUT |
/financing/:id |
Update a financing plan. |
DELETE |
/financing/:id |
Delete a financing plan and its payments. |
PATCH |
/financing-payments/:id/paid |
Toggle a payment's paid status; auto-closes the plan when fully paid. |
Actuals & Categories
| Method | Path | Description |
|---|---|---|
GET |
/expense-categories |
List all expense categories. |
POST |
/expense-categories |
Create a new expense category. |
GET |
/actuals?paycheckId= |
List actual spending entries for a paycheck. |
POST |
/actuals |
Log an actual spending entry. |
DELETE |
/actuals/:id |
Remove an actual spending entry. |
Summary
| Method | Path | Description |
|---|---|---|
GET |
/summary/monthly?year=&month= |
Spending breakdown and category totals for a single month. |
GET |
/summary/annual?year= |
Income vs. spending, surplus/deficit trend, and stacked variable spending for a full year. |
Health
| Method | Path | Description |
|---|---|---|
GET |
/health |
Returns { ok: true }. Used by Docker healthcheck. |