Skip to content

Latest commit

 

History

History
62 lines (40 loc) · 5.7 KB

File metadata and controls

62 lines (40 loc) · 5.7 KB

AGENTS.md

This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.

Project

Coffee Club is a map-first cafe discovery and community moderation app for Islamabad, built on TanStack Start (React 19, SSR) with Supabase persistence. Public users browse an interactive Leaflet map filtered by features/vibes/hours, search venues, recommend new spots, and post notes/photo "memories". All community content is moderated: submissions start pending and only approved content reaches the public map. A token-gated /admin dashboard handles review.

Commands

npm run dev          # Vite dev server on port 3000
npm run build        # production build
npm run preview      # preview the build
npm run test         # Vitest (jsdom) — run once
npm run check        # Biome format + lint + organize-imports (the canonical check)
npm run lint         # Biome lint only
npm run format       # Biome format only
npm run seed:supabase  # upsert seed-data.ts into Supabase (needs server creds)

Run a single test: npx vitest run src/lib/types.test.ts or filter by name with npx vitest run -t "name". Use npx vitest (no run) for watch mode.

Biome uses tab indentation and double quotes; it ignores src/routeTree.gen.ts and src/styles.css. The editor auto-organizes imports on save. Run npm run check before considering work done — there is no separate tsc step in the scripts (type-checking happens via the IDE / build).

Environment & data backend

The data layer auto-selects its backend at runtime in coffee-db.server.ts:

  • Supabase is used when VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY, and SUPABASE_SERVICE_ROLE_KEY are all set. Server code uses the service-role client (RLS-bypassing) for all reads/writes.
  • Local JSON fallback (.local-data/coffee-club.json, seeded from src/lib/seed-data.ts) is used in development when Supabase is not configured. In production, missing Supabase credentials throw — the local store is never a production backend.

COFFEE_CLUB_ADMIN_TOKEN gates all admin server functions via assertAdminAccess. Copy .env.example to configure. The admin UI stores the entered token in localStorage (coffee-club-admin-token) and sends it with every admin request — there is no real auth/session yet.

Architecture

Data flows client component → TanStack Query → server function → data-access layer → Supabase or local JSON. Keep these layers distinct:

  • src/lib/coffee.functions.ts — TanStack Start server functions (createServerFn). The RPC boundary. Each validates input with a Zod schema (inputValidator) then delegates to the data layer. This is the only thing client code calls for data.
  • src/lib/coffee-db.server.ts.server.ts data-access layer (never imported by client). Owns backend selection, Supabase queries, the local-store read/write, row↔domain mapping (snake_case DB rows → camelCase domain types), moderation filtering, asset upload, and admin_audit_log writes. Public reads return only approved content; admin reads include all statuses.
  • src/lib/schemas.ts — Zod schemas for every server-function input. parseLocationActivityFormData handles the multipart (file upload) path specially.
  • src/lib/types.ts — domain types plus the shared pure filter functions (filterLocations, filterCommunityPins, isWithinBounds) used by both the data layer and the client, and the tag/filter taxonomy (LOCATION_TAG_DEFINITIONS, FILTER_GROUPS). LocationFeatureKey (the hasWifi/hasWork/… union) is the single source of truth for filterable features — adding a feature touches this union, the definitions array, the DB row types, and the migration.
  • src/lib/query-options.tsqueryOptions factories shared by route loaders and components, keeping query keys/staleTimes consistent.

Routing & rendering

File-based routes in src/routes/ (__root.tsx, index.tsx, admin.tsx); routeTree.gen.ts is generated — never edit it (it's also marked read-only in VS Code). The router (src/router.tsx) wires TanStack Query into SSR via setupRouterSsrQueryIntegration, so route loaders call ensureQueryData to prefetch and components read the same query with useSuspenseQuery/useQuery.

Components

src/components/coffee-club/ holds the UI. Two big stateful screens: home-page.tsx (public map, filters, cards, submission modals) and admin-page.tsx (moderation dashboard). map-panel.client.tsx uses the .client.tsx suffix because Leaflet is browser-only — it's loaded via lazy/ClientOnly and must never run during SSR. Keep Leaflet/window-dependent code inside client-only boundaries.

Supabase schema

Migrations in supabase/migrations/: ..._init_coffee_club.sql (base tables) and ..._backend_admin_pipeline.sql (moderation fields, profiles, roles, claims, lists, reports, audit log, and RLS policies). scripts/seed-supabase.ts upserts seed-data.ts and calls the sync_coffee_club_sequences RPC to fix ID sequences after manual-ID inserts. When changing a table shape, update in lockstep: the SQL migration, the *Row types + mappers in coffee-db.server.ts, the domain type in types.ts, and the seed script.

Conventions

  • Path alias #/*src/* (also @/*); prefer #/… imports as the codebase does.
  • Server-only modules use the .server.ts suffix and must not be imported into client bundles.
  • DB columns are snake_case; domain types are camelCase — convert only in the mapper functions in coffee-db.server.ts.
  • Geocoding (geocode.server.ts) calls the public OpenStreetMap Nominatim API and biases queries to Islamabad; it has no API key and is rate-limited, so avoid hammering it in loops.