devist 0.15.0

Project bootstrap CLI for AI-assisted development. Spin up new projects from templates, manage backends, and keep your codebase comprehensible.
# 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.