Files
oysqn.app/CLAUDE.md

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 against types/supabase.ts

Edge Functions

  • Located in supabase/functions/<name>/ — each function has its own deno.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:
    sudo chcon -Rt container_file_t $(pwd)
    
    This must be applied after any git clone or 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 useAppCache on success and read from it when offline.
  • Composables: useAppCache (localStorage, 24h TTL), useOfflineStatus (reactive isOnline)
  • 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 keys intervals:{monday} and slots:{monday}.
  • Realtime: app/app.vue subscribes to reservations, intervals, and boats changes and patches the cache in real time. When adding a new table subscription, add it to the app-cache-sync channel in app.vue.
  • Cross-page navigation state (not persistence): use useBookingDraft as a pattern — module-level ref set by the source page, consumed (take()) by the destination page. Do not use query params for structured objects.

Rules

  1. Do not mix unrelated project contexts in one session.
  2. 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.
  3. Before compaction or session end, write to disk: every number, every decision with rationale, every open question, every file path, exact next action.
  4. When switching work types (research → writing → review), write a handoff to docs/summaries/handoff-[date]-[topic].md and suggest a new session.
  5. Do not silently resolve open questions. Mark them OPEN or ASSUMED.
  6. Do not bulk-read documents. Process one at a time: read, summarize to disk, release from context before reading next.
  7. 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.