docs: update CLAUDE.md, redesign AGENTS.md, add design-system.md
- CLAUDE.md: updated API endpoints, dev workflow, current stack - web/AGENTS.md: replaced auto-generated text with Tessera design rules - docs/design-system.md: new — component patterns, typography scale, color tokens, anti-patterns to avoid Ensures design consistency across all future work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
60
CLAUDE.md
60
CLAUDE.md
@@ -14,8 +14,10 @@ tessera/
|
|||||||
│ ├── models/ # TypeScript types + Zod schemas
|
│ ├── models/ # TypeScript types + Zod schemas
|
||||||
│ ├── scrip/ # Scrip engine (prepare/commit two-phase)
|
│ ├── scrip/ # Scrip engine (prepare/commit two-phase)
|
||||||
│ └── lifecycle/ # State machine validator
|
│ └── lifecycle/ # State machine validator
|
||||||
├── web/ # Frontend: Next.js 15 + shadcn/ui
|
├── web/ # Frontend: Next.js 16 + shadcn/ui
|
||||||
│ └── src/app/ # App Router pages
|
│ ├── src/app/ # App Router pages
|
||||||
|
│ ├── src/components/ # Reusable components + widgets
|
||||||
|
│ └── src/lib/ # API client + types + utils
|
||||||
├── drizzle/ # SQL migration files
|
├── drizzle/ # SQL migration files
|
||||||
└── docs/ # Architecture + design specs
|
└── docs/ # Architecture + design specs
|
||||||
```
|
```
|
||||||
@@ -24,9 +26,9 @@ tessera/
|
|||||||
|
|
||||||
**Backend:** Bun runtime, Hono web framework, Drizzle ORM, PostgreSQL 17, Zod validation, Handlebars templates, nodemailer
|
**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
|
## Running Locally
|
||||||
|
|
||||||
@@ -39,45 +41,47 @@ tessera/
|
|||||||
### Start backend
|
### Start backend
|
||||||
```bash
|
```bash
|
||||||
cd ~/projects/tessera
|
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
|
npm run dev:backend # Starts API on port 9876
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run migrations
|
### Run migrations
|
||||||
```bash
|
```bash
|
||||||
npm run db:migrate
|
npm run db:migrate
|
||||||
npm run db:seed # Optional demo data for UI review
|
npm run db:seed # Demo data
|
||||||
npm run db:seed:reset # Reset local app data, then recreate demo data
|
npm run db:seed:reset # Reset + re-seed
|
||||||
```
|
```
|
||||||
|
|
||||||
### Start frontend
|
### Start frontend
|
||||||
```bash
|
```bash
|
||||||
cd web
|
cd web
|
||||||
npm install # Use npm, NOT bun (bun has compatibility issues with Next.js dev server)
|
npm install # Use npm, NOT bun
|
||||||
npm run build # Production build
|
bun run dev # Dev server on 127.0.0.1:3100 (HMR)
|
||||||
npm run start # Production server on 127.0.0.1:3100
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** `bun run dev` (Turbopack) has WebSocket HMR issues in this environment. Use production mode only.
|
|
||||||
|
|
||||||
## API Endpoints
|
## 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 |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| GET | /health | Health check |
|
| 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 |
|
| POST | /tickets | Create ticket |
|
||||||
| GET | /tickets/:id | Get ticket with custom fields |
|
| 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/preview | Dry-run scrips for status change |
|
||||||
| POST | /tickets/:id/comment | Add comment to ticket |
|
| POST | /tickets/:id/comment | Add comment to ticket |
|
||||||
| GET | /tickets/:id/transactions | List ticket transactions |
|
| GET | /tickets/:id/transactions | List ticket transactions |
|
||||||
| GET/POST | /queues | List/create queues |
|
| GET/POST/PATCH | /queues | CRUD queues |
|
||||||
| GET/POST/PATCH | /scrips | CRUD scrips |
|
| GET/POST/PATCH/DELETE | /scrips | CRUD scrips |
|
||||||
| GET/POST | /custom-fields | List/create custom fields |
|
| GET/POST/PATCH | /custom-fields | CRUD custom fields |
|
||||||
| GET/POST | /lifecycles | List/create lifecycles |
|
| 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
|
## 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.
|
- **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.
|
- **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.
|
- **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`).
|
- **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
|
## Git Workflow
|
||||||
|
|
||||||
Repo: `https://git.gjermund.xyz/gjermund/tessera`
|
Repo: `https://git.gjermund.xyz/gjermund/tessera`
|
||||||
|
|
||||||
Push via HTTPS with token auth (SSH port 2222 is not configured on Gitea):
|
|
||||||
```bash
|
```bash
|
||||||
git remote set-url origin https://gjermund:TOKEN@git.gjermund.xyz/gjermund/tessera.git
|
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
|
## 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`.
|
- **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`.
|
- **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.
|
- **Build errors after migration:** Run `bun run src/db/migrate.ts` to apply new migrations.
|
||||||
|
|||||||
118
docs/design-system.md
Normal file
118
docs/design-system.md
Normal file
@@ -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
|
||||||
|
<section className="overflow-hidden rounded-lg border border-border/50">
|
||||||
|
...
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Section Header / Title Bar
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-center justify-between border-b border-border/50 bg-muted/20 px-4 py-3">
|
||||||
|
<h2 className="text-sm font-semibold">Title</h2>
|
||||||
|
<Button size="sm">Action</Button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### List Item (clickable)
|
||||||
|
```tsx
|
||||||
|
<button className={cn(
|
||||||
|
"rounded-lg border p-3 text-left transition",
|
||||||
|
"border-border/50 hover:border-primary/30 hover:bg-accent/30",
|
||||||
|
isActive && "border-primary/50 bg-primary/5"
|
||||||
|
)}>
|
||||||
|
<div className="text-sm font-semibold">Name</div>
|
||||||
|
<div className="text-xs text-muted-foreground">Description</div>
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Property Row (key-value)
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-baseline justify-between gap-2 text-sm">
|
||||||
|
<span className="text-muted-foreground">Label</span>
|
||||||
|
<span className="text-foreground">Value</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sidebar Nav Item
|
||||||
|
```tsx
|
||||||
|
<Link href={href} className={cn(
|
||||||
|
"flex items-center px-2.5 py-1.5 rounded-md text-[13px] transition-colors",
|
||||||
|
active
|
||||||
|
? "bg-sidebar-accent text-sidebar-foreground font-medium"
|
||||||
|
: "text-sidebar-foreground/55 hover:text-sidebar-foreground hover:bg-sidebar-accent/50"
|
||||||
|
)}>
|
||||||
|
<Icon className={cn("w-4 h-4", active ? "opacity-90" : "opacity-50 group-hover:opacity-70")} />
|
||||||
|
<span className="truncate">{label}</span>
|
||||||
|
</Link>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Section Label (small caps)
|
||||||
|
```tsx
|
||||||
|
<div className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60">
|
||||||
|
LABEL
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form Input
|
||||||
|
```tsx
|
||||||
|
<input className="h-8 w-full rounded-md border border-input bg-transparent px-2.5 text-sm outline-none focus:border-ring" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form Select
|
||||||
|
```tsx
|
||||||
|
<select className="h-8 w-full rounded-md border border-input bg-transparent px-2.5 text-sm outline-none focus:border-ring">
|
||||||
|
<option>...</option>
|
||||||
|
</select>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Typography Scale
|
||||||
|
|
||||||
|
| Size | Weight | Use |
|
||||||
|
|------|--------|-----|
|
||||||
|
| `text-[10px]` | `font-medium` | Section labels, form labels |
|
||||||
|
| `text-[11px]` | `font-medium` | Meta, timestamps, badges |
|
||||||
|
| `text-[13px]` | `font-normal` | Nav items, list items |
|
||||||
|
| `text-sm` | `font-normal` | Body, property values |
|
||||||
|
| `text-sm` | `font-semibold` | Card titles, list item names |
|
||||||
|
| `text-base` | `font-semibold` | Section headings |
|
||||||
|
| `text-lg` | `font-semibold` | Form titles |
|
||||||
|
| `text-xl` | `font-semibold` | Page headings |
|
||||||
|
|
||||||
|
## Color Tokens
|
||||||
|
|
||||||
|
| Token | Use |
|
||||||
|
|-------|-----|
|
||||||
|
| `border-border/50` | Section borders, card borders |
|
||||||
|
| `border-border/30` | Subtle dividers, list separators |
|
||||||
|
| `border-border/50` | Sidebar border |
|
||||||
|
| `bg-muted/20` | Section header background |
|
||||||
|
| `bg-muted/40` | Tab bar background |
|
||||||
|
| `bg-sidebar-accent` | Active nav item |
|
||||||
|
| `text-sidebar-foreground/55` | Inactive nav item |
|
||||||
|
| `text-muted-foreground/60` | Section labels |
|
||||||
|
| `text-foreground/90` | Slightly muted body text |
|
||||||
|
|
||||||
|
## What to Avoid
|
||||||
|
|
||||||
|
- ❌ `rounded-md` — use `rounded-lg` for cards, `rounded-md` for small elements
|
||||||
|
- ❌ `shadow-sm` or `shadow-md` on any card or section
|
||||||
|
- ❌ `bg-card/82` — use solid `bg-card` or `bg-transparent`
|
||||||
|
- ❌ `border-border` (full opacity) — always `/50` or `/30`
|
||||||
|
- ❌ `backdrop-blur` on headers
|
||||||
|
- ❌ `<dl>`/`<dt>`/`<dd>` grids for property lists — use flex rows
|
||||||
|
- ❌ `text-[11px] font-semibold uppercase` — use the section label pattern
|
||||||
|
- ❌ Heavy icon usage in section headers — keep icons small (`h-3 w-3`)
|
||||||
@@ -1,5 +1,49 @@
|
|||||||
<!-- BEGIN:nextjs-agent-rules -->
|
# Tessera Design Rules
|
||||||
# This is NOT the Next.js you know
|
|
||||||
|
|
||||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
Follow these rules when writing any frontend code. The design is clean, modern, and minimal — avoid the "2015 admin panel" look.
|
||||||
<!-- END:nextjs-agent-rules -->
|
|
||||||
|
## Borders
|
||||||
|
- **Never** use `border-border` (full opacity). Always `border-border/50` or `border-border/30`.
|
||||||
|
- Section dividers: `border-b border-border/50`
|
||||||
|
- Subtle item separators: `border-b border-border/30`
|
||||||
|
- Card borders: `border border-border/50`
|
||||||
|
|
||||||
|
## Shadows
|
||||||
|
- **Never** use `shadow-sm` or `shadow-md` on cards, sections, or tabs.
|
||||||
|
- The sidebar is the only element with a shadow (subtle).
|
||||||
|
- No `bg-card/82`, no `bg-background/55` — use `bg-card` or `bg-transparent`.
|
||||||
|
|
||||||
|
## Section wrappers
|
||||||
|
- `rounded-lg border border-border/50` — NOT `rounded-md border border-border bg-card shadow-sm`
|
||||||
|
|
||||||
|
## List items
|
||||||
|
- `rounded-lg border border-border/50 p-3`, hover: `hover:border-primary/30 hover:bg-accent/30`
|
||||||
|
- Active: `border-primary/50 bg-primary/5`
|
||||||
|
|
||||||
|
## Nav items (sidebar)
|
||||||
|
- Default: `text-sidebar-foreground/55`, icons at 50% opacity
|
||||||
|
- Hover: `text-sidebar-foreground hover:bg-sidebar-accent/50`, icons at 70%
|
||||||
|
- Active: `bg-sidebar-accent text-sidebar-foreground font-medium`, icons at 90%
|
||||||
|
- No inset shadow on active items
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
- Section headers: `text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60`
|
||||||
|
- No `text-[11px] font-semibold uppercase` anywhere — use the above pattern
|
||||||
|
- Body text: `text-sm` for main, `text-xs` for secondary
|
||||||
|
- Keep font sizes consistent: 10px (labels), 12-13px (content), 14px (headings)
|
||||||
|
|
||||||
|
## Icons
|
||||||
|
- Section headers: `h-3 w-3` (not `h-4 w-4`)
|
||||||
|
- In nav items: `w-4 h-4`
|
||||||
|
- Always dimmed when inactive (opacity-50), bright when active
|
||||||
|
|
||||||
|
## Forms
|
||||||
|
- Inputs/selects: `h-8 rounded-md border border-input bg-transparent px-2.5 text-sm`
|
||||||
|
- No `bg-card` on inputs unless inside a card
|
||||||
|
- Labels: `text-[10px] font-medium text-muted-foreground`
|
||||||
|
|
||||||
|
## General
|
||||||
|
- No `backdrop-blur` on headers or sections
|
||||||
|
- No `bg-card/82` or `bg-background/55` — use solid colors or `bg-transparent`
|
||||||
|
- Content areas: `bg-background/80` for the main page background
|
||||||
|
- Avoid `<dl>`/`<dt>`/`<dd>` grids for properties — use simple `flex justify-between` rows
|
||||||
|
|||||||
Reference in New Issue
Block a user