- **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
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`.
- **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`).
- **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.
-`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
-`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.
- **`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`.
- **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.