6.1 KiB
CLAUDE.md
Session Start
Read the latest handoff in docs/summaries/ if one exists. Load only the files that handoff references — not all summaries. If no handoff exists, ask: what is the project, what type of work, what is the target deliverable.
Before starting work, state: what you understand the project state to be, what you plan to do this session, and any open questions.
Identity
You work with Patrick, a Solutions Architect, on the OYS Borrow a Boat app (oysqn.app) — a mobile-first PWA for managing a Borrow a Boat program for a Yacht Club. Backend is Supabase.
Project Overview
- App: OYS Borrow a Boat (oysqn.app) — rewrite of bab-app
- Stack: Nuxt 4 (SSR=false), Ionic Vue (@ionic/vue), PrimeVue 4, TypeScript, Supabase (BaaS)
- Purpose: Manage a Borrow a Boat program for a Yacht Club
- Personas: BAB Member, Certified Skipper, Program Administrator, Boatswain, Volunteer, Instructor
- Docs: docs/planning/ contains personas, user/role/permission model
Architecture
App Shell
app/app.vue— IonApp + IonMenu + IonRouterOutlet (NO NuxtLayout/NuxtPage)- Each page is self-contained: IonPage > IonHeader > IonContent
- No Nuxt layout system — layouts handled at the page level via IonPage
app/plugins/ionic.client.ts— installs IonicVue with mode: 'md'
Routing
- Nuxt file-based routing → Vue Router → IonRouterOutlet handles transitions
app/middleware/auth.ts— global auth guard via useSupabaseUser()- Auth pages use
definePageMeta({ layout: false })(effectively a no-op with IonRouterOutlet, but documents intent)
Auth
- Supabase Auth — magic link + OTP only (no password auth)
useSupabaseUser()composable (from @nuxtjs/supabase)app/stores/auth.ts— isAdmin computed from user_metadata.role
Backend
- Supabase (replaces Appwrite)
- Types in
types/supabase.ts— regenerate with:npx supabase gen types typescript --project-id YOUR_ID > types/supabase.ts useSupabaseClient<Database>()typed againsttypes/supabase.ts
Edge Functions
- Located in
supabase/functions/<name>/— each function has its owndeno.json - Auth pattern: extract Bearer token →
adminClient.auth.getUser(token)(pass JWT directly to service-role client). Do NOT create a separate userClient with the anon key. - Use
SUPABASE_SERVICE_ROLE_KEY(adminClient) for all DB operations inside functions; the caller's identity comes from JWT claims (claims.sub= user ID). - SELinux (Fedora/RHEL local dev): Before running
supabase functions serve, label the project directory for container access:This must be applied after anysudo chcon -Rt container_file_t $(pwd)git cloneor directory move. Failure symptom: function bootstrap error with no useful stderr output.
Icons
- Ionicons only (
ionicons/icons) — no PrimeIcons - Always import individual icon names from
ionicons/icons(tree-shakeable)
Offline Cache
- Rule: Every table/view read from Supabase must be written to
useAppCacheon success and read from it when offline. - Composables:
useAppCache(localStorage, 24h TTL),useOfflineStatus(reactiveisOnline) - Pattern for any data fetch:
const cache = useAppCache() const { isOnline } = useOfflineStatus() if (!isOnline.value) { const cached = cache.peek<T>('my-key') if (cached) data.value = cached return } const { data: fresh } = await supabase.from('my_table').select('*') data.value = fresh ?? [] if (fresh) cache.set('my-key', fresh) - Schedule data is keyed by ISO week Monday:
cache.weekKey(utcIso)→ use keysintervals:{monday}andslots:{monday}. - Realtime:
app/app.vuesubscribes toreservations,intervals, andboatschanges and patches the cache in real time. When adding a new table subscription, add it to theapp-cache-syncchannel inapp.vue. - Cross-page navigation state (not persistence): use
useBookingDraftas a pattern — module-levelrefset by the source page, consumed (take()) by the destination page. Do not use query params for structured objects.
Rules
- Do not mix unrelated project contexts in one session.
- Write state to disk, not conversation. After completing meaningful work, write a summary to docs/summaries/ using templates from templates/claude-templates.md. Include: decisions with rationale, exact numbers, file paths, open items.
- Before compaction or session end, write to disk: every number, every decision with rationale, every open question, every file path, exact next action.
- When switching work types (research → writing → review), write a handoff to docs/summaries/handoff-[date]-[topic].md and suggest a new session.
- Do not silently resolve open questions. Mark them OPEN or ASSUMED.
- Do not bulk-read documents. Process one at a time: read, summarize to disk, release from context before reading next.
- Sub-agent returns must be structured, not free-form prose. Use output contracts from templates/claude-templates.md.
Where Things Live
- templates/claude-templates.md — summary, handoff, decision, analysis, task, output contract templates (read on demand)
- docs/summaries/ — active session state (latest handoff + project brief + decision records)
- docs/context/ — reusable domain knowledge
- docs/planning/ — original planning documents (personas, roles/permissions)
- types/supabase.ts — Supabase generated types (placeholder until Supabase project configured)
- app/ — Nuxt app source (Nuxt 4 structure)
- app/plugins/ionic.client.ts — Ionic Vue plugin
- app/stores/ — Pinia stores (Supabase-backed)
- app/composables/ — shared composables
Error Recovery
If context degrades or auto-compact fires unexpectedly: write current state to docs/summaries/recovery-[date].md, tell the user what may have been lost, suggest a fresh session.
Before Delivering Output
Verify: exact numbers preserved, open questions marked OPEN, output matches what was requested (not assumed), claims backed by specific data, output consistent with stored decisions in docs/context/, summary written to disk for this session's work.