# CLAUDE.md

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

## Project Overview

Full-stack starter template: **Nuxt 4 SPA** frontend + **PocketBase** backend + **Capacitor** mobile (Android/iOS) + **Deno sidecar** for FCM push notifications. Designed as a copy-and-customize base for shipping apps fast.

**SSR is disabled** (`ssr: false`) — this is a pure SPA. There are no Nuxt server routes.

## Commands

```bash
# Frontend
pnpm dev                  # Start Nuxt dev server (localhost:3000)
pnpm build                # Production build
pnpm lint                 # ESLint
pnpm typecheck            # nuxt typecheck (vue-tsc)
pnpm test                 # Vitest (unit/e2e/nuxt projects)

# Backend
pnpm pocketbase:start     # docker compose up (PocketBase + sidecar + mailpit)
pnpm pocketbase:stop      # docker compose down
pnpm pocketbase:types     # Regenerate TypeScript types from PocketBase schema

# Mobile
pnpm assets               # Generate app icons and splash screens
```

**Dev workflow:** Run `pnpm pocketbase:start` first, then `pnpm dev` in a second terminal.

**Mailpit** (dev email viewer) runs at `http://localhost:8025` when PocketBase is up.

## Architecture

```
app/                      # Nuxt frontend (SPA)
├── pages/                # File-based routing (index, login, profile, confirm)
├── stores/               # Pinia stores (user, counter, notifications, avatar)
├── plugins/pocketbase.ts # PB client init with ReactiveAuthStore
├── composables/          # usePocketBase() → { pb, authStore }
├── middleware/            # auth guard + global user fetch
├── components/           # App/, Counter/, Profile/ subdirs
├── i18n/locales/         # en.json, de.json (frontend translations)
└── types/                # pocketbase.types.ts (auto-generated), i18n.types.ts

pocketbase/
├── pb_migrations/        # Numbered JS migrations (0-4)
├── pb_hooks/             # Server-side JS hooks (OTP user creation, mail templates, FCM, custom endpoints)
│   ├── templates/        # Localized HTML email templates (en/, de/)
│   └── locales/          # Backend translation JSON (mail subjects, notification text)
├── sidecar/              # Deno microservice for FCM push (:8091)
└── docker-compose.yml    # pocketbase + sidecar + mailpit
```

## Key Patterns

### Authentication
Three methods: **Email OTP** (primary), **Google OAuth**, **Apple OAuth**. Flow lives in `app/stores/user.ts`. PocketBase hooks auto-create users on first OTP request (`pb_hooks/createOtpUser.pb.js`). OAuth redirect lands on `/confirm` page.

### Reactive Auth
`app/plugins/pocketbase.ts` wraps PocketBase's `LocalAuthStore` in a Vue-reactive `ReactiveAuthStore` using `shallowRef`. Access via `usePocketBase()` composable — never instantiate PocketBase directly.

### Realtime Subscriptions
Stores (`counter.ts`, `notifications.ts`) use `pb.collection('x').subscribe('*', callback)` for live updates. Always call `unsubscribe()` on component unmount.

### PocketBase Hooks
Hooks in `pocketbase/pb_hooks/` run inside PocketBase's embedded JS engine (not Node/Deno). They use PocketBase's JSVM API — `onMailerRecordOTPSend`, `routerAdd`, `$app.findFirstRecordByFilter`, etc. See [PocketBase JSVM docs](https://pocketbase.io/docs/js-overview/).

### Custom Endpoints
Custom backend routes are defined in PocketBase hooks via `routerAdd()`. Example: `POST /counter/reset` in `resetCounter.pb.js`.

### Adding a New Collection
1. Create a migration in `pocketbase/pb_migrations/` (next number in sequence)
2. Run `pnpm pocketbase:types` to regenerate `app/types/pocketbase.types.ts`
3. Create a Pinia store in `app/stores/` for the new collection
4. Use typed collection names from `Collections` enum in `pocketbase.types.ts`

### Localization
Frontend: `@nuxtjs/i18n` with `no_prefix` strategy (en, de). Backend: per-user `language` field drives email template and notification text selection. When adding a new language, update both `app/i18n/locales/` and `pocketbase/pb_hooks/locales/` + `templates/`.

## Tech Stack Quick Reference

| Layer | Tech |
|-------|------|
| Frontend | Nuxt 4, Vue 3, Pinia, Nuxt UI, Tailwind CSS v4 |
| Backend | PocketBase (SQLite, REST, Realtime WS) |
| Mobile | Capacitor (Android/iOS), Push Notifications |
| Sidecar | Deno (FCM auth + push delivery, port 8091) |
| Email (dev) | Mailpit (SMTP + web UI) |
| Validation | Zod |
| Package mgr | pnpm (monorepo via pnpm-workspace.yaml) |
| Testing | Vitest + @nuxt/test-utils + Playwright |

## Environment

Copy `.env.example` to `.env`. Key vars: `NUXT_PUBLIC_POCKETBASE_URL`, `SUPERUSER_EMAIL`/`SUPERUSER_PW`, OAuth client IDs/secrets, `SIDECAR_SECRET`, `GOOGLE_CREDENTIALS_JSON` (for FCM).

## Validating Changes

After making changes to the frontend, **always validate them using the Playwright MCP server** by interacting with the app as a user would. The dev server runs at `http://localhost:3000`.

Typical validation flow:
1. Navigate to the relevant page with `mcp__playwright__browser_navigate`
2. Use `mcp__playwright__browser_snapshot` to inspect the page structure
3. Interact with UI elements via `mcp__playwright__browser_click`, `mcp__playwright__browser_fill_form`, etc.
4. Confirm the expected behaviour is visible in the snapshot or page state

This applies to any visible UI change — new components, routing changes, form flows, auth states, etc.

## File Conventions

- `app/types/pocketbase.types.ts` is **auto-generated** — do not edit manually, run `pnpm pocketbase:types`
- PocketBase migrations are numbered sequentially (`0_setup.js`, `1_users_language.js`, ...)
- PocketBase hooks use `.pb.js` extension
- Email templates are plain HTML with `{{.Variable}}` Go template syntax
