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
-
React Query (TanStack Query v5): Primary tool for server state
- 5-minute default cache time
- Hierarchical query keys:
["tablos"],["tablos", id],["tablo-files", tabloId] - Targeted cache invalidation on mutations
-
Zustand: Global client state, especially user context
- User fetched via React Query, stored in Zustand for app-wide access
- Two hooks:
useUser()(throws if no session) anduseMaybeUser()(returns null)
Authentication & Sessions
- Supabase Auth with JWT tokens
SessionContextlistens tosupabase.auth.onAuthStateChange()- API validates JWT from Authorization header
- Passwordless flow generates temporary accounts (
is_temporary: true) - Protected routes use
useMaybeUser()to check authentication
API Architecture
- Framework: Hono (edge-runtime compatible)
- Middleware Manager: Singleton pattern (
initializeMiddleware()called once, reused) - Router Order: Public routes first → middleware applied (supabase → stream/r2/email) → authenticated routes
- Key Routers:
public.ts: Unauthenticated endpointsauthRouter.ts: Requires authenticationmaybeAuthRouter.ts: Optional authenticationtablo.ts,tablo_data.ts: Core business logicstripe.ts: Payment webhooks and operations
Database & Types
- Supabase PostgreSQL with auto-generated types
- Generate types:
npx supabase gen types typescript > packages/shared-types/src/database.types.ts - Type hierarchy:
database.types.ts(auto-generated) → domain types (nulls removed) → API responses - @xtablo/shared-types: Zero-dependency package for all types
- Frontend uses direct Supabase client:
supabase.from("table").select() - API uses service role key to bypass RLS when needed
Data Fetching Patterns
- Direct Supabase queries:
useQuery()→supabase.from("table").select().eq(...) - API calls:
useQuery()→api.get("/api/v1/...")with Bearer token - File operations: Specialized hooks (
useUploadTabloFile,useDeleteTabloFile) with automatic cache invalidation
Routing
- Main app: Two route sets
publicRoutes: Auth pages, legal pages (outside UserStoreProvider)routes: Protected app routes (inside UserStoreProvider)
- Protected routes: Component checks
useMaybeUser(), redirects to landing if unauthenticated - External app: Query params control mode (
?mode=embed&eventTypeId=...)
Component Organization
- Modals:
*Modal.tsx - Sections:
*Section.tsx - Cards:
*Card.tsx - Tests co-located:
*.test.tsx - Shared UI in
@xtablo/ui - Business logic hooks in
@xtablo/shared
External Integrations
Stream Chat
- Chat provider wraps routes
- Users authenticate with
streamTokenfrom API
Stripe
- Webhooks:
/api/v1/stripe-webhook - Sync engine keeps Supabase ↔ Stripe in sync
- See
docs/STRIPE_*.mdfor detailed documentation
Storage
- Files stored in Cloudflare R2 (S3-compatible)
- AWS S3 SDK for uploads/downloads
- File metadata in database
Observability
- Frontend: Datadog RUM
- API: dd-trace for APM
Build & Deployment
Turborepo
- Caches build outputs intelligently
- Tasks run in parallel when possible
- Filter specific apps:
turbo build --filter=@xtablo/main
Deployments
- Main app: Cloudflare Workers (Vite build)
- API: Google Cloud Run (TypeScript compiled)
- Config: Centralized in API, secrets from Google Secret Manager
Environment-Specific Builds
pnpm build:staging # Uses .env.staging
pnpm build:prod # Uses .env.production
Development Conventions
Query Keys
Use hierarchical naming for proper cache invalidation:
["tablos"] // List of tablos
["tablos", tabloId] // Single tablo
["tablo-files", tabloId] // Files for a tablo
Hook Patterns
All hooks return consistent shapes:
// Queries
const { data, isLoading, error } = useMyQuery()
// Mutations
const { mutate, isPending } = useMyMutation()
Error Handling
- Errors display as toast messages via
toast.add() - Use friendly, user-facing error messages
- Log technical details for debugging
Loading States
Three levels of loading feedback:
- Route level:
ProtectedRouteshows spinner - Feature level: React Query
isLoading - Action level: Button
disabledduring mutation
Type Safety
- No circular dependencies between packages
- API only imports from
@xtablo/shared-types - Frontend apps can import from all shared packages
Adding New Features
- Define types in
@xtablo/shared-typesor update database schema - Add API endpoint in
apps/api/src/routers/ - Create React Query hook in shared or app-specific hooks
- Build UI component using the hook and
@xtablo/uicomponents - Add route to
apps/main/src/lib/routes.tsxif needed
Testing Strategy
API Tests
- Vitest with test environment setup
- Mock Supabase client for database operations
- Test middleware and routers independently
- See
docs/API_TESTS.mdanddocs/MIDDLEWARE_TESTS.md
Frontend Tests
- Vitest + React Testing Library + happy-dom
- Test components in isolation
- Mock React Query hooks for integration tests
- Run with
pnpm testorpnpm test:watch
Important Notes
Type Generation
After database schema changes, regenerate types:
npx supabase gen types typescript > packages/shared-types/src/database.types.ts
Cache Issues
If you encounter stale builds or weird caching:
pnpm clean
rm -rf node_modules/.cache
pnpm install
pnpm build
IDE TypeScript
If VS Code shows type errors but build works:
- Cmd+Shift+P → "TypeScript: Restart TS Server"
- Check
pnpm typecheckpasses - Ensure package TypeScript configs are valid
Docker Development
The project includes Docker configurations for deployment:
- See
docs/DOCKER_*.mdfor Docker build optimization - API Dockerfile uses multi-stage builds with pnpm
- Cloud Build configurations in
docs/CLOUD_BUILD_*.md
Documentation
Extensive documentation available in /docs:
DEVELOPMENT.md: Comprehensive development guideAPI_*.md: API testing and integrationSTRIPE_*.md: Stripe integration detailsAUTH_*.md: Authentication patternsDOCKER_*.md: Docker and deploymentCLOUD_BUILD_*.md: GCP Cloud Build setup
For questions about architecture decisions or detailed implementation notes, check the docs folder first.
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.0across most workspaces (api uses^5.8.3). Root devDependency inpackage.json. - Node.js —
>=20.0.0enforced via theenginesfield in the rootpackage.json. The API Dockerfile builds onnode:20-alpine(apps/api/Dockerfile). - Package manager — pnpm
10.19.0(declared aspackageManagerin the rootpackage.json). Cloudflare Workers builds and the API container usecorepackto install pnpm. - Go — a parallel
go-backend/service exists alongside the TS monorepo (go-backend/go.mod,go 1.26.0) usingchi,templ,pgx/v5, andsqlc. It is included in the pnpm workspace but is otherwise its own toolchain. - Python — a small infra utility at
infra/app/main.pywithinfra/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, withtw-animate-cssandtailwind-merge. - Radix UI + React Aria — primitives composed in
@xtablo/ui(seepackages/ui/package.json). - BlockNote — rich-text editor (
@blocknote/core,@blocknote/mantine,@blocknote/react) used inapps/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, plusexternal,clients,tablo-views). - react-day-picker — calendar UI.
- PWA —
vite-plugin-pwa+workbox-windowinapps/main.
Backend (Hono)
- Hono ^4.7.7 — HTTP framework for both
apps/apiandapps/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-workeruses Hono with aChatRoomSQLite-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 inapps/main,apps/clients,apps/admin,apps/external, and@xtablo/tablo-views.axios ^1.12.2— HTTP client wrapper atpackages/shared/src/lib/api.ts.
Client State
zustand ^5.0.5— global stores (notably user). Lives in@xtablo/sharedand is consumed viauseUser/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 viapackages/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 inapps/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 inapps/main/src/lib/rum.tsandapps/clients/src/lib/rum.ts.dd-trace ^5.74.0— APM tracer started at the top ofapps/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. Cachesdist/**andtsconfig.tsbuildinfo. - Biome
2.2.5— formatter + linter, config atbiome.jsonwith explicit per-packagefiles.includes. - Vite
^6.2.2with@vitejs/plugin-react ^4.3.4,vite-tsconfig-paths,@tailwindcss/vite,@cloudflare/vite-plugin,rollup-plugin-visualizer,vite-plugin-pwa. - Vitest —
^3.2.4in frontend apps,^4.0.8inapps/api. Browser env viahappy-dom(main, admin) orjsdom(clients). - Testing Library —
@testing-library/react,@testing-library/jest-dom,@testing-library/user-event. - tsc — every package has its own
tsconfig.jsonand runstsc -bortsc --noEmitfor typecheck. - Wrangler
^4.24.3— Cloudflare Workers CLI used bymain,external,admin,clients,chat-worker. - tsx
^4.7.1— dev runner for the API (pnpm devinvokestsx watch src/index.ts).
Configuration
Environment loading
- API:
dotenv.config({ path:.env.${NODE_ENV}})inapps/api/src/config.ts.createConfig(secrets)synthesizes a typedAppConfigfrom env + Google Secret Manager values. - Frontend: Vite
import.meta.env.*; modesdev,staging,productionselected viavite build --mode.
Secret loading
apps/api/src/secrets.tspulls all sensitive values fromprojects/xtablo/secrets/*/versions/latestusing@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;globalDependenciesinclude**/.env.*local.tsconfig.jsonper 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 — seepackages/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-generateddatabase.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: 2lineEnding: "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 toofffor some files via overrides; warn inxtablo-expo)correctness.noUnusedVariables,noUnusedImports,noUndeclaredVariables— allerrorstyle.useConst,useTemplate,noNamespace,noCommonJs— allerrorcomplexity.noBannedTypes,suspicious.noDebugger,suspicious.noEmptyBlockStatements— allerrorpnpm lint→turbo lint→ each package runsbiome check .pnpm lint:fix→biome check --write .pnpm format→biome format --write .
TypeScript Conventions
noUnusedLocals: true,noUnusedParameters: true,noFallthroughCasesInSwitch: truenoUncheckedIndexedAccess: trueforpackages/shared-types,packages/sharedisolatedModules: 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 viatsc) - All packages declare
"type": "module"in package.json apps/main:@ui/*→./src/*,@xtablo/auth-ui→../../packages/auth-ui/src,@xtablo/ui/*→../../packages/ui/src/*(seeapps/main/tsconfig.app.json)apps/main/tsconfig.json: also@external/*→src/external/*- No circular dependencies between packages
apps/apimay only import from@xtablo/shared-types- Frontend apps may import from all shared packages
@xtablo/sharedand@xtablo/uiare source-only — TypeScript is consumed directly viavite-tsconfig-paths; no build step- Database types are auto-generated into
packages/shared-types/src/database.types.tsvianpx supabase gen types typescript - Domain types in
@xtablo/shared-typesare derived fromdatabase.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(...)(seeapps/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 inpackages/shared/packages/ui) — messages should be friendly and actionable - Technical:
console.errorfor developer-only context (stack traces, raw API errors) - API errors are caught at the React Query hook layer and surfaced via
errorfromuseQuery/useMutation; UI components branch onisError - Server side (
apps/api): Hono routers returnc.json({ error: ... }, statusCode); middleware handles auth failures with 401s (seedocs/MIDDLEWARE_TESTS.md)
Loading States — three levels
Import / Export Patterns
- ESM throughout (
"type": "module"in every package.json; Biome enforcesnoCommonJs) - Source-only packages (
@xtablo/shared,@xtablo/ui) export fromsrc/index.tsand are consumed via TS path aliases — nodist/involved - Compiled packages (
@xtablo/shared-typesandapps/api) emitdist/viatsc;shared-typesincludesdeclaration: trueanddeclarationMap: true - Type-only imports preferred where supported (
verbatimModuleSyntaxis on for the API) - Biome's
noUnusedImportsrule 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 injectionhelpers/— Pure logic (testable in isolation, e.g.helpers/orgIcons.ts+orgIcons.test.ts)__tests__/— Test-only fixtures, setup, globalSetup, route + middleware suites
Type Safety
noExplicitAnyis on aserrorin the root config (relaxed towarninxtablo-expoandofffor select API/legacy file overrides)- Prefer derived types from
@xtablo/shared-typesover inline shape literals Databasetable types are generated; domain types should narrow them rather than redeclare from scratchnoUncheckedIndexedAccess: truein shared packages — index access returnsT | undefined; handle the undefined branch explicitly- API enforces
verbatimModuleSyntax, soimport type { ... }is required for type-only imports — relevant for compiled API code
Reference files
biome.json— single source for lint + formatapps/main/tsconfig.app.json— canonical frontend TS configapps/api/tsconfig.json— canonical backend TS configpackages/shared-types/tsconfig.json— type-only package configCLAUDE.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 componentapps/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 inapps/clients/src/routes.tsx. - Admin app (
apps/admin): internal admin tools. Entry:apps/admin/src/main.tsx, routes inapps/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 tosupabase.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
supabasemiddleware 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/sharedis 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 inpackages/shared/src/index.ts.packages/ui— Radix + Tailwind component library. Source-only. Components inpackages/ui/src/components/(button.tsx,dialog.tsx,select.tsx, ...).packages/shared-types— zero-runtime-dependency TypeScript types. Auto-generateddatabase.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.jsonat repo root). apps/mainand other Vite apps deploy to Cloudflare Workers viawrangler.tomland the bundledworker/folder.apps/apicompiles TypeScript and deploys to Google Cloud Run; secrets resolved via Google Secret Manager.apps/chat-workerdeploys as a Cloudflare Worker with Durable Objects.- Observability: Datadog RUM on frontends (
apps/main/src/lib/rum.ts),dd-traceon the API (initialized at the top ofapps/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-quickfor small fixes, doc updates, and ad-hoc tasks/gsd-debugfor investigation and bug fixing/gsd-execute-phasefor 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-userto generate your developer profile. This section is managed bygenerate-claude-profile-- do not edit manually.