# 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