# CLAUDE.md — website
> Project-specific guidance for Claude Code (`claude.ai/code`).
> This is a guide, not a spec. Read the code for ground truth.
## Project
devist worker dashboard — read-only web UI for `worker_events` pushed by
the devist worker daemon to Supabase. Lives inside the main devist repo
at `website/`, ships with the same release cadence.
## Working in this codebase
This sub-project follows the **devist workflow**.
### Daily commands
```bash
cd website
pnpm dev # Vite dev server
pnpm typecheck
pnpm lint:fix
pnpm build # production build to dist/
```
The dashboard reads from the same Supabase project the worker pushes to.
Local development uses the local Supabase started by
`devist start <some-project>`. `.env.local` holds URL + anon key.
## Folder layout — what goes where
```
website/
├── src/
│ ├── App.tsx Root component. Owns routing + auth gate. Keep slim.
│ ├── main.tsx Vite entry. Don't put logic here.
│ ├── index.css Tailwind v4 CSS-first config (@theme, @plugin).
│ ├── types.ts Shared TS types that mirror DB shapes. Update before pages.
│ ├── vite-env.d.ts Env var types.
│ │
│ ├── contexts/ React Contexts. Cross-cutting state (auth, theme, settings).
│ │ One context per file. Provider + hook colocated.
│ │
│ ├── lib/ Non-React modules. Pure or framework-agnostic.
│ │ - supabase client, query functions, realtime hooks,
│ │ utility helpers, third-party adapters.
│ │ - No JSX. No React imports beyond `useEffect`/hooks.
│ │
│ ├── hooks/ Custom React hooks that don't fit lib/ (touch DOM/lifecycle).
│ │ Create on demand.
│ │
│ ├── components/ Reusable presentational components.
│ │ - Layouts, rows, cards, banners, controls.
│ │ - Take props, render. Avoid fetching here — pages do that.
│ │
│ ├── components/ui/ shadcn/ui primitives. DO NOT hand-edit.
│ │ Add via `pnpm dlx shadcn@latest add <name>`.
│ │ If you need a variant, wrap in components/ instead.
│ │
│ └── pages/ Route-level components. One file per top-level URL.
│ - Owns its data fetching and local state.
│ - Composes components/ + lib/.
```
When in doubt where a new file goes:
- Renders JSX and is reused across pages → `components/`
- Renders JSX, owns a route → `pages/`
- No JSX, async data work → `lib/`
- No JSX, React lifecycle/state hook → `hooks/`
- Cross-cutting state needed by many components → `contexts/`
- Type definition shared across files → `types.ts` (or `types/<domain>.ts` if it grows)
### Conventions
- **Read-only by design** for non-ack columns. Service-role writes happen
from the Rust worker, never the browser. The only client-side write is
`acked_at` (column-level GRANT enforced server-side).
- **Realtime is opt-in per page.** Pages decide when to subscribe.
- **Optimistic UI for writes.** Update local state first, then call the
server. Surface failures with a banner.
- **Tailwind v4 CSS-first.** Tokens live in `src/index.css` under `@theme`.
No `tailwind.config.js`.
- **shadcn/ui only as needed.** Don't pre-import components you don't use.
- **Biome for lint+format.** Match the surrounding `import` order; Biome
organizes them.
### Adding a new column the dashboard renders
When the chain works, all five layers stay consistent:
1. **Migration** — new column in a new SQL file under `../migrations/000N_*.sql`.
2. **Worker writes it** — Rust daemon populates it (`src/worker/daemon.rs` or wherever the event is built).
3. **Supabase push includes it** — `src/worker/supabase.rs EventRow` shape.
4. **`types.ts` mirrors the new shape.**
5. **A page renders it.**
Skip any layer and one of the slices is silently wrong.
### What Claude should do
1. **Read before writing.** Skim the relevant files. Check git log for context.
2. **Match the surrounding style.**
3. **Type everything.** `pnpm typecheck` before claiming done.
4. **One feature at a time.** Don't touch unrelated files in a "while I'm here" sweep.
5. **Realtime + optimistic UI must agree.** When you add a write, also handle
the realtime UPDATE that comes back (idempotent merge by `id`).
### What Claude should NOT do
- ❌ Add a global state library (Zustand, Redux). Local state + page-level fetches are intentional.
- ❌ Add server-state libs (TanStack Query, SWR) without asking — only if the
fetch surface justifies it.
- ❌ Add tests without asking — the worker has the test surface; the dashboard
is intentionally manual-smoke for now.
- ❌ Touch `.env*` or commit them.
- ❌ Generate planning docs / summaries / READMEs unless explicitly asked.
- ❌ Create a sibling repo or move code out of `website/`.
### Worker context
The Rust worker daemon (`../src/worker/`) populates everything this dashboard
reads. If a feature needs a new field, follow the 5-step chain above.
## When in doubt
- The user is technical; explain decisions, don't justify them.
- Match existing conventions over introducing new ones.
- If an action is destructive (delete, force-push, drop table), ask first.
- Read `package.json` for the stack rather than maintaining a duplicate list here.