> ## Documentation Index
> Fetch the complete documentation index at: https://docs.causeloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend repo tour

> A guided tour of the Next.js console repo: route groups, the API client layer, component tree, brand tokens, and test setup.

The frontend repo (`/Users/rushabhpatel/Desktop/causeloop.ai/frontend`) is a single Next.js 15 / React 19 application (`causeloop-console`) that serves two distinct authenticated surfaces — the tenant console and the staff onboarding portal — behind one same-origin API proxy. Unlike the backend, this repo has no legacy/product split: it is entirely the product. Its own README states the governing rule plainly: "The production frontend uses one deliberately small backend contract: `services.api.product_app:app`" — the full research backend (`services.api.main`, [Legacy Surfaces](/api-reference/research-api)) is not a production dependency of this app.

<Note>
  `npm run audit:mvp:strict` (`scripts/audit-mvp-surface.mjs`) statically extracts every backend call this codebase makes from the TypeScript AST and fails if it finds a legacy/compatibility family, an unclassified call, or an import of the synthetic fixture/retired report adapter/provenance module from production code. Run it before trusting any change that touches `src/lib/api/`.
</Note>

## Directory map at a glance

| Directory            | Contains                                                                                                                                                 |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/app/`           | Every route: `(console)` tenant console, `admin/(portal)` staff portal, `login`, `accept-invite`, `api/backend/[...path]` proxy, `api/workspaces` facade |
| `src/lib/api/`       | One typed function per backend call, plus the shared `client.ts` fetch wrapper                                                                           |
| `src/lib/data/`      | `useConsoleData` (the one dashboard data hook), `client-adapter.ts`, view-model `types.ts`                                                               |
| `src/lib/stores/`    | Zustand stores for client-only UI state (theme, toasts, lens, misc UI)                                                                                   |
| `src/lib/selectors/` | Pure functions deriving chart/KPI shapes from `ConsoleData`                                                                                              |
| `src/components/`    | `shell/`, `ui/`, `charts/`, `overlays/`, `views/`, `onboarding/`                                                                                         |
| `src/styles/`        | `tokens.css` brand system, `atmosphere.css`                                                                                                              |
| `e2e/`               | Playwright specs                                                                                                                                         |
| `scripts/`           | `audit-mvp-surface.mjs` and other dev tooling                                                                                                            |

## `src/app/` — routes

```mermaid theme={null}
flowchart TD
    L["/login — tenant/staff entry point"] --> C
    L --> A
    AI["/accept-invite — tenant invitation acceptance"] --> C
    C["(console) route group — authenticated tenant console"]
    A["admin/(portal) route group — authenticated staff portal"]
    A --> AL["/admin/login — staff-only entry point"]
    P["/api/backend/[...path] — same-origin product API proxy"]
    W["/api/workspaces — public workspace-directory facade"]
    C -.->|every fetch| P
    A -.->|every fetch| P
    L -.->|populates workspace picker| W
```

<AccordionGroup>
  <Accordion title="(console)/ — tenant console">
    A route group (parentheses are excluded from the URL) covering `/`, `/sources`, `/loops`, `/issues`, `/remediation`, and `/settings`. `layout.tsx` is the auth gate for the whole group: it reads the `TENANT_SESSION_COOKIE` server-side, calls the backend's `GET /me` directly (forwarding the cookie), and redirects to `/login` on any failure — there is no client-side auth check. On success it builds a `Session` (name, initials, `WorkspaceRole` mapped from the identity's permissions, tenant id/name) and provides it via `session-context.tsx`'s `SessionProvider`/`useSession()` to every page and component below it. The same layout also mounts the app shell used by every console route: `Rail`, `Pagebar`, `InsightsBanner`, and the singleton overlays (`Scrim`, `LoopDrawer`, `MoreModal`, `IssueModal`, `PlanModal`, `CommandPalette`, `Toaster`) — these are mounted once here rather than per-view specifically so opening a drawer/modal works from any route.
  </Accordion>

  <Accordion title="admin/(portal)/ — staff onboarding portal">
    Covers `/admin` (the tenant list, the group's own index page), `/admin/tenants/[tenantId]`, `/admin/tenants/new`, `/admin/jobs`, and `/admin/control-plane` — there is no separate `/admin/tenants` page. `layout.tsx` gates the group by calling the backend's `GET /admin/me` with the `ADMIN_SESSION_COOKIE`, redirecting unauthenticated staff to `/admin/login`. `AdminRail.tsx`/`AdminPagebar.tsx` are this portal's own shell components (parallel to, but separate from, the tenant console's `Rail`/`Pagebar`). This is the staff-facing UI for the flow documented in [Client onboarding pipeline](/features/client-onboarding-pipeline).
  </Accordion>

  <Accordion title="login/ and accept-invite/">
    `login/page.tsx` is one page with two tabs — client and staff — calling `tenantLogin`/`adminLogin` respectively depending on which tab is active; it also fetches `/api/workspaces` to populate a workspace picker for the client tab. `accept-invite/page.tsx` is where a tenant user invited by staff (via Mailpit locally, SES/Azure Email in cloud) lands to set a password and complete `POST /accept-invite`.
  </Accordion>

  <Accordion title="api/backend/[...path]/ — the product API proxy">
    `route.ts` is the only path the browser uses to reach the backend — the browser never calls the API origin directly. It: requires an `ADMIN_SESSION_COOKIE` or `TENANT_SESSION_COOKIE` for every path except an explicit `PUBLIC_PATHS` allowlist (login, accept-invite, and password-reset routes plus the three `/health*` operations); strips every inbound `x-causeloop-*` header before forwarding (so a client cannot spoof a trust header the backend might read); forwards the real client IP from `x-forwarded-for` as `x-causeloop-client-ip`; and returns a stable `502` JSON body if the backend is unreachable rather than letting Next.js surface an opaque `500`. `BACKEND_URL` (`src/lib/backendUrl.ts`) defaults to `http://127.0.0.1:18000`, matching the product Compose API port, and is overridden by `CAUSELOOP_API_URL`.
  </Accordion>

  <Accordion title="api/workspaces/">
    A small unauthenticated facade: it fetches the backend's `GET /workspaces` server-side and reshapes the response into `{ clientId, company, industry, status }` for the login page's picker, returning `{ workspaces: [], degraded: true }` on any backend failure rather than surfacing an error to a page that has to work for a user who isn't signed in yet.
  </Accordion>
</AccordionGroup>

## `src/lib/` — data and client layer

<Steps>
  <Step title="src/lib/api/ — one function per backend call">
    `client.ts` is the shared fetch wrapper (`apiGet`/`apiSend`, both against the `/api/backend` prefix) with uniform JSON parsing and `ApiError` construction (`errors.ts`). Every other file in this directory is a thin, typed layer directly over one backend concern, and each exported function corresponds to exactly one backend operation — cite file + function when documenting endpoint-to-frontend wiring:

    * `tenantAuth.ts` — `tenantLogin`, `acceptInvitation`, `tenantMe`, `tenantLogout`, plus `mapPermissionsToWorkspaceRole` (backend permission strings → the console's `WorkspaceRole` type).
    * `adminAuth.ts` — `adminLogin`, `adminLogout`, `adminMe`.
    * `adminOnboarding.ts` — the full staff onboarding surface: `listTenants`, `getTenant`, `createTenant`, `provisionTenant`, `uploadIngestFile`, `commitIngest`, `putTrainingConfig`, `trainModel`, `listCheckpoints`, `activateCheckpoint`, `inviteUser`, `listInvitations`, `goLive`, `getRetrainRecommendation`, `getDataPolicy`/`putDataPolicy`, `seedSources`, `impersonateTenant`, and status pollers (`getProvisionStatus`, `getTrainStatus`).
    * `clientInsights.ts` — `fetchClientInsights()`, the single call behind the tenant console's `GET /insights/snapshot`; also exports the `MODEL_COLLECTIONS`/`LLM_COLLECTIONS` constants describing the snapshot's 10 sub-collections (5 model-derived, 5 LLM-generated).
    * `tenantSources.ts` — `listTenantSources`, `createTenantSource`, `uploadTenantSource`.
    * `tenantMembers.ts` — `listTenantMembers`.
    * `hooks-tenant-remediation.ts` — TanStack Query hooks over the remediation API: `useTenantCaps`, `useCreateTenantCap`, `useUpdateTenantCap`, `useCompleteTenantCapStep`, `useTenantReviews`, `useCreateTenantReview`.
    * `remediationExport.ts` — `downloadExport()` against `GET /remediation/export.xlsx`.
    * `adminMetrics.ts` — parses the staff `GET /metrics` Prometheus text response (`parsePrometheusText`, `toJobDashboardData`) for the jobs dashboard.
  </Step>

  <Step title="src/lib/data/ — the dashboard data hook">
    `useConsoleData.ts` is the single hook every dashboard view calls; it wraps `fetchClientInsights` in a `useQuery` and reduces the result to one of five states — `loading`, `locked` (a tenant onboarded with "share onboarding outputs" off, before anything is visible), `ready` (with the adapted `ConsoleData`, any still-generating LLM collections, and the narrative text), `error` (with a `refetch()` the shared `ErrorState` component wires to a Retry button), and implicitly `empty` via the adapter. `client-adapter.ts` (`adaptClientInsights`) does the actual snapshot → `ConsoleData` shape conversion and is deliberately defined at module scope so TanStack Query can memoize it as a stable `select` function. `types.ts` defines the `ConsoleData` view-model types. `__fixtures__/client-insights.fixture.ts` is a test-only backend-snapshot fixture (the audit script's directory walk explicitly skips any `fixtures` directory). Separately, `demo-data.json` in this same directory is the bundled synthetic report used by the selector unit tests; `scripts/audit-mvp-surface.mjs` explicitly flags any production import of `demo-data.json` or the retired `data/provenance` module.
  </Step>

  <Step title="src/lib/stores/ — client UI state">
    Zustand stores for state that is genuinely client-only: `theme.ts` (light/dark, persisted to `localStorage` under `cl-theme`, applied via `document.documentElement.dataset.theme`), `ui.ts`, `lens.ts`, `toast.ts`. Per the README's data rule, browser storage here is used only for UI preferences — never as a second source of truth for tenant data.
  </Step>

  <Step title="src/lib/selectors/ — pure view derivations">
    `home.ts`, `issues.ts`, `loops.ts`, `remediation.ts`, `hazardWave.ts` — pure functions that derive chart/KPI-ready shapes from `ConsoleData` (e.g. `homeKpis`, `commonCauses`, `scoreBins`, `boneAllocationForLoop`, `donutByBone`, `roiByLoop`). These are unit-tested directly against `src/lib/data/demo-data.json` (a fixture dataset, not live data) rather than through component rendering.
  </Step>
</Steps>

## `src/components/` — component tree

* **`shell/`** — app chrome shared by (most of) the app: `Rail.tsx`, `Pagebar.tsx`, `InsightsBanner.tsx`, `ThemeToggle.tsx`/`ThemeInit.tsx`, `Toaster.tsx`, `LockView.tsx` (the "locked" console state), `ErrorState.tsx`, `Skeletons.tsx`, `LensSeg.tsx`.
* **`ui/`** — generic primitives: `Btn.tsx`, `Modal.tsx`, `Drawer.tsx`, `Tabs.tsx`, `Seg.tsx`, `Chip.tsx`, `Badge.tsx`, `Switch.tsx`, `Avatar.tsx`, `Kpi.tsx`, `ListRow.tsx`, `BoardCard.tsx`, `Callout.tsx`, `Disclosure.tsx`, `Scrim.tsx`, `TooltipLayer.tsx`, `LiveDot.tsx`, `FakeInput.tsx`.
* **`charts/`** — the console's data visualizations, largely ported line-for-line from an earlier static HTML mockup (each file's header comments cite the exact mockup function and line range it was ported from): `Fishbone.tsx`/`BoneChart.tsx`, `Constellation.tsx`, `HazardWave.tsx`, `LoopHeat.tsx`, `BubbleMap.tsx`, `Donut.tsx`, `DriverBars.tsx`, `ScoreHist.tsx`, `Sparkline.tsx`, `EngineGraphic.tsx`.
* **`overlays/`** — the singleton, route-independent overlays mounted once by the console layout: `CommandPalette.tsx`, `LoopDrawer.tsx`, `MoreModal.tsx`, `IssueModal.tsx`, `PlanModal.tsx`.
* **`views/`** — one component per console route's main content: `HomeView.tsx`, `IssuesView.tsx`, `LoopsView.tsx`, `RemediationView.tsx`, `SourcesView.tsx`/`SourcesClient.tsx`, `SettingsView.tsx`, `AggregationPanel.tsx`.
* **`onboarding/`** — `onboarding.css`, shared by the staff portal's onboarding flow pages.

Every visual component directory pairs `.tsx` files with a co-located `.css` file (`charts.css`, `home.css`, `remediation.css`, etc.) and, for anything with real logic, a co-located `.test.tsx`/`.test.ts`.

## `src/styles/` — the brand system

`tokens.css` is "Brand System v2.2," explicitly ported verbatim from a static HTML mockup's `<style>` block. It defines the entire color system as CSS custom properties on `:root` (base palette: `--cl-navy`, `--cl-mag-400/700/300`, `--cl-peri-*`, `--cl-amber-*`, `--cl-red-*`, `--cl-green-*`, then semantic tokens built from them: `--bg`, `--ink`, `--body`, `--muted`, `--surface*`, `--line*`, `--accent-*`, `--shadow*`) and a full parallel set under `[data-theme="dark"]`. The theme store (`src/lib/stores/theme.ts`) toggles that `data-theme` attribute; it never touches `document`/`window` at module-init time, so SSR and first client render always agree (no flash-of-wrong-theme handling is attempted or needed). `atmosphere.css` layers in background decoration; `tokens.test.ts` guards the token contract itself.

<Warning>
  Do not reintroduce hardcoded hex colors in component CSS — the whole point of `tokens.css` is that every surface reads color, spacing, and shadow through these custom properties so both themes stay in sync automatically.
</Warning>

## Test setup

* **Unit/component tests — Vitest** (`vitest.config.ts`): jsdom environment, global test APIs, `src/test/setup.ts` (which just imports `@testing-library/jest-dom/vitest`), path alias `@ -> src/`, and an explicit exclude for `e2e/**` (Vitest's default include glob would otherwise sweep up Playwright specs and fail them on `test.describe`). Run with `npm test` (or `npm run test:watch`).
* **End-to-end — Playwright** (`playwright.config.ts`, specs in `e2e/`: `auth.spec.ts`, `console.spec.ts`, `helpers.ts`): single worker, no retries, `baseURL http://localhost:3000`, and a `webServer` block that runs `npm run dev` and reuses an already-running dev server. The FastAPI product app must already be reachable at `http://127.0.0.1:18000` (i.e. the product Compose stack must be up) — Playwright only manages the Next.js dev server, not the backend. Authenticated specs require `CAUSELOOP_E2E_WORKSPACE`, `CAUSELOOP_E2E_EMAIL`, and `CAUSELOOP_E2E_PASSWORD` for a seeded tenant; without them, only the unauthenticated-redirect check runs and the rest are explicitly skipped. Run with `npm run e2e`.
* **Static product-call gate**: `scripts/audit-mvp-surface.mjs`, run via `npm run audit:mvp` / `npm run audit:mvp:strict`.

## `package.json` scripts

| Script                                           | What it does                                                                             |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| `npm run dev`                                    | `next dev` — hot-reloading dev server on port 3000.                                      |
| `npm run build`                                  | `next build` — production build; part of the required check list.                        |
| `npm run start`                                  | `next start` — serve a production build (what the container's `Dockerfile` runs).        |
| `npm run lint`                                   | `next lint`.                                                                             |
| `npm run typecheck`                              | `tsc --noEmit`.                                                                          |
| `npm test` / `npm run test:watch`                | Vitest, once or in watch mode.                                                           |
| `npm run audit:mvp` / `npm run audit:mvp:strict` | The static backend-call gate described above; `--strict` is the required pre-merge form. |
| `npm run e2e`                                    | `playwright test`.                                                                       |

The full required-check sequence (from the repo's own README) is `npm run audit:mvp:strict && npm run typecheck && npm test -- --run && npm run build`.

## Related

* [Prerequisites](/onboarding/prerequisites)
* [Backend repo tour](/onboarding/backend-repo-tour)
* [Local product stack](/onboarding/local-product-stack)
* [Tenant console wiring](/api-reference/integration/tenant-console-wiring)
