|
| 1 | +# Completo - All the Toppings. None of the Mess. |
| 2 | + |
| 3 | +> **About this file:** CLAUDE.md is for agent guidance — architectural decisions, rules, conventions, and gotchas that can't be inferred from reading code. Don't bloat it with code-level details (file listings, prop docs, full API specs) that agents can discover by reading the source. Focus on the "why", not the "what". |
| 4 | +
|
| 5 | +Kanban board app. Nuxt 4 + Nuxt UI 4 + Tailwind 4 + Drizzle ORM + SQLite. Plus Jakarta Sans + JetBrains Mono. Lucide icons (`i-lucide-*`). pnpm. |
| 6 | + |
| 7 | +```bash |
| 8 | +pnpm install && npx drizzle-kit push && pnpm db:seed && pnpm dev # http://localhost:3000 |
| 9 | +``` |
| 10 | + |
| 11 | +Demo: `demo@example.com` / `demo1234` | Admin: `admin@example.com` / `admin1234` |
| 12 | + |
| 13 | +## Architecture |
| 14 | + |
| 15 | +### The Core Model |
| 16 | + |
| 17 | +**Statuses and cards belong to projects, not views.** Boards and lists are *views* — they don't own data. Cards have a `projectId` + `statusId`; boards see cards through `boardColumns` (junction table). Removing a column from a board just unlinks it — cards survive. Deleting a status cascades everywhere. |
| 18 | + |
| 19 | +**Don't confuse the two kinds of "column":** |
| 20 | + |
| 21 | +| Concept | What it is | DB table | |
| 22 | +|---------|-----------|----------| |
| 23 | +| **Board column** | How a status appears on a board (position) | `boardColumns` | |
| 24 | +| **Field column** | Which card field shows in a list table | `listColumns` | |
| 25 | + |
| 26 | +### Key Design Decisions |
| 27 | + |
| 28 | +- **Done status & retention:** `doneStatusId` + `doneRetentionDays` on projects. Views **filter out** (not delete) old done cards. Card counts exclude done status. `null` retention = keep forever. |
| 29 | +- **Primary keys:** UUIDs everywhere. **Exception:** cards use INTEGER AUTOINCREMENT (for `TK-42` style IDs). Always parse card IDs with `Number()`. |
| 30 | +- **Positions:** Integer `position` field. New items = `max(existing) + 1` (not `.length`). |
| 31 | +- **Password sentinels:** `'!oauth'` = OAuth-only user, `'!invited'` = admin-created pending setup. Both are unhashable. |
| 32 | +- **Synthetic admin role:** API returns `role: 'admin'` for non-member admins viewing projects — don't display it as a real project role. |
| 33 | + |
| 34 | +### Auth & Permissions |
| 35 | + |
| 36 | +- `isAdmin=1` bypasses membership checks via synthetic `{ role: 'owner' }`. My Tasks is NOT admin-elevated. |
| 37 | +- **404 not 403** for non-member access (don't leak resource existence). |
| 38 | +- **IDOR prevention:** Every card/tag/board endpoint validates resources belong to the correct project. |
| 39 | +- **No email in search results** — user search returns name only. |
| 40 | +- Domain allowlist restricts self-registration only — invitations and admin-created users bypass it. |
| 41 | +- Login requires verified email. `isEmailEnabled()` checks `SMTP_HOST`. |
| 42 | + |
| 43 | +### Email Templates (Gotchas) |
| 44 | + |
| 45 | +- Table-based layout only (no flexbox/grid) |
| 46 | +- Solid hex colors only (no `rgba()` — breaks in 21% of clients) |
| 47 | +- No `border-radius` on buttons (use VML `v:roundrect` for Outlook) |
| 48 | + |
| 49 | +## Conventions |
| 50 | + |
| 51 | +### Do |
| 52 | + |
| 53 | +- **Fetch:** Pages use `useFetch()`, composables use `$fetch()`. Refresh after mutations. |
| 54 | +- **Composables:** `useKanban()`/`useListView()` accept slug-or-ID + optional `{ projectSlug }` to prevent cross-project slug collisions. |
| 55 | +- **Transactions:** `db.transaction()` for multi-step DB operations. |
| 56 | +- **SSR:** `ssr: true` with `routeRules`. Auth routes `ssr: false`. vuedraggable via `defineAsyncComponent` + `<ClientOnly>`. |
| 57 | +- **DB access:** `db` + `schema` auto-imported. `server/utils/` is auto-imported — don't use `~/server/utils/...` (Nuxt 4: `~` = `app/`). |
| 58 | +- **Database schema:** `server/database/schema.ts` — all tables, columns, relations. Migrations in `server/database/migrations/`. |
| 59 | +- **OpenAPI spec** (`server/api/openapi.get.ts`) must stay in sync with endpoints. Only covers headless API usage — frontend-internal endpoints (slug/key validation, UI column config, notifications, OAuth redirects, registration flows) are intentionally omitted. |
| 60 | +- **Write tests** for new features. Run `pnpm test` after changes. |
| 61 | + |
| 62 | +### Don't |
| 63 | + |
| 64 | +- **Don't use `theme()` in scoped CSS** — Tailwind v4 uses `var(--color-*)`. |
| 65 | +- **Don't use CSS `ring-*` for tag pills** — they use `box-shadow` inset borders. |
| 66 | +- **Don't use native `<input type="date">`** — use `UPopover` + `UCalendar` + `CalendarDate`. |
| 67 | +- **Don't use `document.createElement('input')` for file pickers** — use a persistent hidden `<input>` ref. |
| 68 | +- **Don't use `@keydown` on forms for Cmd+Enter** — portals break it. Use global `document` listener with `capture: true`. |
| 69 | +- **Don't close the browser** during Playwright MCP sessions. Screenshots go in `.playwright/`, clean up after. |
| 70 | + |
| 71 | +### Styling |
| 72 | + |
| 73 | +- **Aesthetic:** "Trello meets Linear" — indigo-violet primary, zinc neutrals. |
| 74 | +- **Priority icons:** `alert-circle`=urgent, `chevron-up`=high, `grip-horizontal`=medium, `chevron-down`=low. Colors: red/orange/indigo/slate. Helpers in `app/utils/constants.ts`. |
| 75 | +- **Due date colors:** red=overdue, orange=due-soon (today/tomorrow), slate=future. |
| 76 | +- **Ticket IDs:** `{projectKey}-{cardId}`, sits above card title (not in footer). |
| 77 | +- **Destructive actions:** Wrapped in `<UTooltip>`. Views/projects use type-name-to-confirm; cards use simple confirm. |
| 78 | +- **ESLint:** No comma dangles, 1tbs brace style. |
| 79 | + |
| 80 | +## Testing |
| 81 | + |
| 82 | +Two vitest projects: `unit` (fast) + `integration` (sequential, 30s timeout). Test DB on `:43210`. |
| 83 | + |
| 84 | +**Gotchas:** |
| 85 | +- `fetch(url('/path'))` for raw responses (ofetch throws on non-2xx) |
| 86 | +- `randomKey()` in fixtures to avoid 409 conflicts |
| 87 | +- `process.env.NODE_ENV` inlined at build — use custom env vars for runtime gating |
| 88 | +- Kill stale test server: `lsof -ti:43210 | xargs kill -9` |
| 89 | + |
| 90 | +## Environment & Commands |
| 91 | + |
| 92 | +`NUXT_SESSION_PASSWORD` is the only required env var (min 32 chars). See `.env.example` for the rest. Key vars: `DATABASE_URL` (default `sqlite.db`), `AI_PROVIDER` (`anthropic`/`openai`/`openrouter`, empty=disabled), `SMTP_HOST` (empty=email disabled), `UPLOAD_DIR` (default `data/uploads`), `NUXT_OAUTH_*_CLIENT_ID/SECRET` (empty=provider disabled). |
| 93 | + |
| 94 | +```bash |
| 95 | +pnpm dev / build / test / lint / typecheck |
| 96 | +pnpm db:migrate / db:seed / db:cleanup |
| 97 | +pnpm user:create <email> <password> [name] [admin] |
| 98 | +pnpm user:set-role <email> <admin|user> |
| 99 | +pnpm user:verify-email <email> |
| 100 | +npx drizzle-kit push # Dev only — diffs schema against DB |
| 101 | +npx drizzle-kit generate # Generate migration SQL from schema changes |
| 102 | +``` |
| 103 | + |
| 104 | +### Schema Changes & Migrations |
| 105 | + |
| 106 | +`drizzle-kit push` = dev (no migration files). `pnpm db:migrate` = production (applies SQL from `server/database/migrations/`). |
| 107 | + |
| 108 | +**After every schema change:** edit `schema.ts` → `npx drizzle-kit generate` → commit the migration. If you skip generating, production deploys will fail. |
| 109 | + |
| 110 | +**`scripts/package.json`** is a deploy manifest for CLI tools. When changing imports in scripts, update it to keep deps in sync. |
| 111 | + |
| 112 | +## Documentation |
| 113 | + |
| 114 | +- Nuxt: https://nuxt.com/llms.txt |
| 115 | +- Nuxt UI: https://ui.nuxt.com/llms.txt |
| 116 | +- Nuxt Auth Utils: https://raw.githubusercontent.com/atinux/nuxt-auth-utils/refs/heads/main/README.md |
0 commit comments