5.7 KiB
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
# 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.
Custom Endpoints
Custom backend routes are defined in PocketBase hooks via routerAdd(). Example: POST /counter/reset in resetCounter.pb.js.
Adding a New Collection
- Create a migration in
pocketbase/pb_migrations/(next number in sequence) - Run
pnpm pocketbase:typesto regenerateapp/types/pocketbase.types.ts - Create a Pinia store in
app/stores/for the new collection - Use typed collection names from
Collectionsenum inpocketbase.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:
- Navigate to the relevant page with
mcp__playwright__browser_navigate - Use
mcp__playwright__browser_snapshotto inspect the page structure - Interact with UI elements via
mcp__playwright__browser_click,mcp__playwright__browser_fill_form, etc. - 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.tsis auto-generated — do not edit manually, runpnpm pocketbase:types- PocketBase migrations are numbered sequentially (
0_setup.js,1_users_language.js, ...) - PocketBase hooks use
.pb.jsextension - Email templates are plain HTML with
{{.Variable}}Go template syntax