diff --git a/CLAUDE.md b/CLAUDE.md
index b69713d..cf8c4db 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,8 +14,10 @@ tessera/
│ ├── models/ # TypeScript types + Zod schemas
│ ├── scrip/ # Scrip engine (prepare/commit two-phase)
│ └── lifecycle/ # State machine validator
-├── web/ # Frontend: Next.js 15 + shadcn/ui
-│ └── src/app/ # App Router pages
+├── web/ # Frontend: Next.js 16 + shadcn/ui
+│ ├── src/app/ # App Router pages
+│ ├── src/components/ # Reusable components + widgets
+│ └── src/lib/ # API client + types + utils
├── drizzle/ # SQL migration files
└── docs/ # Architecture + design specs
```
@@ -24,9 +26,9 @@ tessera/
**Backend:** Bun runtime, Hono web framework, Drizzle ORM, PostgreSQL 17, Zod validation, Handlebars templates, nodemailer
-**Frontend:** Next.js 15 App Router, shadcn/ui (Tailwind CSS), next-themes (light/dark), React Hook Form + Zod, TanStack Table, date-fns, lucide-react icons
+**Frontend:** Next.js 16 App Router (Turbopack), shadcn/ui (Tailwind CSS), next-themes, date-fns, lucide-react icons
-**Fonts:** Inter (variable, with cv01+ss03 OpenType features), JetBrains Mono
+**Fonts:** Inter (variable), JetBrains Mono
## Running Locally
@@ -39,45 +41,47 @@ tessera/
### Start backend
```bash
cd ~/projects/tessera
-cp .env.example .env # Edit DATABASE_URL to point to postgres://tessera:***@127.0.0.1:5433/tessera
+cp .env.example .env
npm run dev:backend # Starts API on port 9876
```
### Run migrations
```bash
npm run db:migrate
-npm run db:seed # Optional demo data for UI review
-npm run db:seed:reset # Reset local app data, then recreate demo data
+npm run db:seed # Demo data
+npm run db:seed:reset # Reset + re-seed
```
### Start frontend
```bash
cd web
-npm install # Use npm, NOT bun (bun has compatibility issues with Next.js dev server)
-npm run build # Production build
-npm run start # Production server on 127.0.0.1:3100
+npm install # Use npm, NOT bun
+bun run dev # Dev server on 127.0.0.1:3100 (HMR)
```
-**Note:** `bun run dev` (Turbopack) has WebSocket HMR issues in this environment. Use production mode only.
-
## API Endpoints
-All endpoints are served by the backend on port 9876. The frontend proxies `/api/*` to the backend via `next.config.ts`.
+All endpoints on port 9876. Frontend proxies `/api/*` via `next.config.ts`.
| Method | Path | Description |
|--------|------|-------------|
| GET | /health | Health check |
-| GET | /tickets | List tickets (?queue_id=&status=) |
+| GET | /tickets | List tickets (?queue_id=&status=&owner_id=&team_id=&q=&limit=&cf.*=) |
| POST | /tickets | Create ticket |
| GET | /tickets/:id | Get ticket with custom fields |
-| PATCH | /tickets/:id | Update ticket (validates lifecycle, runs scrips) |
+| PATCH | /tickets/:id | Update ticket (validates lifecycle, runs scrips, returns scrip_results) |
| POST | /tickets/:id/preview | Dry-run scrips for status change |
| POST | /tickets/:id/comment | Add comment to ticket |
| GET | /tickets/:id/transactions | List ticket transactions |
-| GET/POST | /queues | List/create queues |
-| GET/POST/PATCH | /scrips | CRUD scrips |
-| GET/POST | /custom-fields | List/create custom fields |
-| GET/POST | /lifecycles | List/create lifecycles |
+| GET/POST/PATCH | /queues | CRUD queues |
+| GET/POST/PATCH/DELETE | /scrips | CRUD scrips |
+| GET/POST/PATCH | /custom-fields | CRUD custom fields |
+| GET/POST/PATCH | /lifecycles | CRUD lifecycles |
+| GET/POST/PATCH/DELETE | /users | CRUD users |
+| GET/POST/PATCH/DELETE | /templates | CRUD templates + POST /preview |
+| GET/POST/PATCH/DELETE | /views | CRUD saved views |
+| GET/POST/PATCH/DELETE | /teams | CRUD teams + POST/DELETE members |
+| GET/POST/PATCH/DELETE | /dashboards | CRUD dashboards + widgets + widget data |
## Key Design Decisions
@@ -85,32 +89,22 @@ All endpoints are served by the backend on port 9876. The frontend proxies `/api
- **Transaction-centric:** Every state change creates a transaction record. The scrip engine runs on transactions.
- **Two-phase scrip engine:** Prepare (no side effects) then Commit (execute actions). Supports dry-run mode.
- **Lifecycle state machines:** Per-queue configurable status transitions with wildcard support.
-- **Light mode is default.** Dark mode available via theme toggle (next-themes).
+- **SQL-level filtering:** Ticket filters (status, queue, owner, team, custom fields) pushed to PostgreSQL via Drizzle WHERE clauses.
- **No ORM for frontend:** Drizzle is only on the backend. Frontend uses a typed fetch wrapper (`web/src/lib/api.ts`).
+- **Dev server over production:** Use `bun run dev` (port 3100) with HMR. Build+restart only when dev server has issues.
+- **Design consistency:** See `docs/design-system.md` for the design rules applied across the app.
## Git Workflow
Repo: `https://git.gjermund.xyz/gjermund/tessera`
-Push via HTTPS with token auth (SSH port 2222 is not configured on Gitea):
```bash
git remote set-url origin https://gjermund:TOKEN@git.gjermund.xyz/gjermund/tessera.git
```
-## Development Workflow
-
-All code is written by **OpenCode** (AI coding agent). Hermes writes specs, OpenCode writes code, Gjermund reviews.
-
-OpenCode server: `opencode serve --port 4096` (Gjermund attaches with `opencode --attach http://127.0.0.1:4096`)
-
-After OpenCode makes changes:
-1. `cd web && npx next build` — verify zero errors
-2. `npm run start` — restart production server on 127.0.0.1:3100
-3. `git push` — push to origin
-
## Common Issues
-- **Frontend shows skeleton/blank page:** The frontend dev server (Turbopack) has WebSocket HMR issues. Use production mode (`npx next build` + `npx next start`).
+- **Frontend shows skeleton/blank page:** Dev server may have HMR issues. Kill port 3100, rebuild with `npm run build`, restart with `npm run start`.
- **Backend not running on 9876:** Restart with `bun run src/index.ts`. Check port with `ss -tlnp | grep 9876`.
- **Database connection refused:** Docker container may be stopped. `docker start tessera-db`.
- **Build errors after migration:** Run `bun run src/db/migrate.ts` to apply new migrations.
diff --git a/docs/design-system.md b/docs/design-system.md
new file mode 100644
index 0000000..ee14834
--- /dev/null
+++ b/docs/design-system.md
@@ -0,0 +1,118 @@
+# Tessera Design System
+
+The app follows a clean, minimal aesthetic — no heavy borders, no unnecessary shadows, flat hierarchy.
+
+## Principles
+
+1. **Flat, not boxed.** Avoid bordered containers around every section. Use whitespace for separation.
+2. **Soft borders.** Never full-opacity borders. Always `/50` or `/30`.
+3. **No shadows.** Cards and sections don't need shadows. The sidebar has the only subtle shadow.
+4. **Consistent typography.** Three font sizes: labels (10px), content (12-13px), headings (14-24px).
+5. **Dimmed by default.** Icons, borders, and secondary text start dimmed. They brighten on hover/focus.
+
+## Component Patterns
+
+### Section Wrapper
+```tsx
+