# Architecture

_Last updated: 2026-05-14_

This document describes the high-level architecture of the `xtablo-source` monorepo: how apps, packages, and external services fit together, the dominant data-flow patterns, and where the key abstractions live.

## High-Level Diagram

```
                +-----------------------------------------------------+
                |                    Frontend Apps                    |
                |                                                     |
                |  apps/main         apps/external      apps/clients  |
                |  (dashboard)       (booking widget)   (client portal)|
                |  apps/admin                                         |
                |  (internal admin)                                   |
                +------+----------------+--------------------+--------+
                       |                |                    |
                       |   shared packages (source-only)     |
                       |   @xtablo/shared  @xtablo/ui        |
                       |   @xtablo/shared-types              |
                       |   @xtablo/auth-ui  @xtablo/chat-ui  |
                       |   @xtablo/tablo-views               |
                       |                                     |
                       v                                     v
                +-----------------+              +---------------------+
                |   apps/api      |<------------>|  apps/chat-worker   |
                |   (Hono REST)   |              |  (CF Durable Obj)   |
                +--------+--------+              +----------+----------+
                         |                                  |
        +----------------+----------------+-----------------+
        |                |                |                 |
        v                v                v                 v
   Supabase         Stripe           Cloudflare R2      Stream Chat
   (Postgres+Auth)  (payments)       (file storage)     (messaging)
                                                          |
                                                          + Datadog (RUM/APM)
                                                          + Google Secret Manager
```

## 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

All server state flows through React Query. Default cache time is 5 minutes. Query keys follow a hierarchical convention so that mutations can invalidate just the affected sub-tree:

```ts
["tablos"]                  // list
["tablos", tabloId]         // single
["tablo-files", tabloId]    // related collection
```

Hooks live in:
- `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

Used sparingly, primarily for the current user. The user is fetched via React Query then mirrored into a Zustand store so any component can read it synchronously:
- `useUser()` — throws if no session (use inside protected routes).
- `useMaybeUser()` — returns null if unauthenticated (use in route guards / public-aware components).

Provider: `apps/main/src/providers/UserStoreProvider.tsx` (mirrored in `apps/external/src/UserStoreProvider.tsx`).

### Direct Supabase queries vs API calls

Two parallel data-access patterns coexist:

1. **Direct Supabase** (`supabase.from("table").select()...`) — used from frontend hooks when row-level security is sufficient and no server-side logic is required. Client lives in `packages/shared/src/lib/supabase.ts` (re-exported via `apps/main/src/lib/supabase.ts`).
2. **API calls** (`api.get("/api/v1/...")`) — used when the operation needs the service role key, must run server-side logic (Stripe, file ops, email, multi-tenant integrity), or aggregates data. The HTTP client wrapper lives in `packages/shared/src/lib/api.ts` (re-exported via `apps/main/src/lib/api.ts`) and attaches the Supabase JWT as a Bearer token.

File operations use specialized mutation hooks (e.g. `useUploadTabloFile`, `useDeleteTabloFile`) that invalidate `["tablo-files", tabloId]` automatically.

## 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)

Entry: `apps/api/src/index.ts`. Flow:

1. `loadSecrets()` pulls secrets (locally from env, in staging/prod from Google Secret Manager) — `apps/api/src/secrets.ts`.
2. `createConfig(secrets)` produces the typed `AppConfig` — `apps/api/src/config.ts`.
3. `MiddlewareManager.initialize(config)` constructs the middleware singleton — `apps/api/src/middlewares/middleware.ts`.
4. The root Hono app applies `logger()` and a CORS middleware that only accepts `*.xtablo.com` and `localhost` origins.
5. All routes mount under `/api/v1` via `getMainRouter(config)` (`apps/api/src/routers/index.ts`).

### Middleware Manager (singleton pattern)

`MiddlewareManager` builds each piece of middleware once on init and exposes them as instance properties. The main router pulls them via `MiddlewareManager.getInstance()` and chains them in this fixed order:

```
supabase  ->  r2  ->  transporter  ->  stripe  ->  stripeSync
```

Auxiliary middleware modules:
- `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

Public-first, then authenticated. From `apps/api/src/routers/index.ts`:

1. `/public` — unauthenticated (`public.ts`).
2. `/tasks` — task router (`tasks.ts`).
3. `/revenuecat-webhook`, `/stripe-webhook` — webhook receivers.
4. `/admin` — admin-only routes (`admin.ts`, `adminAuth.ts`, ...).
5. `/client-auth`, `/client-portal`, `/client-invites` — client portal stack.
6. `/` — `maybeAuthRouter.ts` (optional auth — must come before authed to allow public booking).
7. `/` — `authRouter.ts` (requires JWT).

The exported `ApiRoutes` type (`ReturnType<typeof getMainRouter>`) enables Hono RPC clients to consume the API in a type-safe way.

## 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

`@xtablo/shared`, `@xtablo/ui`, `@xtablo/shared-types`, `@xtablo/auth-ui`, `@xtablo/chat-ui`, and `@xtablo/tablo-views` export TypeScript directly with no build step. Consumers import source files; Vite handles transpilation and HMR. Benefits: instant updates, no watch processes, simpler dependency graph. Constraint: no circular dependencies between packages, and the API can only depend on `@xtablo/shared-types` (pure types, no React).

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