Frontend
The web UI is a Vite SPA built with React, React Router v7 for client-side routing, Tailwind CSS v4 for styling, and a lightweight useSession() hook backed by /api/auth/session. There is no Next.js, no App Router, and no NextAuth in the frontend.
Pages
| Route | Component | Description |
|---|---|---|
/ | pages/Home.tsx | Home / dashboard |
/chat | pages/Chat.tsx | AI chat interface |
/calendar | pages/Calendar.tsx | Calendar view |
/todos | pages/Todos.tsx | Task list |
/files | pages/Files.tsx | File browser |
/media | pages/Media.tsx | Media library browser |
/cameras | pages/Cameras.tsx | Camera list with status |
/cameras/:id | pages/CameraDetail.tsx | Camera detail, events, test capture |
/paired-devices | pages/PairedDevices.tsx | Phone / tablet / TV pairing |
/devices | pages/Devices.tsx | IoT device management |
/devices/:id | pages/DeviceDetail.tsx | IoT device detail |
/events | pages/Events.tsx | IoT event log |
/automation | pages/Automation.tsx | Jobs, webhooks, actions, skills |
/memory | pages/Memory.tsx | Memory entries and raw file editor |
/plugins | pages/Plugins.tsx | Plugin management |
/settings | pages/Settings.tsx | App settings |
/settings/:slug | pages/SettingsBySlug.tsx | Per-plugin settings page |
/setup | pages/Setup.tsx | First-run wizard |
/audit | pages/Audit.tsx | Audit log viewer |
Routing
Routes are defined in apps/web/client/routes.tsx using React Router v7's lazy loading pattern:
export const routes = [
{ path: "/", lazy: () => import("./pages/Home").then((m) => ({ Component: m.default })) },
{ path: "/chat", lazy: () => import("./pages/Chat").then((m) => ({ Component: m.default })) },
// ...
];
The router is bootstrapped in apps/web/client/main.tsx with <BrowserRouter>. Unknown paths are caught by the SPA's index.html fallback on the server.
Authentication
The useSession() hook in apps/web/client/lib/use-session.ts fetches /api/auth/session once on mount to retrieve the current browser session. The backend issues an HTTP-only cookie after Google OAuth completes.
import { useSession } from "@/lib/use-session";
function MyComponent() {
const { user, status, isAdmin } = useSession();
// user: { email, name, picture } | null
// status: "loading" | "authenticated" | "unauthenticated"
// isAdmin: true for any signed-in browser session
}
There is no SessionProvider wrapper — each component that needs session state calls the hook directly.
State Management
The frontend uses local React state — no global state library. Each page fetches its own data and manages its own state:
useEffect → fetch /api/... → useState → render
Common Patterns
useStatefor UI state (modals, forms, loading flags)useCallbackfor memoized fetch functionsuseEffectfor initial data loading and side effectsuseReffor scroll positioning, AbortControllers, timers
Key Page Details
Chat (/chat)
- Full-screen chat interface with streaming responses
- Conversation list sidebar
- Messages rendered with
react-markdownandhighlight.jsfor code blocks - Supports conversation switching via URL query params
- AbortController for canceling in-flight requests
Memory (/memory)
- Two tabs: Entries (database records) and Raw (SOUL.md / MEMORY.md editors)
- Entry list with category grouping
- Create/edit/delete memory entries via modals
- Direct text editor for personality (SOUL.md) file
Cameras (/cameras)
- List view with connection status indicators
- Detail page (
/cameras/:id) includes configuration editor, event history with alert level badges, and "Test Capture" button
Media (/media)
- Grid/list view with thumbnail previews
- Filter by type (video, audio, image, document)
- Pagination controls
- Click-to-preview modal for images and video
Automation (/automation)
- Four tabs: Jobs, Webhooks, Actions, Skills
- Jobs: Create/edit cron jobs, view run history, manual execute
- Webhooks: Create/manage webhooks, copy webhook URL + secret
- Actions: Browse registered actions, test execute
- Skills: View loaded skills and their manifests
Paired Devices (/paired-devices)
- List of paired devices (phones, tablets, TVs) with live connection status
- QR-code pairing flow — generates a one-time code; device scans and receives tokens
- Device type badges and scope display
Styling
- Tailwind CSS v4 with utility classes
@tailwindcss/typographyplugin for prose styling (markdown rendering)- Dark mode via
prefers-color-scheme - Design tokens from
@genie/tokens
Key Files
| File | Purpose |
|---|---|
apps/web/client/main.tsx | SPA entry point — mounts React, wraps <BrowserRouter> |
apps/web/client/App.tsx | Root component — renders routes |
apps/web/client/routes.tsx | Route definitions (React Router v7, lazy pages) |
apps/web/client/lib/use-session.ts | Auth hook (/api/auth/session) |
apps/web/client/pages/ | Page components |
apps/web/client/components/ | Shared components |
apps/web/client/styles.css | Tailwind imports, global styles |