How the operator console is structured, kept faithful to the design, wired to the gateway, and verified. Public usage lives in reference/admin-ui.md; this is the design rationale + invariants for contributors.
app/<route>/page.tsx ← one client page per route
│ uses
▼
components/shell (sidebar, topbar, app-shell) components/ui (primitives, overlays, toaster)
components/shared (PageHeader, DataPanel, KV/JSON/YAML) components/providers (theme + app ctx + toasts)
│
▼
lib/use-live ──(NEXT_PUBLIC_GATEWAY_URL?)──▶ lib/api (gateway fetch)
└────────────────fallback────────────▶ lib/mock (typed seed)
The console is a client-rendered Next.js App Router app: the shell + every
page are client components because the surface is heavily interactive (modals,
sheets, dropdowns, toggles, optimistic mutations). It builds statically
(output: standalone) and is configured with one env var.
The design target is shadcn/ui, but the primitives are hand-rolled in
TypeScript rather than pulled via the shadcn CLI (ADR-0019): deterministic
offline builds, a small fixed dependency set, and a 1:1 port of the handoff's own
hand-rolled prototype. The shadcn CSS-variable tokens (light + dark), Geist
fonts, animations, and YAML-highlight colors are copied verbatim from the
handoff into app/globals.css + tailwind.config.ts, so the result is
pixel-faithful.
cwd invariant: Tailwind's
contentglob is relative (./src/**/*.{ts,tsx}), so the dev server / build must run with the working directory atapps/admin-ui(the pnpm--filterscripts do this). Runningnext devfrom the repo root yields an unstyled app.
lib/use-live.ts is the seam: useLive(mockSelector, fetcher) seeds from local
data scoped to the active tenant, and — when NEXT_PUBLIC_GATEWAY_URL is set and
a fetcher is provided — replaces it with live data, falling back to the seed
on any error and exposing a source: "live" | "mock" flag the page surfaces as
a "Demo data" badge.
- Corpora / Webhooks pass real fetchers (
lib/api.ts→GET /v1/corpora,/v1/webhooks/subscriptions…). - Everything else passes
fetcher = null(seed only); API Keys / Tenants add a Preview marker.
This keeps the console always-renderable (CI, screenshots, no-backend preview) while real data lights up against a gateway, and makes the seed→live promotion a one-line change per resource as Phase 6 endpoints land.
Mutations on live pages (e.g. create/delete webhook, send test) call the gateway
when source === "live" and fall back to optimistic local state otherwise.
Writes that have no endpoint yet (corpus create/delete, connector setup, glossary
edits, key/tenant CRUD) are local-only in v0.
components/providers.tsx holds the global client state: active tenant,
sidebarCollapsed, a review viewState (the top-bar "State" control that
previews loading/empty/error renders), and a toast queue — plus next-themes for
dark mode. Per-page state (rows, selected row, dialog flags, filters) is local.
DataPanel (in shared.tsx) renders one of skeleton / empty / error / content;
useResolvedState lets the global review control override a page's own status so
every state is reviewable.
A frontend's correctness isn't only "tests pass", so the gates are layered
(ADR-0019), all wired into a CI admin-ui job:
tsc --noEmit(strict) +next lint.vitest+ React Testing Library: shell nav coverage, the Corpora page against the no-gateway seed path (asserts rows + Demo-data badge + the new-corpus dialog), the Webhooks one-time-secret reveal flow, and Audit filtering.- Production
next build(static export). - Visual fidelity confirmed by rendering the running app and diffing against the handoff screenshots.
- New page is a client component under
app/<route>/, added toshell/nav.ts, reusing the primitives +DataPanel? - Live data via
useLive(mockSelector, fetcher)with a seed fallback + source badge; seed-only viafetcher = null? - Pages whose backend doesn't exist carry a Preview marker?
- Tokens come from
globals.css/tailwind.config.ts(no ad-hoc colors)? tsc+next lint+vitest+next buildall green?
- reference/admin-ui.md — pages + config + running.
- ADR-0019 — the decision + deferrals.
apps/admin-ui/design/handoff/README.md— the source design spec.