# 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` bind to `127.0.0.1:3000` by default; override with the `HOST`/`PORT` env vars (the `Containerfile` sets `HOST=0.0.0.0` so container port mapping works). The SQLite file lives at `data/app.db` (relative to `process.cwd()`, gitignored) and bootstraps itself from `schema.sql` on first run if missing. ## Container deployment `Containerfile` is a two-stage build (compile w/ native toolchain → slim alpine runtime). It runs as a non-root `app` user and expects `data/app.db` to persist via a bind-mounted host folder at `/app/data`: ``` docker build -t hardingsclocks . docker run -d -p 3000:3000 -v /path/on/host/data:/app/data hardingsclocks ``` Caddy (or whatever's doing auth/TLS per DESIGN.md §9) should reverse-proxy to the mapped `3000` port on the host. ## 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