# 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 — `App.tsx` matches `window.location.pathname` once (module level, constant for the page lifetime): `/share/:publicId` renders the public read-only `SharePage` (no auth), everything else renders the authenticated app. ## 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 2.5 s (`MIN_SPLASH_MS` in `App.tsx`) so the bloom animation always plays, even when the session restores instantly. - 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 `