12 KiB
Structure
Last updated: 2026-05-14
Directory layout, conventions, and where to look for things in the xtablo-source monorepo.
Top-Level Layout
xtablo-source/
├── apps/ # Deployable applications
│ ├── main/ # Authenticated dashboard SPA (Vite + CF Workers)
│ ├── external/ # Public booking widget (Vite)
│ ├── clients/ # Client portal (Vite + CF Workers)
│ ├── admin/ # Internal admin tools (Vite + CF Workers)
│ ├── api/ # Hono REST API (Node, GCP Cloud Run)
│ └── chat-worker/ # Cloudflare Durable Object worker for chat
├── packages/ # Shared workspace packages (source-only)
│ ├── shared/ # Contexts, hooks, API client, toast, supabase client
│ ├── ui/ # Radix + Tailwind component library
│ ├── shared-types/ # Pure TypeScript types (zero runtime deps)
│ ├── auth-ui/ # Auth screens (login/register shells)
│ ├── chat-ui/ # Chat-specific UI components
│ └── tablo-views/ # Tablo detail / sections components
├── backend/ # Legacy / auxiliary backend code
├── go-backend/ # Go services (separate stack)
├── frontend_v2/ # Frontend v2 prototype
├── xtablo-expo/ # React Native (Expo) app
├── supabase/ # Supabase project (migrations, config)
├── infra/ # Infrastructure-as-code
├── docs/ # Architecture, deployment, integration docs
├── prompts/ # Prompt templates
├── scripts/ # Repo scripts
├── CLAUDE.md # Claude Code guidance (root)
├── DEVELOPMENT.md # Dev guide
├── turbo.json # Turborepo pipeline config
├── pnpm-workspace.yaml
└── package.json
Apps
apps/main (@xtablo/main)
Primary dashboard. Internal layout:
apps/main/src/
├── main.tsx # Vite entry
├── App.tsx # Root component, providers
├── i18n.ts # i18next setup
├── main.css # Tailwind entry
├── components/ # UI components (Modals, Sections, Cards, ...)
├── contexts/ # App-local React contexts (UpgradeBlockContext, ...)
├── hooks/ # Feature hooks (tablos, events, tasks, stripe, ...)
├── lib/
│ ├── api.ts # API client wrapper
│ ├── supabase.ts # Supabase client re-export
│ ├── routes.tsx # Protected routes
│ ├── publicRoutes.tsx # Public/auth routes
│ ├── billing.ts # Stripe-related helpers
│ ├── env.ts # Env validation
│ └── rum.ts # Datadog RUM init
├── pages/ # Route page components
├── providers/
│ └── UserStoreProvider.tsx # Zustand user store
├── locales/ # i18n JSON
├── utils/
└── assets/
apps/external (@xtablo/external)
Embeddable booking widget. Internal layout:
apps/external/src/
├── main.tsx
├── routes.tsx
├── EmbeddedBookingPage.tsx
├── FloatingBookingWidget.tsx
├── CustomModal.tsx
├── UserStoreProvider.tsx
├── lib/
└── locales/
Mode is driven by query string: ?mode=embed&eventTypeId=... or ?mode=floating.
apps/clients (@xtablo/clients)
Public-facing client portal.
apps/clients/src/
├── main.tsx
├── App.tsx
├── routes.tsx
├── components/
├── hooks/
├── lib/
├── pages/
├── locales/
└── test/
apps/admin (@xtablo/admin)
Internal admin app.
apps/admin/src/
├── main.tsx
├── App.tsx
├── routes.tsx
├── components/
├── hooks/
├── lib/
├── pages/
└── registry/
apps/api (@xtablo/api)
Hono REST API. Internal layout:
apps/api/src/
├── index.ts # Entry: tracer, secrets, server start
├── config.ts # createConfig(secrets) -> AppConfig
├── secrets.ts # loadSecrets() (env or GCP Secret Manager)
├── client.ts # Supabase admin client factory
├── middlewares/
│ ├── middleware.ts # MiddlewareManager singleton + supabase/r2/stripe
│ ├── stripeSync.ts # Stripe<->Supabase sync engine
│ └── transporter.ts # Email transport middleware
├── routers/
│ ├── index.ts # getMainRouter — composition + ordering
│ ├── public.ts # Unauthenticated endpoints
│ ├── authRouter.ts # Requires JWT
│ ├── maybeAuthRouter.ts # Optional auth (booking-aware)
│ ├── tablo.ts # Tablo CRUD
│ ├── tablo_data.ts # Tablo content blocks
│ ├── tasks.ts # Tasks
│ ├── notes.ts # Notes
│ ├── events.ts (in user.ts/tablo_data.ts)
│ ├── stripe.ts # Stripe webhook + ops
│ ├── revenuecat.ts # RevenueCat webhook
│ ├── invite.ts # Tablo invites
│ ├── user.ts # User profile / settings
│ ├── admin*.ts # admin.ts, adminActions.ts, adminAuth.ts,
│ │ # adminDatasets.ts, adminOverview.ts, adminTables.ts
│ ├── clientAuth.ts # Client portal magic-link auth
│ ├── clientPortal.ts # Client portal endpoints
│ └── clientInvites.ts # Public client invites
├── helpers/
├── types/ # BaseEnv, app.types.ts
└── __tests__/
apps/chat-worker
apps/chat-worker/src/
├── index.ts # Worker entry
├── durable-objects/ # Durable Object classes
└── lib/
Packages
packages/shared (@xtablo/shared)
Public surface via packages/shared/src/index.ts.
packages/shared/src/
├── index.ts # Barrel
├── contexts/
│ ├── SessionContext.tsx # Supabase session listener
│ └── ThemeContext.tsx
├── hooks/
│ ├── auth.ts # useSignIn / useSignOut / useSession ...
│ ├── book.ts # Booking hooks
│ ├── public.ts # Public data hooks
│ └── useClickOutside.ts
├── lib/
│ ├── api.ts # HTTP client w/ Bearer token
│ ├── supabase.ts # Supabase JS client
│ ├── toast.ts # toast.add() wrapper (sonner)
│ └── cn.ts # clsx + tailwind-merge
├── types/ # Re-exported domain types
└── utils/
└── helpers.ts
packages/ui (@xtablo/ui)
Radix UI + Tailwind component library. Source-only.
packages/ui/src/
├── components/ # button.tsx, dialog.tsx, select.tsx,
│ # popover.tsx, tabs.tsx, dropdown-menu.tsx, ...
├── hooks/
└── styles/
packages/shared-types (@xtablo/shared-types)
Zero-dependency TypeScript types — safe to import from API and frontend.
packages/shared-types/src/
├── index.ts
├── database.types.ts # Auto-generated by supabase gen types
├── tablos.types.ts
├── tablo-data.types.ts
├── events.types.ts
├── kanban.types.ts
├── stripe.types.ts
├── admin.types.ts
└── utils.ts
packages/auth-ui (@xtablo/auth-ui)
Auth screens shared between apps: AuthCardShell.tsx, AuthEmailPasswordForm.tsx, AuthInfoBanner.tsx.
packages/chat-ui (@xtablo/chat-ui)
Chat-specific UI (components/, hooks.ts, security.ts, types.ts, chat-ui.css).
packages/tablo-views (@xtablo/tablo-views)
Tablo detail sections and shell: TabloDetailsShell.tsx, TabloEventsSection.tsx, TabloFilesSection.tsx, TabloTasksSection.tsx, TabloDiscussionSection.tsx, TabloHeaderActions.tsx, EtapesSection.tsx, RoadmapSection.tsx, plus single-tablo/, components/, hooks/, styles/.
Naming Conventions
- Modals:
*Modal.tsx— e.g.apps/main/src/components/CreateTabloModal.tsx,EventDetailsModal.tsx. - Sections:
*Section.tsx— e.g.packages/tablo-views/src/TabloFilesSection.tsx. - Cards:
*Card.tsx— e.g.apps/main/src/components/EventTypeCard.tsx,AvailabilityCard.tsx. - Pages: live in
apps/<app>/src/pages/, lowercased file names matching the route (planning.tsx,events.tsx,login.tsx). - Tests: co-located as
*.test.tsx/*.test.tsnext to the source (AvailabilityCard.test.tsx,auth.signup.test.ts). - Hooks: lowercase domain file in
hooks/(tablos.ts,events.ts,tasks.ts); each exports multiple named hooks. - UI primitives: lowercase in
packages/ui/src/components/(button.tsx,select.tsx).
Key File Locations (Cheatsheet)
| Concern | Path |
|---|---|
| Main app routes (protected) | apps/main/src/lib/routes.tsx |
| Main app public routes | apps/main/src/lib/publicRoutes.tsx |
| External app routes | apps/external/src/routes.tsx |
| Clients app routes | apps/clients/src/routes.tsx |
| Admin app routes | apps/admin/src/routes.tsx |
| Session context | packages/shared/src/contexts/SessionContext.tsx |
| User store (Zustand) | apps/main/src/providers/UserStoreProvider.tsx |
| HTTP API client | packages/shared/src/lib/api.ts |
| Supabase client (browser) | packages/shared/src/lib/supabase.ts |
| API entry | apps/api/src/index.ts |
| API router composition | apps/api/src/routers/index.ts |
| API middleware singleton | apps/api/src/middlewares/middleware.ts |
| API config | apps/api/src/config.ts |
| API secrets loader | apps/api/src/secrets.ts |
| Auto-generated DB types | packages/shared-types/src/database.types.ts |
| Shared barrel export | packages/shared/src/index.ts |
| UI component barrel | packages/ui/src/components/index.ts |
| Turborepo pipeline | turbo.json |
| Workspace definition | pnpm-workspace.yaml |
| Root Claude guidance | CLAUDE.md |
Adding New Features (5-Step Flow)
From CLAUDE.md, the canonical workflow for a new feature is:
- Define types in
packages/shared-types/src/(or runnpx supabase gen types typescript > packages/shared-types/src/database.types.tsif you changed the DB schema). - Add API endpoint in
apps/api/src/routers/— pick the right router (public.ts,maybeAuthRouter.ts,authRouter.ts, or a domain router liketasks.ts). Mount it inapps/api/src/routers/index.tsif it's new. - Create a React Query hook in either
packages/shared/src/hooks/(cross-app) orapps/main/src/hooks/(app-local). Use the hierarchical query-key convention so mutations can invalidate predictably. - Build the UI using primitives from
@xtablo/uiand patterns from@xtablo/tablo-views/@xtablo/chat-uiwhere applicable. Follow the*Modal.tsx/*Section.tsx/*Card.tsxnaming convention and co-locate a*.test.tsx. - Wire the route in
apps/main/src/lib/routes.tsx(or the equivalent in the relevant app) so the new page is reachable.
Testing Locations
- API tests:
apps/api/src/__tests__/(Vitest, mock Supabase). - Frontend tests: co-located
*.test.tsxnext to source, run viapnpm testper app (apps/main/src/setupTests.tsconfigures happy-dom + Testing Library). - Docs on testing:
docs/API_TESTS.md,docs/MIDDLEWARE_TESTS.md.
Documentation Index (in docs/)
DEVELOPMENT.md— broad dev guide.API_*.md— API testing and integration.STRIPE_*.md— Stripe integration deep-dives.AUTH_*.md— Authentication patterns.DOCKER_*.md— Docker build optimization.CLOUD_BUILD_*.md— GCP Cloud Build setup.