---
title: Frontend
description: A React 19 SPA on Vite 7 — 17 routes, two API layers, no global state library, and a test suite that guards against mock data returning.
---

The SPA has no global store. There is no Redux, no Zustand, and no data-fetching library — state is component `useState` plus a small set of bespoke hooks, and the server is the source of truth for everything except collection favourites, which live only in `localStorage`.

**[Routing](/frontend/routing)**

Every route, its guard, and the session state machine.

**[API client](/frontend/api-client)**

`fetchJSON`, the typed facade, and the hooks.

**[Data shapes](/frontend/types)**

Every domain interface, and where the shapes mislead.

**[Adding a feature](/frontend/recipes)**

Wiring a new endpoint in, end to end.

## Layout

<FileTree>

- src/
  - api/
    - tala.ts — typed facade over the collaboration surface
  - components/
    - auth/ — RequireAuth, RedirectIfAuthenticated
    - profile/ — six widgets, none currently imported
    - shared/ — AppHeader, AuthenticatedLayout, DashboardSidebar, OrganizationSwitcher, OrganizationMembersPanel
    - ui/ — shadcn primitives
  - context/
    - AuthContext.ts
    - AuthProvider.tsx
  - hooks/ — useRemoteData, useCollections, useAssetUpload, useLogin, …
  - pages/ — 16 page components
  - utils/
    - api.ts — transport, tokens, refresh
  - test/
    - setup.ts

</FileTree>

Roughly 5,400 lines across `src/` — 2,638 in `pages/` (excluding the two test files) and 1,232 in `components/`.

## Two API layers

Both layers are current.

| | `utils/api.ts` | `api/tala.ts` |
| --- | --- | --- |
| Level | transport | typed facade |
| Handles | bearer + org header, 401 refresh, token storage, `ApiError` | envelope unwrapping, request shapes, response interfaces |
| Used by | the older per-feature hooks, directly | the collaboration pages |
| Covers | anything | organizations, projects, tasks, comments, collections, assets, profile, plans, dashboard |

New code should go through `api/tala.ts`. The direct-`fetchJSON` hooks (`useCollections`, `useCollectionDetails`, `useAssetUpload`, `useAssetLogs`, and the four auth form hooks) predate it.

## Testing

Vitest, with a setup file at `src/test/setup.ts`. There is no end-to-end runner — the `playwright.config.ts` under `frontend-original-integration/` belongs to the untracked snapshot, not to the repo.

| File | Covers |
| --- | --- |
| `utils/api.test.ts` | transport, refresh, token handling |
| `pages/LibraryPage.upload.test.tsx` | the upload flow |
| `pages/noSyntheticData.test.ts` | guards against mock data creeping back into pages |

`noSyntheticData.test.ts` is the interesting one — it is a structural guard, not a behavioural test. It exists because the SPA used to be roughly 4,800 lines of convincing mock data, and the team decided that must never silently return.

:::warning[The guard only scans `src/pages/`]
`src/components/profile/` is outside its reach, and all six widgets there still synthesize data — `ActivityHeatmap` builds its year of activity from `(index * 17) % 5`. Nothing imports them, so none of it reaches a user, but the files are the last mock data in the tree and the test will not catch them if a page ever wires one up.
:::

:::note[`CLAUDE.md` is stale here]
`CLAUDE.md` still says no frontend test framework is configured. That has not been true since the Core MVP work landed.
:::
