add claude.md for future development

This commit is contained in:
2026-07-11 19:53:40 -04:00
parent e52c8eedc6
commit 23cbd6f2bf
+102
View File
@@ -0,0 +1,102 @@
# Harding's Clocks — Repair Tracker
A repair-order tracker for a one-person clock repair shop. Full spec lives in
`DESIGN.md` — read it for the data model, route table, and design rationale.
This file covers what's useful for day-to-day development that isn't already
in the design doc.
## Goals & constraints
- One user (Dad), one machine, one job at a time. Optimize for simplicity
over flexibility — this is explicitly **not** meant to grow into a
multi-tenant or general-purpose product.
- No client-side app framework: the server renders HTML, HTMX does partial
updates. Don't introduce React/Vue/a JSON API layer for this app.
- No auth in the app itself — that's Caddy's job in front of it (see
DESIGN.md §9). Don't add login/session code here.
- Money is always integer cents in the DB; format at the view layer via the
`money` Nunjucks filter (`src/money.ts`). Never store floats for currency.
## Tech stack
Express + TypeScript (ESM/NodeNext) + better-sqlite3 + Nunjucks + HTMX.
Package manager is **pnpm** (not npm — a `package-lock.json` should not
reappear; `pnpm-lock.yaml` is the one committed).
**Deviation from DESIGN.md §3/§8:** the doc lists Vite for asset bundling.
There's no client JS/TS to bundle — just vendored `htmx.min.js` and one plain
CSS file — so Vite was dropped as ceremony with no payoff. Express serves
`public/` and `src/views/*.njk` directly. `tsc` compiles the server; the
`build` script then copies `schema.sql` and `views/` into `dist/` since `tsc`
only touches `.ts` files.
## pnpm + better-sqlite3 gotcha
better-sqlite3 needs a native addon compiled on install. pnpm blocks
postinstall/install scripts by default for security, which leaves
`better_sqlite3.node` missing and the app fails at runtime with a `bindings`
error (not at `pnpm install` time — it only surfaces when `new Database()`
runs). This is handled by `pnpm.onlyBuiltDependencies: ["better-sqlite3"]` in
`package.json`, which allows pnpm to run its build script non-interactively.
If native-binding errors show up after adding new deps, check whether they
need the same treatment (`pnpm approve-builds` for one-off interactive
approval, or add to `onlyBuiltDependencies` for it to be automatic).
## Commands
```
pnpm install # first-time setup (compiles better-sqlite3's native binding)
pnpm dev # tsx watch src/server.ts — reads views/schema straight from src/
pnpm build # tsc + copy schema.sql/views into dist/
pnpm start # node dist/server.js — run this after pnpm build
pnpm backup # scripts/backup.ts — see DESIGN.md §10
```
Nunjucks template changes are not hot-reloaded (chokidar isn't a dependency,
to avoid the extra install); restart `pnpm dev` after editing `.njk` files.
The dev server and `dist/server.js` both bind to `127.0.0.1:3000`. The SQLite
file lives at `data/app.db` (gitignored) and bootstraps itself from
`schema.sql` on first run if missing.
## Structure
```
src/
├─ server.ts # Express app, Nunjucks config, route mounting
├─ db.ts # DB connection, schema bootstrap, order-number allocation
├─ money.ts # cents <-> string helpers ("money", "lineTotal" filters)
├─ schema.sql # DDL — source of truth for the data model
├─ routes/
│ ├─ orders.ts # order list/detail/create/update/delete, line items, work log
│ ├─ customers.ts # customer list/search
│ ├─ clocks.ts # HTMX fragment: clock <option> list for a given customer
│ └─ invoice.ts # printable invoice page
└─ views/ # Nunjucks templates; `_`-prefixed files are HTMX fragments
public/css/style.css # all styling — design tokens live in :root, see DESIGN.md §12
scripts/backup.ts # nightly SQLite backup via better-sqlite3's backup API
```
## HTMX patterns used here
- Adding/removing a line item returns the changed `<tr>` (or nothing, for a
delete) plus an out-of-band swap of `#totals-summary` so the totals box
stays in sync without a full page reload. See
`src/views/orders/_line_item_added.njk` and `_totals.njk` (the `oob` flag
controls whether `hx-swap-oob="true"` is emitted).
- The order-summary edit form (status, dates, tax/shipping/etc.) posts to
`POST /orders/:id` and swaps the whole `#order-summary` card via
`hx-swap="outerHTML"`, which naturally refreshes its embedded totals too.
- Deleting an order uses `hx-delete` and an `HX-Redirect` response header
rather than a plain 3xx, since HTMX needs that header to force a full-page
navigation after an XHR delete.
## Data model notes worth remembering
- `order_number` starts at 8247, is allocated via `MAX(order_number) + 1` in
`nextOrderNumber()` (`src/db.ts`), and is never reused — gaps from
soft-deleted orders are expected and fine.
- Orders are soft-deleted (`deleted_at`); every query that lists/reads orders
filters on `deleted_at IS NULL` (baked into the `order_totals` view too).
- `order_totals` is a SQL view, not computed in application code — if you add
a new money field to `service_orders`, update the view in `schema.sql`.