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 - `CLOUD_BUILD_*.md`: GCP Cloud Build setup
For questions about architecture decisions or detailed implementation notes, check the docs folder first. 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 -->