183 lines
11 KiB
Markdown
183 lines
11 KiB
Markdown
# 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 test` in both `server/` and `client/` before committing.
|
|
- **Keep documentation current**: Update `CLAUDE.md` after every task that adds, changes, or removes a feature, API endpoint, or architectural pattern. This is mandatory, not optional. Update `PRD.md` only 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 `td` commands with `&&` or `;`. Each `td` call must be its own separate shell invocation.
|
|
- **Only the orchestrator touches td**: Sub-agents must never call `td` commands directly. Concurrent `td` writes from parallel agents corrupt the SQLite database. The orchestrator handles all `td start`/`td close` calls before and after delegating to sub-agents.
|
|
- **td approve requires a separate session**: `td approve` cannot be run by the implementing agent or any sub-agent spawned from the same session — `td` tracks 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:
|
|
|
|
```bash
|
|
td usage --new-session # required at conversation start or after /clear
|
|
td usage -q # quick check for subsequent reads
|
|
```
|
|
|
|
Optional session labeling:
|
|
```bash
|
|
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):**
|
|
```bash
|
|
docker compose up
|
|
```
|
|
|
|
**Run development stack with live reload (Docker):**
|
|
```bash
|
|
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
|
|
```
|
|
|
|
**Frontend only (Vite dev server):**
|
|
```bash
|
|
cd client && npm install && npm run dev
|
|
```
|
|
|
|
**Backend only (nodemon):**
|
|
```bash
|
|
cd server && npm install && npm run dev
|
|
```
|
|
|
|
## Testing
|
|
|
|
Unit tests are required when adding or modifying features. Both server and client use [Vitest](https://vitest.dev/).
|
|
|
|
**Run all tests:**
|
|
```bash
|
|
cd server && npm test # server unit tests
|
|
cd client && npm test # client unit tests
|
|
```
|
|
|
|
**Watch mode (re-runs on file change):**
|
|
```bash
|
|
cd server && npm run test:watch
|
|
cd client && npm run test:watch
|
|
```
|
|
|
|
**Server tests** (`server/src/__tests__/`): Use Vitest + [Supertest](https://github.com/ladakh/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](https://testing-library.com/docs/react-testing-library/intro/). 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 test` in both `server/` and `client/` 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](https://recharts.org) (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. |
|