Files
shiftcraft/CLAUDE.md
2026-04-17 23:26:01 +00:00

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

  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