xtablo-source/CLAUDE.md

551 lines
31 KiB
Markdown
Raw Permalink Normal View History

2025-11-15 21:58:06 +00:00
# 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.
2026-05-14 14:28:40 +00:00
<!-- GSD:project-start source:PROJECT.md -->
## 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
<!-- GSD:project-end -->
<!-- GSD:stack-start source:codebase/STACK.md -->
## 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
<!-- GSD:stack-end -->
<!-- GSD:conventions-start source:CONVENTIONS.md -->
## 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)
<!-- GSD:conventions-end -->
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
## 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 <jwt>` 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`).
<!-- GSD:architecture-end -->
<!-- GSD:skills-start source:skills/ -->
## 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:skills-end -->
<!-- GSD:workflow-start source:GSD defaults -->
## 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.
<!-- GSD:workflow-end -->
<!-- GSD:profile-start -->
## 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.
<!-- GSD:profile-end -->