568 lines
32 KiB
Markdown
568 lines
32 KiB
Markdown
# 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.
|
|
|
|
<!-- 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 -->
|
|
|
|
## Agent skills
|
|
|
|
### Issue tracker
|
|
|
|
Issues live as local markdown files under `.scratch/`. See `docs/agents/issue-tracker.md`.
|
|
|
|
### Triage labels
|
|
|
|
Default canonical label names (needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix). See `docs/agents/triage-labels.md`.
|
|
|
|
### Domain docs
|
|
|
|
Single-context layout — `CONTEXT.md` and `docs/adr/` at the repo root. See `docs/agents/domain.md`.
|
|
|
|
### Sketch findings
|
|
|
|
- **Sketch findings for xtablo-source** (design decisions, CSS patterns, visual direction) → `Skill("sketch-findings-xtablo-source")`
|
|
|
|
<!-- 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 -->
|