# Frontend React 18 + Vite + Tailwind 3 + lucide-react + react-markdown/remark-gfm, Inter Variable self-hosted (`@fontsource-variable/inter` — no external requests). Built with `bun run build:frontend` into `public/` (Vite `emptyOutDir`; that directory is a build artifact, never hand-edit, not committed). ## Routing No router library — a tiny history wrapper (`lib/route.ts`: `navigate()`, `useRoute()`, trailing-slash normalization) drives pathname routing: - `/login` — sign-in card (username + access key + TOTP step). Renders instantly, **never shows the boot splash**. - `/register` — full-page self-registration (`RegisterPage.tsx`; key shown once, then hands username+key to `/login` via `sessionStorage`). No splash. - `/chat` — the platform itself (auth required; boots behind the splash). Bare `/chat` is the empty composer (new chat, created lazily on first send). - `/chat/:id` — one conversation page (deep-linkable, back/forward supported). Selecting a chat navigates there; unknown ids bounce back to `/chat`. Archived chats are viewed read-only on bare `/chat` (no per-id URL). - `/` — redirects to `/chat` when logged in, `/login` otherwise (placeholder for a future landing page). - Public routes (unchanged): `/share/:publicId`, `/reset/:token`, `/review/:id`, `/admin`. ## State & data flow - `App.tsx` owns: `user`, `models`/`model`, `convs`, `activeId`, `messages`, `streaming`, modal states. All server state is re-fetched (no client cache library); conversations themselves live server-side in SQLite. - `lib/api.ts` — typed `fetch` wrapper: Bearer header from `localStorage`, `{ error }` parsing, `ApiError` with status, auto logout+reload on 401 (except on `/share/*`). `chatStream()` parses SSE `token`/`done`/`error`. - Boot shows `LoadingScreen` for at least 1.5 s (`MIN_SPLASH_MS` in `App.tsx`) so the bloom animation always plays, even when the session restores instantly — **only on `/` and `/chat`**. `/login` and `/register` skip it. - Sending: ensure a server conversation exists (created lazily on first send), optimistic user message, stream tokens into `streaming`, then append the full assistant message and refresh the list (server auto-titles new chats). ## Components (`components/`) - `ui.tsx` — design system. `Button` (primary/secondary/ghost/danger), `IconButton`, `Input`, `Field`, `Modal`, `ConfirmDialog`, `ContextMenu`, `Avatar` (remote URL or gradient initials), `CopyButton`, `Spinner` and **`Picker`** — the framework-styled dropdown (pill button + popover, optional option groups, lucide `ChevronDown`/`Check`, Escape/outside-click to close). **Never use a native `