docs: create roadmap (7 phases)

This commit is contained in:
Arthur Belleville 2026-05-14 16:28:40 +02:00
parent b46d314a35
commit f58596e496
No known key found for this signature in database
3 changed files with 475 additions and 0 deletions

139
.planning/ROADMAP.md Normal file
View file

@ -0,0 +1,139 @@
# Roadmap: Xtablo Go+HTMX Rewrite
**Created:** 2026-05-14
**Project mode:** Vertical MVP (each phase delivers an end-to-end user-visible slice where possible)
**Milestone:** v1 — Tablos workflow on Go+HTMX
7 phases, sequential. Earlier phases are foundational; later phases build atop them. Phase boundaries are deliberate review points — especially for DB schema decisions (Phases 2, 3, 4, 5).
---
## Phase Summary
| # | Phase | Goal | Requirements |
|---|-------|------|--------------|
| 1 | Foundation | Fresh `backend/` Go package boots, renders HTMX, talks to Postgres | FOUND-01..05 |
| 2 | Auth | A user can sign up, log in, and stay logged in | AUTH-01..07 |
| 3 | Tablos CRUD | An authenticated user can manage their tablos end-to-end | TABLO-01..06 |
| 4 | Tasks (Kanban) | A user can run a kanban board inside a tablo | TASK-01..07 |
| 5 | Files | A user can attach, list, download, delete files on a tablo | FILE-01..06 |
| 6 | Background Worker | A second binary runs jobs against the same Postgres | WORK-01..04 |
| 7 | Deploy v1 | The product runs in production on a single host | DEPLOY-01..05 |
---
## Phase Details
### Phase 1: Foundation
**Goal:** A fresh `backend/` Go package boots a web server, renders an HTMX-driven base layout, and connects to a local Postgres with migrations.
**Mode:** mvp
**Requirements:** FOUND-01, FOUND-02, FOUND-03, FOUND-04, FOUND-05
**Success Criteria:**
1. `just dev` starts the web server on a local port and live-reloads on `.go` and template changes
2. `GET /healthz` returns 200 with a JSON `{status:"ok", db:"ok"}` only when the DB is reachable
3. The root route renders a Tailwind-styled HTMX page that loads without console errors and includes a working `hx-get` example
4. `just migrate up` applies migrations from `backend/migrations/` against the local Postgres
5. A new dev can clone the repo, run `compose up -d` + `just dev`, and see the page within ~5 minutes following `backend/README.md`
**User-in-loop:** Approve directory layout (`backend/cmd/web`, `backend/cmd/worker`, `backend/internal/...`) and pick the migration tool (`goose` vs `golang-migrate`).
### Phase 2: Authentication
**Goal:** A new user can sign up, log in with email + password, and stay logged in across requests using server-managed sessions.
**Mode:** mvp
**Requirements:** AUTH-01, AUTH-02, AUTH-03, AUTH-04, AUTH-05, AUTH-06, AUTH-07
**Success Criteria:**
1. Signing up creates a user row with hashed password (argon2id or bcrypt) and starts a session
2. Logging in with valid credentials issues a signed HTTP-only cookie; invalid credentials show a clear error
3. Hitting any protected route while unauthenticated redirects to `/login`; logged-in users on `/login` go to `/`
4. Logout invalidates the session server-side (cookie cleared + DB session row deleted)
5. All POST routes require a valid CSRF token; missing/invalid tokens return 403
6. >5 failed logins per email/IP per minute triggers rate-limiting
**User-in-loop:** Approve the `users` and `sessions` table schemas (columns, indexes, deletion semantics) before sqlc generation. Approve hash algorithm choice.
### Phase 3: Tablos CRUD
**Goal:** A logged-in user can list, create, view, edit, and delete their tablos end-to-end through HTMX-driven flows.
**Mode:** mvp
**Requirements:** TABLO-01, TABLO-02, TABLO-03, TABLO-04, TABLO-05, TABLO-06
**Success Criteria:**
1. Dashboard lists the current user's tablos newest-first; empty state shows a "Create your first tablo" CTA
2. Creating a tablo via the create form inserts a row, dismisses the modal/inline form, and prepends the new tablo via HTMX swap
3. Tablo detail page renders title and description; non-owners (or unauthenticated) get a 404
4. Editing title/description updates the row and re-renders the affected fragments without a full page reload
5. Deleting a tablo removes it from the list (with a confirmation step) and is irreversible via the UI
6. All actions work without JS errors and degrade gracefully if HTMX is unavailable (forms still submit)
**User-in-loop:** Approve the `tablos` table schema (ownership model, soft-delete vs hard-delete, slug strategy).
### Phase 4: Tasks (Kanban)
**Goal:** Inside a tablo, a user can run a kanban board — create, edit, move, reorder, and delete tasks across columns.
**Mode:** mvp
**Requirements:** TASK-01, TASK-02, TASK-03, TASK-04, TASK-05, TASK-06, TASK-07
**Success Criteria:**
1. A tablo detail page shows a kanban board with at least three columns
2. Creating a task inserts it into the target column and renders without full reload
3. Editing a task updates title/description in place
4. Moving a task between columns persists the new column and refreshes the source + target columns
5. Reordering within a column persists and survives reload
6. Deleting a task removes it from the board with a confirmation
7. Two concurrent edits don't corrupt order (last-write-wins is acceptable for v1, documented)
**User-in-loop:** Approve the `task_columns` (or fixed-column) schema and the ordering strategy (fractional indices, gaps-of-100, linked list — to be decided with research). Approve whether reorder is drag-and-drop or button-driven.
### Phase 5: Files
**Goal:** A user can attach files to a tablo, list them, download them via signed URLs, and delete them — backed by S3-compatible storage.
**Mode:** mvp
**Requirements:** FILE-01, FILE-02, FILE-03, FILE-04, FILE-05, FILE-06
**Success Criteria:**
1. Uploading a file from a tablo detail page creates a `tablo_files` row and stores bytes in the configured S3 bucket
2. The files list shows original filename, size, and uploaded-at; sorted newest first
3. Downloads use signed URLs with a short TTL (e.g. 5 minutes) generated server-side
4. Deleting a file removes both the DB row and the S3 object; failures are surfaced and logged
5. Only the tablo owner can upload/list/download/delete files for a given tablo (verified by tests)
6. Configurable max upload size enforced server-side, with a friendly error message above the form
**User-in-loop:** Approve the `tablo_files` schema (key strategy, content-type handling, dedup). Approve upload method (direct PUT vs server-proxied).
### Phase 6: Background Worker
**Goal:** A second binary (`cmd/worker`) runs against the same Postgres, processes jobs from a queue, and proves end-to-end with at least one real job.
**Mode:** mvp
**Requirements:** WORK-01, WORK-02, WORK-03, WORK-04
**Success Criteria:**
1. `cmd/worker` starts, connects to Postgres, and registers handlers; logs are structured and graceful shutdown works
2. At least one real job (chosen during plan-phase — e.g. periodic orphan-file cleanup) runs on a schedule and is observable in logs
3. A failing job is retried with backoff and visible via a simple CLI surface (`backend list-failed-jobs` or admin route)
4. Web binary can enqueue a job; worker picks it up within a few seconds
5. README documents how to run the worker locally alongside the web binary
**User-in-loop:** Approve the queue library/approach (`river` vs `asynq` vs hand-rolled `pg_notify`) and pick the proof-of-life job.
### Phase 7: Deploy v1
**Goal:** The product runs in production on a single host, behind a documented deploy + rollback workflow.
**Mode:** mvp
**Requirements:** DEPLOY-01, DEPLOY-02, DEPLOY-03, DEPLOY-04, DEPLOY-05
**Success Criteria:**
1. A multi-stage Dockerfile builds both `web` and `worker` and the image starts either via a subcommand
2. The container runs on the chosen single-host target (e.g. Hetzner VM / Fly.io / Cloud Run) with env-injected config
3. Deploy step runs migrations against the production database before traffic is shifted
4. `/healthz` and `/readyz` return appropriate status codes during startup, steady state, and shutdown
5. `backend/README.md` documents: first-time deploy, routine deploy, rollback, and incident triage basics
**User-in-loop:** Approve the deploy target choice (Hetzner / Fly / Cloud Run) and the secret-management strategy (env vars vs `.env` file vs SOPS).
---
## Coverage
- v1 requirements: 40
- Mapped to phases: 40
- Unmapped: 0 ✓
## Notes
- Sequential execution: each phase depends on the previous. Auth must work before Tablos; Tablos must exist before Tasks/Files can attach to them.
- The user is in the loop on **DB schema decisions** at the start of Phases 2, 3, 4, and 5. Treat schema approval as a hard gate inside `plan-phase` — sqlc generation does not run until the schema is approved.
- Visual design is intentionally undefined per phase; UI plans choose Tailwind patterns inline. A `gsd-ui-phase` step can be invoked for any phase if a more deliberate UI contract is desired (Phases 3, 4, 5 are the strongest candidates).
- The legacy `apps/*` JS app is the behavioral reference — `.planning/codebase/` is the source of truth for "what does the JS version do today?".
---
*Roadmap created: 2026-05-14*

39
.planning/STATE.md Normal file
View file

@ -0,0 +1,39 @@
# STATE
**Project:** Xtablo Go+HTMX Rewrite
**Milestone:** v1 — Tablos workflow
**Created:** 2026-05-14
## Project Reference
See: `.planning/PROJECT.md` (updated 2026-05-14)
**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.
**Current focus:** Phase 1 — Foundation
## Phase Status
| # | Phase | Status |
|---|-------|--------|
| 1 | Foundation | ○ Pending |
| 2 | Authentication | ○ Pending |
| 3 | Tablos CRUD | ○ Pending |
| 4 | Tasks (Kanban) | ○ Pending |
| 5 | Files | ○ Pending |
| 6 | Background Worker | ○ Pending |
| 7 | Deploy v1 | ○ Pending |
## Active Phase
**Phase 1: Foundation** — not started.
Next: `/gsd-discuss-phase 1` to gather context, or `/gsd-plan-phase 1` to plan directly.
## Notes
- Existing `go-backend/` is set aside; new code lives in a fresh `backend/` Go package.
- DB schema is changing from the JS/Supabase version — user is in the loop on every schema decision (Phases 25).
- GSD subagents are not installed in this repo; downstream commands may fail until `npx get-shit-done-cc@latest --global` is run.
---
*Last updated: 2026-05-14 after project initialization*

297
CLAUDE.md
View file

@ -251,3 +251,300 @@ Extensive documentation available in `/docs`:
- `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 -->
<!-- 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 -->