add claude.md for future development
This commit is contained in:
@@ -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`.
|
||||
Reference in New Issue
Block a user