From f58596e496be3afd1fcf4fa87221eb1748368044 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 14 May 2026 16:28:40 +0200 Subject: [PATCH] docs: create roadmap (7 phases) --- .planning/ROADMAP.md | 139 ++++++++++++++++++++ .planning/STATE.md | 39 ++++++ CLAUDE.md | 297 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 .planning/ROADMAP.md create mode 100644 .planning/STATE.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md new file mode 100644 index 0000000..63c7aae --- /dev/null +++ b/.planning/ROADMAP.md @@ -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* diff --git a/.planning/STATE.md b/.planning/STATE.md new file mode 100644 index 0000000..07ca114 --- /dev/null +++ b/.planning/STATE.md @@ -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 2–5). +- 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* diff --git a/CLAUDE.md b/CLAUDE.md index 4e7e331..4fb2cd0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. + + +## Project + +**Xtablo — Go + HTMX Rewrite** + +A full rewrite of the Xtablo product from a JS/Turbo monorepo (`apps/main`, `apps/external`, `apps/api`, …) into a single Go server with HTMX-driven UI. v1 focuses on the authenticated Tablos workflow only; other surfaces (booking, client portal, admin, chat, billing) come in later milestones. Built for a developer who wants a simpler, more durable stack and is using this rewrite as a deliberate pivot. + +**Core Value:** **A user can sign in and run the Tablos workflow — create tablos, manage their tasks (kanban), and attach files — without a JS framework.** + +If everything else fails, this must work end-to-end on a single Go binary backed by Postgres and an S3-compatible bucket. + +### Constraints + +- **Tech stack**: Go (server + templates) + HTMX + Tailwind + Postgres + sqlc — no third-party auth, no JS framework, no managed BaaS +- **Auth**: Server-managed sessions only (signed/HTTP-only cookies), no JWTs from external providers +- **Storage**: Files in S3-compatible object storage (Cloudflare R2 to start) +- **Architecture**: One web server binary + one background worker (same repo, possibly same binary with subcommand) +- **Deploy target**: Single VPS / container — no Kubernetes +- **Scope discipline**: v1 ships the Tablo workflow only; resist scope creep from JS feature inventory + + + +## Technology Stack + +## Languages & Runtimes +- **TypeScript** — pinned at `^5.7.0` across most workspaces (api uses `^5.8.3`). Root devDependency in `package.json`. +- **Node.js** — `>=20.0.0` enforced via the `engines` field in the root `package.json`. The API Dockerfile builds on `node:20-alpine` (`apps/api/Dockerfile`). +- **Package manager** — pnpm `10.19.0` (declared as `packageManager` in the root `package.json`). Cloudflare Workers builds and the API container use `corepack` to install pnpm. +- **Go** — a parallel `go-backend/` service exists alongside the TS monorepo (`go-backend/go.mod`, `go 1.26.0`) using `chi`, `templ`, `pgx/v5`, and `sqlc`. It is included in the pnpm workspace but is otherwise its own toolchain. +- **Python** — a small infra utility at `infra/app/main.py` with `infra/requirements.txt` (not part of the runtime stack of the apps). +## Frameworks +### Frontend (React) +- **React 19.0.0** + **React DOM 19.0.0** — used by `apps/main`, `apps/external`, `apps/admin`, `apps/clients`, and all React packages. +- **React 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 + + + +## 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) + + + +## 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 ` on every API call. +- The API's `supabase` middleware validates the JWT and attaches the resolved user / supabase clients to the Hono context. +- **Passwordless onboarding** generates temporary accounts flagged with `is_temporary: true`. +- **Protected routes** check `useMaybeUser()` and redirect to landing when null. +- **Client portal** has a parallel auth path: magic-link based, signed cookies issued by `apps/api/src/routers/clientAuth.ts`. Configurable TTLs, cookie domain, JWT secret are passed into the router factory. +## API Architecture (Hono) +### Middleware Manager (singleton pattern) +``` +``` +- `apps/api/src/middlewares/middleware.ts` — central singleton and supabase / r2 / email / stripe middlewares. +- `apps/api/src/middlewares/stripeSync.ts` — bidirectional Supabase <-> Stripe sync engine. +- `apps/api/src/middlewares/transporter.ts` — email transporter. +### Router ordering +## Key Abstractions +- **`packages/shared`** is the central runtime sharing point. Re-exports cover contexts (`SessionContext`, `ThemeContext`), cross-app hooks, the API client, the Supabase client, toast helpers, and shared types. Public surface in `packages/shared/src/index.ts`. +- **`packages/ui`** — Radix + Tailwind component library. Source-only. Components in `packages/ui/src/components/` (`button.tsx`, `dialog.tsx`, `select.tsx`, ...). +- **`packages/shared-types`** — zero-runtime-dependency TypeScript types. Auto-generated `database.types.ts` + hand-written domain layers (`tablos.types.ts`, `tablo-data.types.ts`, `events.types.ts`, `kanban.types.ts`, `stripe.types.ts`, `admin.types.ts`). +- **`packages/auth-ui`, `packages/chat-ui`, `packages/tablo-views`** — feature-scoped UI packages, also source-only. +- **Query keys** — convention enforced by colocation in `apps/main/src/hooks/` and feature naming (`["tablos", id]`, `["tablo-files", id]`, etc.). +## Source-Only Package Pattern +## Entry Points +| App / package | Entry file | +|----------------------|---------------------------------------------------------| +| `apps/main` | `apps/main/src/main.tsx` -> `App.tsx` | +| `apps/external` | `apps/external/src/main.tsx` -> `routes.tsx` | +| `apps/clients` | `apps/clients/src/main.tsx` -> `App.tsx` / `routes.tsx` | +| `apps/admin` | `apps/admin/src/main.tsx` -> `routes.tsx` | +| `apps/api` | `apps/api/src/index.ts` -> `routers/index.ts` | +| `apps/chat-worker` | `apps/chat-worker/src/index.ts` | +| `@xtablo/shared` | `packages/shared/src/index.ts` | +| `@xtablo/ui` | `packages/ui/src/components/index.ts` | +| `@xtablo/shared-types` | `packages/shared-types/src/index.ts` | +## Build & Deployment Notes +- **Turborepo** orchestrates tasks with caching (`turbo.json` at repo root). +- `apps/main` and other Vite apps deploy to **Cloudflare Workers** via `wrangler.toml` and the bundled `worker/` folder. +- `apps/api` compiles TypeScript and deploys to **Google Cloud Run**; secrets resolved via Google Secret Manager. +- `apps/chat-worker` deploys as a **Cloudflare Worker with Durable Objects**. +- Observability: Datadog RUM on frontends (`apps/main/src/lib/rum.ts`), `dd-trace` on the API (initialized at the top of `apps/api/src/index.ts`). + + + +## Project Skills + +No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, `.github/skills/`, or `.codex/skills/` with a `SKILL.md` index file. + + + +## GSD Workflow Enforcement + +Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync. + +Use these entry points: +- `/gsd-quick` for small fixes, doc updates, and ad-hoc tasks +- `/gsd-debug` for investigation and bug fixing +- `/gsd-execute-phase` for planned phase work + +Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it. + + + +## Developer Profile + +> Profile not yet configured. Run `/gsd-profile-user` to generate your developer profile. +> This section is managed by `generate-claude-profile` -- do not edit manually. +