The app uses Next.js App Router with file-based routing. There are three route groups ((app), (org-settings), (portal)) plus several ungrouped public routes:
There is no(app)/dashboard route — it was retired in favor of home/. Org settings live under the (org-settings) group, not a flat /organization-details.
The root layout (src/app/layout.tsx) wraps the tree in a next-themesThemeProvider and then AppProviders (src/lib/providers/AppProviders.tsx). The rendered nesting is:
AuthenticatedProviders classifies resolved paths before mounting private contexts. Sign-in and sign-up retain the session cache without downloading workspace providers. Portal and service-console routes use the private auth boundary with workspace data disabled; they do not mount workspace bootstrap, the assistant/Mintlify widget, or WebMcpTools. WebMcpTools is a sibling of the assistant inside workspace bootstrap.Keep ApiTokenGate and both SWR cache owners above the dynamic, route Suspense, and Activity boundaries. Hiding an SWR cache owner can remove its global registration while retained consumers still reference it. Loading gates preserve children with Activity; token verification and organization bootstrap stay outside the boundary whose readiness they determine. Session, user, and organization changes replace the session cache, and scoped mutations cannot reach public or later-session caches.Unclassified incoming children wait behind the loading shell. A validated private subtree retains its provider identity through pathname suspension, preserving drafts on same-route refreshes. Account forms retain their validated children in a hidden Activity boundary until the path resolves; incoming private children cannot render through that retained public branch. Public booking has its own stable cache and remains interactive while Clerk is unavailable; a retained public page stays hidden until its pathname is validated again.Gig pages use next/dynamic for the calendar, maps, rich text, section drag grid, and heavy tab panels. The read-only rich-text viewer imports DOMPurify independently of the Tiptap editor; viewing notes must not request the editing engine. Preserve the existing conditional/deferred-tab mounting so inactive panels do not request their chunks. Skeletons use the existing UI primitives, and the travel tab warms its chunk on hover or focus. Temporal remains shared because public booking and appearance storage use it; coordinate timezone lookup is absent from sign-in’s initial bundle.
cacheComponents and partialPrefetching are on in web/next.config.mjs. Next prerenders a static (or Partial Prerender) shell per route; Instant Navigations prefetch that shell in production. Almost every (app) page is still 'use client' + SWR after Clerk hydration, and useWorkspaceLoadingGate still gates the protected chrome. Do not move auth or org data onto 'use cache' — SWR remains the data layer.Staff app paths live in web/src/lib/auth/protectedAppRoutes.ts. Middleware and AppProviders both use that list so a single-segment staff page cannot fall through to the public [slug] booking profile.
The Axios client and request/response types are generated from the OpenAPI spec — do not hand-write them. web/scripts/generate-api-client.mjs runs swagger-typescript-api against docs/openapi/soundcheck.json and emits a typed, modular client into web/src/lib/generated/api-client/.
npm run generate:api-client # node scripts/generate-api-client.mjs
This runs automatically via the predev, prebuild, pretypecheck, and pretest hooks, and skips regeneration when the output is newer than the spec (pass --force or FORCE_API_CODEGEN=1 to override). The generated client lives under src/lib/generated/ and is gitignored — a fresh checkout must regenerate it (this happens automatically on npm run dev/build).
The app uses Tailwind CSS v4. There is no tailwind.config.* file — configuration lives in src/app/globals.css via @import "tailwindcss", an @custom-variant dark, and an @theme inline { … } block that maps utility names to CSS variables (the shadcn v4 pattern). Prefer Tailwind utility classes:
Calendars (Gigs, My Availability) use the vendored ReUI event calendar in components/reui/event-calendar/, which is Tailwind-only; see components/reui/README.md.(react-pdf also imports its own annotation-layer CSS for the PDF viewer.) Outside these, avoid adding .css files.
Rich text uses headless Tiptap (@tiptap/react + starter-kit, table, text-align, text-style extensions) in components/common/Gigs/RichTextEditor/. TinyMCE has been removed — the only remaining references are backward-compat comments for content authored by the old editor.
Semantic color tokens are defined as CSS variables in globals.css (with :root light values and .dark overrides from the dark-mode redesign) and exposed as utilities:
Token
Usage
text-finance, bg-finance
Financial positive (income, profit)
text-success, bg-success
General positive states
text-destructive, bg-destructive
Negative states (errors, delete)
text-warning, bg-warning
Caution states
text-info, bg-info
Informational states
Avoid hardcoded colors like text-green-500. Use semantic tokens for stateful UI. See web/docs/THEMING.md for the full light/dark token contract.