# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

Saturn — "the operating system for your freelance business." A Next.js app that unifies projects, requirements, proposals, milestones, timesheets, expenses, invoices, and client management for freelancers and agencies. Clients register on the same platform (no separate portal) and are scoped to what they're allowed to see.

Read `CONTEXT.md` for domain semantics (custom fields, invoice time units, settings cascades, permissions). It is the source of truth for business rules and is kept current — consult it before touching timesheets, invoices, or custom fields.

## Commands

Package manager is **bun**.

```bash
bun dev                  # dev server
bun build                # production build
bun typecheck            # tsc --noEmit (run this to verify types; there are no tests)
bun check                # ultracite (biome) lint
bun check:write          # auto-fix lint/format
bun check:spelling       # cspell — new domain words go in .cspell.json

bun db:generate          # generate migration from schema changes
bun db:migrate           # apply migrations
bun db:studio            # drizzle studio
bun auth:generate        # regenerate src/server/db/schema/auth.ts from better-auth config
bun email:dev            # preview React Email templates
```

There is no test runner. Verify changes with `bun typecheck` and `bun check`.

## Architecture

### The layering rule (non-negotiable)

```text
page.tsx / page.client.tsx  →  services / actions  →  db
```

- **`page.tsx` must never import `db` or call Drizzle directly.** Server components fetch data only through service objects.
- **Services** live in `src/app/api/<domain>/service.ts`, exported as a single object (e.g. `projectsService`, `teamService`, `timesheetService`). They own all read queries and shared write logic. Pages and actions call services.
- **`src/services/`** is for infrastructure integrations only (`s3.service.ts`, `email.service.ts`, `analytics.service.ts`, `invoice-number.service.ts`), not domain data access.
- **Mutations** go through Server Actions in a colocated `actions.ts`, always built with **next-safe-action**.

### Server Actions

Actions are defined with `authedActionClient` (or `actionClient`) from `src/lib/safe-action.ts`:

```ts
export const doThingAction = authedActionClient
  .inputSchema(someZodSchema)   // schema lives in the route's common.ts
  .action(async ({ parsedInput, ctx: { user, role, orgMember } }) => {
    if (!role.authorize({ resource: ['create'] }).success) {
      throw new Error('You do not have permission to ...')
    }
    // ...
  })
```

- `authedActionClient` injects `ctx`: `user`, `session`, `orgMember`, and `role`. Authorize every mutation with `role.authorize(...)` and verify org ownership of inputs.
- Input/output Zod schemas live in the route's `common.ts`; types in `types.ts`.
- On the client, invoke actions via `next-safe-action` hooks (`useAction` / `useHookFormAction`), never by calling the action as a bare function.

### Buttons and loading state

The `Button` (`src/components/ui/button.tsx`) has a `loading` prop that swaps children for a spinner. Use it:

```tsx
<Button loading={isPending}>Save</Button>
```

Do **not** hand-roll `{isPending ? <Spinner/> : 'Save'}` in children.

### Routing

App Router with route groups: `(root)` (marketing, auth, dashboard, onboarding), `(organization)/[org]` (workspace-scoped), `(project)/[org]/[project]` (project-scoped). Org/project context is resolved through cache helpers (e.g. `resolveOrgContext` in `(organization)/[org]/cache.ts`) — reuse them rather than re-querying. Per-route convention: `page.tsx` (server), `page.client.tsx`, `actions.ts`, `common.ts` (schemas), `types.ts`, `_components/`.

### Data & auth

- **DB**: Drizzle ORM + Postgres. Schema is split by domain under `src/server/db/schema/`; `auth.ts` is generated by better-auth — edit the auth config, not that file.
- **Auth**: Better Auth with the organization plugin. Roles are owner / admin / member / client; permission matrix in `src/server/auth/permissions.ts`.
- **Content**: marketing blog/changelog via content-collections (`content/`, `content-collections.ts`).

## Code quality

This codebase will outlive you. Every shortcut becomes someone else's burden; every hack compounds into debt that slows the whole team. The patterns you establish get copied — fight entropy and leave the code better than you found it.

Concretely:

- **No verbose comments.** Comment only the non-obvious *why*. Don't narrate what the code already says.
- **A `.tsx` file defines its component and nothing else** — the component, its props/local subcomponents, that's it. Don't bolt unrelated helpers onto it.
- **Don't litter the code with random util functions.** A helper earns its place in `src/lib/` only when it's genuinely shared and named for what it does. Don't scatter one-off utilities.
- Match the surrounding code's style. Formatting is enforced by biome/ultracite (single quotes, no semicolons, sorted Tailwind classes via `cn`). `src/components/ui` and `drizzle/` are lint-excluded — don't reformat them.
