# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Common Commands ### Development ```bash 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 ```bash 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 ```bash 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 ```bash pnpm lint # Check all packages with Biome pnpm lint:fix # Fix linting issues pnpm typecheck # Type check everything pnpm format # Format code ``` ### Cleanup ```bash 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 1. **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 2. **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) and `useMaybeUser()` (returns null) ### Authentication & Sessions - **Supabase Auth** with JWT tokens - `SessionContext` listens to `supabase.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 endpoints - `authRouter.ts`: Requires authentication - `maybeAuthRouter.ts`: Optional authentication - `tablo.ts`, `tablo_data.ts`: Core business logic - `stripe.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 1. **Direct Supabase queries**: `useQuery()` → `supabase.from("table").select().eq(...)` 2. **API calls**: `useQuery()` → `api.get("/api/v1/...")` with Bearer token 3. **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 `streamToken` from API ### Stripe - Webhooks: `/api/v1/stripe-webhook` - Sync engine keeps Supabase ↔ Stripe in sync - See `docs/STRIPE_*.md` for 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 ```bash pnpm build:staging # Uses .env.staging pnpm build:prod # Uses .env.production ``` ## Development Conventions ### Query Keys Use hierarchical naming for proper cache invalidation: ```typescript ["tablos"] // List of tablos ["tablos", tabloId] // Single tablo ["tablo-files", tabloId] // Files for a tablo ``` ### Hook Patterns All hooks return consistent shapes: ```typescript // 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: 1. Route level: `ProtectedRoute` shows spinner 2. Feature level: React Query `isLoading` 3. Action level: Button `disabled` during 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 1. **Define types** in `@xtablo/shared-types` or update database schema 2. **Add API endpoint** in `apps/api/src/routers/` 3. **Create React Query hook** in shared or app-specific hooks 4. **Build UI component** using the hook and `@xtablo/ui` components 5. **Add route** to `apps/main/src/lib/routes.tsx` if 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.md` and `docs/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 test` or `pnpm test:watch` ## Important Notes ### Type Generation After database schema changes, regenerate types: ```bash npx supabase gen types typescript > packages/shared-types/src/database.types.ts ``` ### Cache Issues If you encounter stale builds or weird caching: ```bash 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 typecheck` passes - Ensure package TypeScript configs are valid ### Docker Development The project includes Docker configurations for deployment: - See `docs/DOCKER_*.md` for 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 guide - `API_*.md`: API testing and integration - `STRIPE_*.md`: Stripe integration details - `AUTH_*.md`: Authentication patterns - `DOCKER_*.md`: Docker and deployment - `CLOUD_BUILD_*.md`: GCP Cloud Build setup For questions about architecture decisions or detailed implementation notes, check the docs folder first. ## Project **Xtablo — Go + HTMX Rewrite** A full rewrite of the Xtablo product from a JS/Turbo monorepo (`apps/main`, `apps/external`, `apps/api`, …) into a single Go server with HTMX-driven UI. v1 focuses on the authenticated Tablos workflow only; other surfaces (booking, client portal, admin, chat, billing) come in later milestones. Built for a developer who wants a simpler, more durable stack and is using this rewrite as a deliberate pivot. **Core Value:** **A user can sign in and run the Tablos workflow — create tablos, manage their tasks (kanban), and attach files — without a JS framework.** If everything else fails, this must work end-to-end on a single Go binary backed by Postgres and an S3-compatible bucket. ### Constraints - **Tech stack**: Go (server + templates) + HTMX + Tailwind + Postgres + sqlc — no third-party auth, no JS framework, no managed BaaS - **Auth**: Server-managed sessions only (signed/HTTP-only cookies), no JWTs from external providers - **Storage**: Files in S3-compatible object storage (Cloudflare R2 to start) - **Architecture**: One web server binary + one background worker (same repo, possibly same binary with subcommand) - **Deploy target**: Single VPS / container — no Kubernetes - **Scope discipline**: v1 ships the Tablo workflow only; resist scope creep from JS feature inventory ## Technology Stack ## Languages & Runtimes - **TypeScript** — pinned at `^5.7.0` across most workspaces (api uses `^5.8.3`). Root devDependency in `package.json`. - **Node.js** — `>=20.0.0` enforced via the `engines` field in the root `package.json`. The API Dockerfile builds on `node:20-alpine` (`apps/api/Dockerfile`). - **Package manager** — pnpm `10.19.0` (declared as `packageManager` in the root `package.json`). Cloudflare Workers builds and the API container use `corepack` to install pnpm. - **Go** — a parallel `go-backend/` service exists alongside the TS monorepo (`go-backend/go.mod`, `go 1.26.0`) using `chi`, `templ`, `pgx/v5`, and `sqlc`. It is included in the pnpm workspace but is otherwise its own toolchain. - **Python** — a small infra utility at `infra/app/main.py` with `infra/requirements.txt` (not part of the runtime stack of the apps). ## Frameworks ### Frontend (React) - **React 19.0.0** + **React DOM 19.0.0** — used by `apps/main`, `apps/external`, `apps/admin`, `apps/clients`, and all React packages. - **React Router** — `react-router-dom ^7.9.4`. - **Vite 6.2** — bundler/dev server for every web app (`apps/main/vite.config.ts`, `apps/external/vite.config.ts`, `apps/admin/vite.config.ts`, `apps/clients/vite.config.ts`). - **TailwindCSS 4.1** — utility CSS via `@tailwindcss/vite`, with `tw-animate-css` and `tailwind-merge`. - **Radix UI** + **React Aria** — primitives composed in `@xtablo/ui` (see `packages/ui/package.json`). - **BlockNote** — rich-text editor (`@blocknote/core`, `@blocknote/mantine`, `@blocknote/react`) used in `apps/main`. - **AG Grid Community** — data grid in `apps/main` (`ag-grid-community`, `ag-grid-react`). - **React Hook Form** + **Zod** — forms and validation, wired through `@hookform/resolvers`. - **i18next** + `react-i18next` — translations (`apps/main/src/i18n.ts`, plus `external`, `clients`, `tablo-views`). - **react-day-picker** — calendar UI. - **PWA** — `vite-plugin-pwa` + `workbox-window` in `apps/main`. ### Backend (Hono) - **Hono ^4.7.7** — HTTP framework for both `apps/api` and `apps/chat-worker`. - **@hono/node-server** — Node adapter that drives `apps/api` (`apps/api/src/index.ts`). - **hono-sessions** — session helpers in the API. - **Cloudflare Workers + Durable Objects** — `apps/chat-worker` uses Hono with a `ChatRoom` SQLite-backed DO class (`apps/chat-worker/wrangler.toml`, `apps/chat-worker/src/durable-objects/ChatRoom.ts`). ## Core Dependencies ### Server State / Data Fetching - `@tanstack/react-query ^5.69.0` — primary server-state cache. Hierarchical query keys; 5-minute default cache. Used in `apps/main`, `apps/clients`, `apps/admin`, `apps/external`, and `@xtablo/tablo-views`. - `axios ^1.12.2` — HTTP client wrapper at `packages/shared/src/lib/api.ts`. ### Client State - `zustand ^5.0.5` — global stores (notably user). Lives in `@xtablo/shared` and is consumed via `useUser` / `useMaybeUser`. ### Auth & JWT - `@supabase/supabase-js ^2.49.x` — front-end and API client. - `jwt-decode ^4.0.0` — decode access tokens on the client. - `jose ^6.0.0` — JWT verification in the chat worker (`apps/chat-worker/src/lib/auth.ts`). ### UI Primitives - Radix: `react-avatar`, `react-checkbox`, `react-collapsible`, `react-dialog`, `react-dropdown-menu`, `react-label`, `react-popover`, `react-select`, `react-separator`, `react-slider`, `react-slot`, `react-switch`, `react-tabs`, `react-tooltip`, `react-radio-group` (`packages/ui/package.json`). - `react-aria` / `react-aria-components ^1.7.0`, `@react-stately/*`, `@react-aria/*`. - `lucide-react ^0.460.0` — iconography. - `class-variance-authority`, `clsx`, `tailwind-merge` — class composition. - `sonner ^2.0.7` — toast notifications (re-exported via `packages/shared/src/lib/toast.ts`). ### Dates, IDs, Utilities - `date-fns ^4.1.0`, `luxon ^3.7.2` (API only), `@internationalized/date`. - `uuid ^11.1.0`, `pluralize ^8.0.0`, `ts-pattern ^5.6.2`. - `jspdf ^3.0.3` — PDF export (main + shared). ### Payments / Billing - `stripe ^20.0.0` — server SDK (`apps/api`). - `@stripe/stripe-js ^8.2.0` — browser SDK (`apps/main`). - `@supabase/stripe-sync-engine ^0.45.0` — Stripe ↔ Supabase sync (`apps/api/src/middlewares/stripeSync.ts`). ### Storage / Email - `@aws-sdk/client-s3 ^3.850.0` — used against Cloudflare R2 in `apps/api/src/middlewares/middleware.ts` (`r2Middleware`). - `multer ^2.0.2`, `sharp ^0.34.5` — file upload handling and image processing. - `nodemailer ^7.0.4` + `googleapis ^161.0.0` — Gmail OAuth2 SMTP (`apps/api/src/middlewares/transporter.ts`). ### Observability - `@datadog/browser-rum ^6.13.0` + `@datadog/browser-rum-react ^6.13.0` — initialised in `apps/main/src/lib/rum.ts` and `apps/clients/src/lib/rum.ts`. - `dd-trace ^5.74.0` — APM tracer started at the top of `apps/api/src/index.ts`. - `@datadog/datadog-ci`, `@datadog/datadog-ci-base`, `@datadog/datadog-ci-plugin-cloud-run` — CI source-map upload and Cloud Run integration. - `static-analysis.datadog.yml` — repo-level Datadog static analysis config. ## Build / Dev Tooling - **Turborepo `^2.5.8`** — pipeline orchestration (`turbo.json`). Tasks: `build`, `dev`, `deploy(:staging|:prod)`, `build:staging`, `build:prod`, `lint`, `lint:fix`, `typecheck`, `test`, `test:watch`, `format`, `clean`. Caches `dist/**` and `tsconfig.tsbuildinfo`. - **Biome `2.2.5`** — formatter + linter, config at `biome.json` with explicit per-package `files.includes`. - **Vite `^6.2.2`** with `@vitejs/plugin-react ^4.3.4`, `vite-tsconfig-paths`, `@tailwindcss/vite`, `@cloudflare/vite-plugin`, `rollup-plugin-visualizer`, `vite-plugin-pwa`. - **Vitest** — `^3.2.4` in frontend apps, `^4.0.8` in `apps/api`. Browser env via `happy-dom` (main, admin) or `jsdom` (clients). - **Testing Library** — `@testing-library/react`, `@testing-library/jest-dom`, `@testing-library/user-event`. - **tsc** — every package has its own `tsconfig.json` and runs `tsc -b` or `tsc --noEmit` for typecheck. - **Wrangler `^4.24.3`** — Cloudflare Workers CLI used by `main`, `external`, `admin`, `clients`, `chat-worker`. - **tsx `^4.7.1`** — dev runner for the API (`pnpm dev` invokes `tsx watch src/index.ts`). ## Configuration ### Environment loading - API: `dotenv.config({ path: `.env.${NODE_ENV}` })` in `apps/api/src/config.ts`. `createConfig(secrets)` synthesizes a typed `AppConfig` from env + Google Secret Manager values. - Frontend: Vite `import.meta.env.*`; modes `dev`, `staging`, `production` selected via `vite build --mode`. ### Secret loading - `apps/api/src/secrets.ts` pulls all sensitive values from `projects/xtablo/secrets/*/versions/latest` using `@google-cloud/secret-manager`. - Test mode bypasses Secret Manager and uses raw env vars. ### Build targets per app | App | Bundler | Output | Deploy target | | --- | --- | --- | --- | | `apps/main` | Vite + `@cloudflare/vite-plugin` | `dist/` + worker | Cloudflare Workers (`apps/main/wrangler.toml`, routes `app.xtablo.com`, `app-staging.xtablo.com`) | | `apps/external` | Vite + Cloudflare plugin | `dist/` | Cloudflare Workers (`apps/external/wrangler.toml`) | | `apps/admin` | Vite + Cloudflare plugin | `dist/` | Cloudflare Workers (`apps/admin/wrangler.toml`) | | `apps/clients` | Vite + Cloudflare plugin | `dist/` | Cloudflare Workers (`apps/clients/wrangler.toml`) | | `apps/chat-worker` | Wrangler-native | Worker bundle | Cloudflare Workers + Durable Objects, `chat.xtablo.com` | | `apps/api` | `tsc` → `dist/` | Node 20 container | Google Cloud Run (`apps/api/Dockerfile`, `apps/api/cloudbuild.yaml`) | | `go-backend` | `go build` | Binary | Separate (see `go-backend/justfile`) | ### Static configs - `biome.json` — single source of truth for formatting/linting scope. - `turbo.json` — task graph; `globalDependencies` include `**/.env.*local`. - `tsconfig.json` per workspace; project references across packages. ## Workspace Structure ### Apps (`apps/`) - `@xtablo/main` — authenticated dashboard (port 5173). - `@xtablo/external` — embeddable public booking widget (port 5174). - `@xtablo/clients` — read-only client portal (port 5175, `clients.xtablo.com`). - `@xtablo/admin` — internal admin app (port 5176). - `@xtablo/api` — Hono REST API (port 8080). - `@xtablo/chat-worker` — Cloudflare Worker hosting Durable-Object chat (`chat.xtablo.com`). ### Packages (`packages/`) - `@xtablo/shared` — React contexts, hooks, supabase wrapper, axios client, toast helper. **Source-only** (no build step; consumers import TS directly — see `packages/shared/package.json` `"main": "./src/index.ts"`). - `@xtablo/ui` — Radix + Tailwind + react-aria component library. **Source-only**. - `@xtablo/shared-types` — pure TS types including Supabase-generated `database.types.ts`. **Source-only**, zero runtime deps. - `@xtablo/auth-ui` — shared auth screens. **Source-only**. - `@xtablo/chat-ui` — chat UI components consumed by main, clients, tablo-views. **Source-only**. - `@xtablo/tablo-views` — tablo view components shared between main and clients. **Source-only**. ### pnpm overrides ## Conventions ## Linter & Formatter — Biome - `indentStyle: "space"`, `indentWidth: 2` - `lineEnding: "lf"`, `lineWidth: 100` - JS: `quoteStyle: "double"`, `jsxQuoteStyle: "double"`, `semicolons: "always"`, `trailingCommas: "es5"`, `arrowParentheses: "always"`, `bracketSpacing: true`, `bracketSameLine: false` - JSON formatter enabled with same indent settings; parser allows comments but not trailing commas - `suspicious.noExplicitAny: "error"` (overridden to `off` for some files via overrides; warn in `xtablo-expo`) - `correctness.noUnusedVariables`, `noUnusedImports`, `noUndeclaredVariables` — all `error` - `style.useConst`, `useTemplate`, `noNamespace`, `noCommonJs` — all `error` - `complexity.noBannedTypes`, `suspicious.noDebugger`, `suspicious.noEmptyBlockStatements` — all `error` - `pnpm lint` → `turbo lint` → each package runs `biome check .` - `pnpm lint:fix` → `biome check --write .` - `pnpm format` → `biome format --write .` ## TypeScript Conventions - `noUnusedLocals: true`, `noUnusedParameters: true`, `noFallthroughCasesInSwitch: true` - `noUncheckedIndexedAccess: true` for `packages/shared-types`, `packages/shared` - `isolatedModules: true`, `moduleDetection: "force"`, `skipLibCheck: true` - API uses `verbatimModuleSyntax: true` (`apps/api/tsconfig.json`) — type-only imports must be explicit - Frontend apps use `module: ESNext` + `moduleResolution: "bundler"` (e.g. `apps/main/tsconfig.app.json`) - API uses `module: NodeNext` (TypeScript compiled output via `tsc`) - All packages declare `"type": "module"` in package.json - `apps/main`: `@ui/*` → `./src/*`, `@xtablo/auth-ui` → `../../packages/auth-ui/src`, `@xtablo/ui/*` → `../../packages/ui/src/*` (see `apps/main/tsconfig.app.json`) - `apps/main/tsconfig.json`: also `@external/*` → `src/external/*` - No circular dependencies between packages - `apps/api` may only import from `@xtablo/shared-types` - Frontend apps may import from all shared packages - `@xtablo/shared` and `@xtablo/ui` are **source-only** — TypeScript is consumed directly via `vite-tsconfig-paths`; no build step - Database types are auto-generated into `packages/shared-types/src/database.types.ts` via `npx supabase gen types typescript` - Domain types in `@xtablo/shared-types` are derived from `database.types.ts` (nulls removed, refined shapes) - API response types live in the same package so frontends and the API agree ## React Component Conventions - Functional components only (no class components observed) - TSX files use named exports for components, e.g. `export function CustomModal(...)` (see `apps/main/src/components/CustomModal.tsx`) - Co-located unit tests: `Foo.tsx` + `Foo.test.tsx` - Naming suffix conventions (per `CLAUDE.md`): - Shared primitives (Radix + Tailwind) live in `packages/ui/src` - Cross-app business hooks/contexts live in `packages/shared/src` ## Hook Patterns - Default cache time: 5 minutes (configured in `packages/shared`'s QueryClient) - Mutations invalidate targeted keys explicitly rather than blowing away the whole cache - `useUser()` — throws if no session (use inside protected routes) - `useMaybeUser()` — returns null if unauthenticated (use in route guards / public surfaces) ## Query Key Conventions ## Error Handling - User-facing: `toast.add({ ... })` (toast system in `packages/shared` / `packages/ui`) — messages should be friendly and actionable - Technical: `console.error` for developer-only context (stack traces, raw API errors) - API errors are caught at the React Query hook layer and surfaced via `error` from `useQuery`/`useMutation`; UI components branch on `isError` - Server side (`apps/api`): Hono routers return `c.json({ error: ... }, statusCode)`; middleware handles auth failures with 401s (see `docs/MIDDLEWARE_TESTS.md`) ## Loading States — three levels ## Import / Export Patterns - ESM throughout (`"type": "module"` in every package.json; Biome enforces `noCommonJs`) - Source-only packages (`@xtablo/shared`, `@xtablo/ui`) export from `src/index.ts` and are consumed via TS path aliases — no `dist/` involved - Compiled packages (`@xtablo/shared-types` and `apps/api`) emit `dist/` via `tsc`; `shared-types` includes `declaration: true` and `declarationMap: true` - Type-only imports preferred where supported (`verbatimModuleSyntax` is on for the API) - Biome's `noUnusedImports` rule will flag dead imports at lint time ## File Organization - `routers/` — Hono routers grouped by concern (`public.ts`, `authRouter.ts`, `tablo.ts`, `stripe.ts`, etc.) - `middlewares/` — Auth, Supabase, R2, Stream, email injection - `helpers/` — Pure logic (testable in isolation, e.g. `helpers/orgIcons.ts` + `orgIcons.test.ts`) - `__tests__/` — Test-only fixtures, setup, globalSetup, route + middleware suites ## Type Safety - `noExplicitAny` is on as `error` in the root config (relaxed to `warn` in `xtablo-expo` and `off` for select API/legacy file overrides) - Prefer derived types from `@xtablo/shared-types` over inline shape literals - `Database` table types are generated; domain types should narrow them rather than redeclare from scratch - `noUncheckedIndexedAccess: true` in shared packages — index access returns `T | undefined`; handle the undefined branch explicitly - API enforces `verbatimModuleSyntax`, so `import type { ... }` is required for type-only imports — relevant for compiled API code ## Reference files - `biome.json` — single source for lint + format - `apps/main/tsconfig.app.json` — canonical frontend TS config - `apps/api/tsconfig.json` — canonical backend TS config - `packages/shared-types/tsconfig.json` — type-only package config - `CLAUDE.md` — high-level conventions (this doc expands it with verified specifics) ## Architecture ## High-Level Diagram ``` ``` ## Application Layers - **Frontend dashboard** (`apps/main`): primary authenticated SPA. Tablos, planning, events, chat, notes, billing. Entry: `apps/main/src/main.tsx`, root component `apps/main/src/App.tsx`. - **Public booking widget** (`apps/external`): embeddable / floating booking widget. Entry: `apps/external/src/main.tsx`. Query params drive mode (`?mode=embed&eventTypeId=...`). - **Client portal** (`apps/clients`): public-facing client portal experience. Entry: `apps/clients/src/main.tsx`, routes in `apps/clients/src/routes.tsx`. - **Admin app** (`apps/admin`): internal admin tools. Entry: `apps/admin/src/main.tsx`, routes in `apps/admin/src/routes.tsx`. - **API** (`apps/api`): Hono-based REST API serving all frontends. Entry: `apps/api/src/index.ts` (compiled to Node, deployed to Google Cloud Run). - **Chat worker** (`apps/chat-worker`): Cloudflare Worker with Durable Objects for real-time chat presence. Entry: `apps/chat-worker/src/index.ts`. ## Data Flow Patterns ### React Query (TanStack Query v5) — primary server-state tool ```ts ``` - `apps/main/src/hooks/` — feature hooks (`tablos.ts`, `events.ts`, `tasks.ts`, `availabilities.ts`, `stripe.ts`, `notes.ts`, ...). - `packages/shared/src/hooks/` — cross-app hooks (`auth.ts`, `book.ts`, `public.ts`). ### Zustand — global client state - `useUser()` — throws if no session (use inside protected routes). - `useMaybeUser()` — returns null if unauthenticated (use in route guards / public-aware components). ### Direct Supabase queries vs API calls ## Authentication Flow - **Supabase Auth** issues JWTs on login / passwordless flows. - **SessionContext** (`packages/shared/src/contexts/SessionContext.tsx`) subscribes to `supabase.auth.onAuthStateChange()` and exposes the current session. - Frontend HTTP client reads the session token and sends `Authorization: Bearer ` on every API call. - The API's `supabase` middleware validates the JWT and attaches the resolved user / supabase clients to the Hono context. - **Passwordless onboarding** generates temporary accounts flagged with `is_temporary: true`. - **Protected routes** check `useMaybeUser()` and redirect to landing when null. - **Client portal** has a parallel auth path: magic-link based, signed cookies issued by `apps/api/src/routers/clientAuth.ts`. Configurable TTLs, cookie domain, JWT secret are passed into the router factory. ## API Architecture (Hono) ### Middleware Manager (singleton pattern) ``` ``` - `apps/api/src/middlewares/middleware.ts` — central singleton and supabase / r2 / email / stripe middlewares. - `apps/api/src/middlewares/stripeSync.ts` — bidirectional Supabase <-> Stripe sync engine. - `apps/api/src/middlewares/transporter.ts` — email transporter. ### Router ordering ## Key Abstractions - **`packages/shared`** is the central runtime sharing point. Re-exports cover contexts (`SessionContext`, `ThemeContext`), cross-app hooks, the API client, the Supabase client, toast helpers, and shared types. Public surface in `packages/shared/src/index.ts`. - **`packages/ui`** — Radix + Tailwind component library. Source-only. Components in `packages/ui/src/components/` (`button.tsx`, `dialog.tsx`, `select.tsx`, ...). - **`packages/shared-types`** — zero-runtime-dependency TypeScript types. Auto-generated `database.types.ts` + hand-written domain layers (`tablos.types.ts`, `tablo-data.types.ts`, `events.types.ts`, `kanban.types.ts`, `stripe.types.ts`, `admin.types.ts`). - **`packages/auth-ui`, `packages/chat-ui`, `packages/tablo-views`** — feature-scoped UI packages, also source-only. - **Query keys** — convention enforced by colocation in `apps/main/src/hooks/` and feature naming (`["tablos", id]`, `["tablo-files", id]`, etc.). ## Source-Only Package Pattern ## Entry Points | App / package | Entry file | |----------------------|---------------------------------------------------------| | `apps/main` | `apps/main/src/main.tsx` -> `App.tsx` | | `apps/external` | `apps/external/src/main.tsx` -> `routes.tsx` | | `apps/clients` | `apps/clients/src/main.tsx` -> `App.tsx` / `routes.tsx` | | `apps/admin` | `apps/admin/src/main.tsx` -> `routes.tsx` | | `apps/api` | `apps/api/src/index.ts` -> `routers/index.ts` | | `apps/chat-worker` | `apps/chat-worker/src/index.ts` | | `@xtablo/shared` | `packages/shared/src/index.ts` | | `@xtablo/ui` | `packages/ui/src/components/index.ts` | | `@xtablo/shared-types` | `packages/shared-types/src/index.ts` | ## Build & Deployment Notes - **Turborepo** orchestrates tasks with caching (`turbo.json` at repo root). - `apps/main` and other Vite apps deploy to **Cloudflare Workers** via `wrangler.toml` and the bundled `worker/` folder. - `apps/api` compiles TypeScript and deploys to **Google Cloud Run**; secrets resolved via Google Secret Manager. - `apps/chat-worker` deploys as a **Cloudflare Worker with Durable Objects**. - Observability: Datadog RUM on frontends (`apps/main/src/lib/rum.ts`), `dd-trace` on the API (initialized at the top of `apps/api/src/index.ts`). ## Project Skills No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, `.github/skills/`, or `.codex/skills/` with a `SKILL.md` index file. ## GSD Workflow Enforcement Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync. Use these entry points: - `/gsd-quick` for small fixes, doc updates, and ad-hoc tasks - `/gsd-debug` for investigation and bug fixing - `/gsd-execute-phase` for planned phase work Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it. ## Developer Profile > Profile not yet configured. Run `/gsd-profile-user` to generate your developer profile. > This section is managed by `generate-claude-profile` -- do not edit manually.