8.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Common Commands
Development
pnpm install # Install dependencies
pnpm dev # Run all apps in development
pnpm dev:main # Run main app only (port 5173)
pnpm dev:external # Run external booking widget (port 5174)
pnpm dev:api # Run API server (port 8080)
Building
pnpm build # Build all apps
pnpm build:apps # Build apps only (packages are source-only)
pnpm build:staging # Build main app for staging
pnpm build:prod # Build main app for production
Testing
pnpm test # Run all tests
pnpm test:watch # Run tests in watch mode
pnpm test:api # Run API tests only
cd apps/main && pnpm test # Run tests for specific package
Quality Checks
pnpm lint # Check all packages with Biome
pnpm lint:fix # Fix linting issues
pnpm typecheck # Type check everything
pnpm format # Format code
Cleanup
pnpm clean # Clean all build artifacts and caches
Architecture Overview
Monorepo Structure
This is a Turborepo-based monorepo with three main apps and shared packages:
- apps/main (
@xtablo/main): Primary authenticated dashboard with tablos, planning, events, chat, and notes - apps/external (
@xtablo/external): Public booking widget (embeddable/floating modes) - apps/api (
@xtablo/api): Hono-based REST API serving both frontend apps - packages/shared (
@xtablo/shared): React contexts, hooks, API client, React Query setup - packages/ui (
@xtablo/ui): Radix UI + Tailwind component library - packages/shared-types (
@xtablo/shared-types): Pure TypeScript types (zero runtime dependencies)
Source-Only Packages
The @xtablo/shared and @xtablo/ui packages are source-only - they export TypeScript directly without a build step. Changes are instantly reflected via Vite's HMR. There's no need to rebuild or watch these packages during development.
Key Architectural Patterns
State Management
-
React Query (TanStack Query v5): Primary tool for server state
- 5-minute default cache time
- Hierarchical query keys:
["tablos"],["tablos", id],["tablo-files", tabloId] - Targeted cache invalidation on mutations
-
Zustand: Global client state, especially user context
- User fetched via React Query, stored in Zustand for app-wide access
- Two hooks:
useUser()(throws if no session) anduseMaybeUser()(returns null)
Authentication & Sessions
- Supabase Auth with JWT tokens
SessionContextlistens tosupabase.auth.onAuthStateChange()- API validates JWT from Authorization header
- Passwordless flow generates temporary accounts (
is_temporary: true) - Protected routes use
useMaybeUser()to check authentication
API Architecture
- Framework: Hono (edge-runtime compatible)
- Middleware Manager: Singleton pattern (
initializeMiddleware()called once, reused) - Router Order: Public routes first → middleware applied (supabase → stream/r2/email) → authenticated routes
- Key Routers:
public.ts: Unauthenticated endpointsauthRouter.ts: Requires authenticationmaybeAuthRouter.ts: Optional authenticationtablo.ts,tablo_data.ts: Core business logicstripe.ts: Payment webhooks and operations
Database & Types
- Supabase PostgreSQL with auto-generated types
- Generate types:
npx supabase gen types typescript > packages/shared-types/src/database.types.ts - Type hierarchy:
database.types.ts(auto-generated) → domain types (nulls removed) → API responses - @xtablo/shared-types: Zero-dependency package for all types
- Frontend uses direct Supabase client:
supabase.from("table").select() - API uses service role key to bypass RLS when needed
Data Fetching Patterns
- Direct Supabase queries:
useQuery()→supabase.from("table").select().eq(...) - API calls:
useQuery()→api.get("/api/v1/...")with Bearer token - File operations: Specialized hooks (
useUploadTabloFile,useDeleteTabloFile) with automatic cache invalidation
Routing
- Main app: Two route sets
publicRoutes: Auth pages, legal pages (outside UserStoreProvider)routes: Protected app routes (inside UserStoreProvider)
- Protected routes: Component checks
useMaybeUser(), redirects to landing if unauthenticated - External app: Query params control mode (
?mode=embed&eventTypeId=...)
Component Organization
- Modals:
*Modal.tsx - Sections:
*Section.tsx - Cards:
*Card.tsx - Tests co-located:
*.test.tsx - Shared UI in
@xtablo/ui - Business logic hooks in
@xtablo/shared
External Integrations
Stream Chat
- Chat provider wraps routes
- Users authenticate with
streamTokenfrom API
Stripe
- Webhooks:
/api/v1/stripe-webhook - Sync engine keeps Supabase ↔ Stripe in sync
- See
docs/STRIPE_*.mdfor detailed documentation
Storage
- Files stored in Cloudflare R2 (S3-compatible)
- AWS S3 SDK for uploads/downloads
- File metadata in database
Observability
- Frontend: Datadog RUM
- API: dd-trace for APM
Build & Deployment
Turborepo
- Caches build outputs intelligently
- Tasks run in parallel when possible
- Filter specific apps:
turbo build --filter=@xtablo/main
Deployments
- Main app: Cloudflare Workers (Vite build)
- API: Google Cloud Run (TypeScript compiled)
- Config: Centralized in API, secrets from Google Secret Manager
Environment-Specific Builds
pnpm build:staging # Uses .env.staging
pnpm build:prod # Uses .env.production
Development Conventions
Query Keys
Use hierarchical naming for proper cache invalidation:
["tablos"] // List of tablos
["tablos", tabloId] // Single tablo
["tablo-files", tabloId] // Files for a tablo
Hook Patterns
All hooks return consistent shapes:
// Queries
const { data, isLoading, error } = useMyQuery()
// Mutations
const { mutate, isPending } = useMyMutation()
Error Handling
- Errors display as toast messages via
toast.add() - Use friendly, user-facing error messages
- Log technical details for debugging
Loading States
Three levels of loading feedback:
- Route level:
ProtectedRouteshows spinner - Feature level: React Query
isLoading - Action level: Button
disabledduring mutation
Type Safety
- No circular dependencies between packages
- API only imports from
@xtablo/shared-types - Frontend apps can import from all shared packages
Adding New Features
- Define types in
@xtablo/shared-typesor update database schema - Add API endpoint in
apps/api/src/routers/ - Create React Query hook in shared or app-specific hooks
- Build UI component using the hook and
@xtablo/uicomponents - Add route to
apps/main/src/lib/routes.tsxif needed
Testing Strategy
API Tests
- Vitest with test environment setup
- Mock Supabase client for database operations
- Test middleware and routers independently
- See
docs/API_TESTS.mdanddocs/MIDDLEWARE_TESTS.md
Frontend Tests
- Vitest + React Testing Library + happy-dom
- Test components in isolation
- Mock React Query hooks for integration tests
- Run with
pnpm testorpnpm test:watch
Important Notes
Type Generation
After database schema changes, regenerate types:
npx supabase gen types typescript > packages/shared-types/src/database.types.ts
Cache Issues
If you encounter stale builds or weird caching:
pnpm clean
rm -rf node_modules/.cache
pnpm install
pnpm build
IDE TypeScript
If VS Code shows type errors but build works:
- Cmd+Shift+P → "TypeScript: Restart TS Server"
- Check
pnpm typecheckpasses - Ensure package TypeScript configs are valid
Docker Development
The project includes Docker configurations for deployment:
- See
docs/DOCKER_*.mdfor Docker build optimization - API Dockerfile uses multi-stage builds with pnpm
- Cloud Build configurations in
docs/CLOUD_BUILD_*.md
Documentation
Extensive documentation available in /docs:
DEVELOPMENT.md: Comprehensive development guideAPI_*.md: API testing and integrationSTRIPE_*.md: Stripe integration detailsAUTH_*.md: Authentication patternsDOCKER_*.md: Docker and deploymentCLOUD_BUILD_*.md: GCP Cloud Build setup
For questions about architecture decisions or detailed implementation notes, check the docs folder first.