xtablo-source/CLAUDE.md
2026-05-14 16:28:40 +02:00

31 KiB

CLAUDE.md

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

Common Commands

Development

pnpm install                  # Install dependencies
pnpm dev                      # Run all apps in development
pnpm dev:main                 # Run main app only (port 5173)
pnpm dev:external             # Run external booking widget (port 5174)
pnpm dev:api                  # Run API server (port 8080)

Building

pnpm build                    # Build all apps
pnpm build:apps               # Build apps only (packages are source-only)
pnpm build:staging            # Build main app for staging
pnpm build:prod               # Build main app for production

Testing

pnpm test                     # Run all tests
pnpm test:watch               # Run tests in watch mode
pnpm test:api                 # Run API tests only
cd apps/main && pnpm test     # Run tests for specific package

Quality Checks

pnpm lint                     # Check all packages with Biome
pnpm lint:fix                 # Fix linting issues
pnpm typecheck                # Type check everything
pnpm format                   # Format code

Cleanup

pnpm clean                    # Clean all build artifacts and caches

Architecture Overview

Monorepo Structure

This is a Turborepo-based monorepo with three main apps and shared packages:

  • apps/main (@xtablo/main): Primary authenticated dashboard with tablos, planning, events, chat, and notes
  • apps/external (@xtablo/external): Public booking widget (embeddable/floating modes)
  • apps/api (@xtablo/api): Hono-based REST API serving both frontend apps
  • packages/shared (@xtablo/shared): React contexts, hooks, API client, React Query setup
  • packages/ui (@xtablo/ui): Radix UI + Tailwind component library
  • packages/shared-types (@xtablo/shared-types): Pure TypeScript types (zero runtime dependencies)

Source-Only Packages

The @xtablo/shared and @xtablo/ui packages are source-only - they export TypeScript directly without a build step. Changes are instantly reflected via Vite's HMR. There's no need to rebuild or watch these packages during development.

Key Architectural Patterns

State Management

  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

pnpm build:staging    # Uses .env.staging
pnpm build:prod       # Uses .env.production

Development Conventions

Query Keys

Use hierarchical naming for proper cache invalidation:

["tablos"]                    // List of tablos
["tablos", tabloId]           // Single tablo
["tablo-files", tabloId]      // Files for a tablo

Hook Patterns

All hooks return consistent shapes:

// Queries
const { data, isLoading, error } = useMyQuery()

// Mutations
const { mutate, isPending } = useMyMutation()

Error Handling

  • Errors display as toast messages via toast.add()
  • Use friendly, user-facing error messages
  • Log technical details for debugging

Loading States

Three levels of loading feedback:

  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:

npx supabase gen types typescript > packages/shared-types/src/database.types.ts

Cache Issues

If you encounter stale builds or weird caching:

pnpm clean
rm -rf node_modules/.cache
pnpm install
pnpm build

IDE TypeScript

If VS Code shows type errors but build works:

  • Cmd+Shift+P → "TypeScript: Restart TS Server"
  • Check pnpm 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 Routerreact-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.
  • PWAvite-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 Objectsapps/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 tscdist/ 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 lintturbo lint → each package runs biome check .
  • pnpm lint:fixbiome check --write .
  • pnpm formatbiome 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

  • 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).

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.