# Claude Code — Session Instructions

This file is your starting point for every Claude Code session on this project.
Paste it (or reference it) at the start of each session to give Claude Code full context.

---

## Project Summary
We are building a local-first restaurant POS system. Full architecture and specs live in the `PLANS AND STRATEGIES/` folder. Always read the relevant guide file before starting work on any component. For a complete picture of the entire system read `PROJECT_REFERENCE.md`.

## Guide Files
- `00_PROJECT_OVERVIEW.md` — Architecture, stack, build order
- `01_LOCAL_BACKEND.md` — FastAPI backend spec
- `02_WAITER_PWA.md` — Waiter-facing PWA spec
- `03_MANAGER_DASHBOARD.md` — Manager web app spec
- `04_CLOUD_BACKEND.md` — Cloud licensing backend spec
- `05_SYSADMIN_PANEL.md` — Sysadmin cloud panel spec
- `TESTING_CHECKLIST.md` — End-to-end testing guide (step-by-step)
- `PROJECT_REFERENCE.md` — Master reference: full system description, wiring, all env vars

## Git Workflow
- The project uses git. **Commit after every meaningful milestone** (e.g. after scaffolding a phase, after a feature is working, after a bug fix).
- Always commit before starting a new phase or major refactor.
- Keep commit messages short and descriptive. No co-author lines needed.
- Never commit `.env`, `*.db`, or `license_state.json` — they are in `.gitignore`.

## Ground Rules for Claude Code
1. **Read the guide before writing code.** Each guide has schema, endpoints, and UX specs. Follow them.
2. **Local backend first.** Nothing else can be built or tested without it.
3. **Ask before deviating.** If something in the spec seems wrong or ambiguous, ask — don't invent.
4. **Keep business logic in the backend.** Frontends are display + interaction only.
5. **Never store sensitive data in frontend localStorage beyond token + username.**
6. **All prices are stored and calculated on the backend.** Frontend only displays them.
7. **The `unit_price` on `order_items` is a snapshot** — it must be copied from the product price at the time of ordering, not referenced dynamically.
8. **Printer failures must never block order saves.** Log and continue.

---

## Current Build Phase
> All 5 phases are built. Now in testing / polish.
> Phase 1: Local Backend — [x] Complete. Smoke tested.
> Phase 2: Waiter PWA — [x] Complete. Smoke tested end-to-end.
> Phase 3: Manager Dashboard — [x] Complete. Scaffolded and smoke tested. Known rough edges remain.
> Phase 4: Cloud Backend — [x] Complete. Built, not yet smoke tested end-to-end with a real site.
> Phase 5: Sysadmin Panel — [x] Complete. Built, not yet smoke tested.

---

## Phase 2 Known Issues & Fixes Applied
- `OrderItemOut` schema now includes `product { id, name }` via `ProductNameOut` — required for item names to show correctly in the PWA.
- `OrderOut` schema now includes `waiters: List[OrderWaiterOut]` — required for `isMyOrder` check on the frontend.
- Close order button is enabled when there are zero active items (not just when all are paid) — allows closing an empty order.
- Printing silently skips items with no `printer_zone_id` — **expected**. Printer zones are assigned in Phase 3 (Manager Dashboard). No code change needed.
- `selected_options` and `removed_ingredients` on `OrderItemInput` now accept `List[dict]` from the PWA (name+price_delta objects and name strings respectively), stored as JSON in the DB. The printer service reads them as raw JSON — modifier names print correctly.

## Phase 3 — What Was Built
- Manager Dashboard: React+Vite, TailwindCSS, React Query, react-hot-toast. Port 5174.
- Pages: Login (PIN pad, manager/sysadmin only), Dashboard (live table grid, 30s polling), OrderDetailPage (full actions), ProductsPage, WaitersPage, TablesPage, ReportsPage, SettingsPage.
- Docker Compose service added.

## Phase 3 Known Issues & Fixes Applied
- `isMyOrder` was always false after page reload — fixed: `AuthRehydrator` fetches `/auth/me` on app load to rehydrate user from token.
- `OrderItemInput` schema mismatch fixed: backend now accepts `selected_options` as `List[{id, name, price_delta}]` objects (not int IDs).
- Blocked waiter now force-logged out of PWA on next request (401 interceptor).
- `opt.price_delta` fixed to read `opt.extra_cost` from backend schema.
- `GET /api/products/?all=true` added for manager to see unavailable products (greyed out, not hidden).
- `PUT /api/products/{id}` now replaces options, ingredients, and preference_sets.
- New: ProductPreferenceSet + ProductPreferenceChoice models (exclusive-choice per product).
- New: Product image upload — POST `/api/products/{id}/image`, stored at `/app/data/product_images`, served as static files. Docker volume: `./data/product_images:/app/data/product_images`.
- New: TableGroup model + endpoints (`/api/tables/groups`). Tables have `group_id` FK.
- New: `DELETE /api/tables/{id}?hard=true` for permanent delete vs deactivate.
- New: `POST /api/tables/batch` for bulk table creation with prefix + count.
- New: `POST /api/auth/me` endpoint.
- Auto-migration on startup adds new columns to existing SQLite DB without dropping data.

## Phase 3 Known Remaining Issues (to revisit)
- Some PWA/manager interactions still have rough edges — to be addressed in a dedicated polish pass.
- Product image upload requires the product to already exist (no upload on creation, only on edit).

## Phase 4 — What Was Built
- Cloud Backend: FastAPI, SQLite (dev) / PostgreSQL (prod), JWT auth. Port 8001.
- Endpoints: `POST /api/auth/login`, `GET|POST|PUT|DELETE /api/sites/:id`, lock/unlock actions, `POST /api/heartbeat/`.
- Admin account seeded from env vars on first startup (`ADMIN_USERNAME`, `ADMIN_PASSWORD`).
- Heartbeat: local backend posts every 6 hours with `X-Site-ID` + `X-Site-Key` headers; cloud returns license status.
- `cloud_sync.py` in local backend runs as background asyncio task, persists state to `license_state.json`.
- Grace period: if cloud unreachable, local backend continues operating for `LICENSE_GRACE_HOURS` (default 24h).
- Docker Compose service: `cloud_backend`, depends on nothing, data volume at `./data/cloud`.

## Phase 5 — What Was Built
- Sysadmin Panel: React+Vite, TailwindCSS, dark theme, cyan accent. Port 5175.
- Points to cloud backend (`VITE_CLOUD_URL`), NOT the local backend.
- Pages: LoginPage (username+password), SitesPage (card grid, 30s polling, summary counts), SiteDetailPage (lock/unlock/extend license/delete with confirmation modals), RegisterSitePage (form + one-time secret key display with copy button).
- Token stored as `sysadmin_token` in localStorage (separate key from manager dashboard).
- Docker Compose service: `sysadmin_panel`, depends on `cloud_backend`.

## Dev Data (seeded / created during development)
- Tables 1–6 exist in local DB
- Category "food" (id=1) + arakas product from Phase 1 smoke test
- Categories: Ποτά, Σαλάτες, Κυρίως — 3 products each
- Printer zones assignable via ProductsPage in Manager Dashboard
- Cloud DB: admin `sysadmin` / `changeme` seeded on first startup

---

## Environment Variables (current actual values)

### Local Backend (`local_backend/.env`)
```
SITE_ID=<from sysadmin panel site registration>
SITE_KEY=<secret key shown once at registration>
CLOUD_URL=http://cloud_backend:8001
SECRET_KEY=<long random string>
LICENSE_GRACE_HOURS=24
```

### Waiter PWA (`waiter_pwa/.env`)
```
VITE_API_URL=http://<local-machine-ip>:8000
```

### Manager Dashboard (`manager_dashboard/.env`)
```
VITE_API_URL=http://<local-machine-ip>:8000
```

### Cloud Backend (`cloud_backend/.env`)
```
SECRET_KEY=<long random string — different from local>
DATABASE_URL=sqlite:////app/data/cloud.db
ACCESS_TOKEN_EXPIRE_MINUTES=60
ADMIN_USERNAME=sysadmin
ADMIN_PASSWORD=<your password>
```

### Sysadmin Panel (`sysadmin_panel/.env`)
```
VITE_CLOUD_URL=http://localhost:8001
```

---

## Ports
| Service | Port |
|---|---|
| Local Backend | 8000 |
| Cloud Backend | 8001 |
| Waiter PWA | 5173 |
| Manager Dashboard | 5174 |
| Sysadmin Panel | 5175 |
