Platform Backend
Studio Frontend
The Studio is a Next.js application (App Router) in frontend/. It renders the two user interfaces of the
platform — the classic Studio and the AI Assistant — signs users in, and serves the OAuth endpoints of
the hosted MCP server. It holds no business logic of its own: data comes from the backend’s REST API and,
for the assistant, from its Socket.IO connection. frontend/ARCHITECTURE.md states the rendering and data
rules in short; this page puts them in context.
Layout
| Path | Contents |
|---|---|
|
Routes. |
|
Feature modules shared by both interfaces: |
|
The component library: buttons, inputs, selects and comboboxes, dialogs and sheets, tables, tags, toggle cards — built on Base UI and Tailwind CSS. |
|
Application components used across features: breadcrumbs, |
|
React contexts — the assistant’s WebSocket connection, workspace scope, navigation guard, UI preferences —
and hooks such as |
|
The API client, authentication helpers, environment detection, the MCP OAuth implementation, the query
client, |
The Studio’s route tree follows the domain: /dashboard/studio/accounts/{tenantId}/projects/{projectId}/
and below it agents, knowledges, evaluations, integrations, interactive-content,
content-navigation, analytics, members, settings, identity-providers and the Test section
(test/simulated-users, test/agent-evaluations, test/test-suites). Account pages (account-members,
billing, settings) sit under the account, and the staff-only admin section (templates, skills,
voices) under /dashboard/studio/admin. / redirects to /login.
Rendering and Data
Server Components render the static structure of a page; data-dependent sections sit behind the smallest
useful Suspense boundary so the page shell streams immediately.
Application data goes through TanStack Query, including the data of the first render:
-
HydratedQuery(lib/hydrated-query.tsx) creates a request-scoped query client on the server and prefetches the queries a section needs. -
It passes the dehydrated cache through a
HydrationBoundary. -
The client component calls
useQuerywith the same query options and reads the hydrated cache; later refetches carry the session’s access token. -
Mutations update known cache entries with
setQueryDatawhere useful and invalidate the affected query keys. Query keys are feature-scoped and exported by the feature, so every consumer invalidates the same key.
Two consequences to keep in mind:
-
HydrationBoundarywrites server data for queries that already exist in the browser cache in an effect, after the first render. A form that takes its defaults on first render can therefore start from an older cached copy: write the mutation response into the cache, and let an untouched form adopt newer data. -
Form fields (
components/form) are lazy components. A form inside an animated container or a dialog needs its own localSuspenseboundary, or the first, cold render suspends the whole dialog.
Requests go through createPlatformApiClient(accessToken) (lib/api-client.ts, Axios): the bearer token,
JSON or multipart bodies, one token refresh and retry on 401, and a readable message for an empty 403.
Response payloads are parsed with Zod schemas next to the feature that uses them.
Authentication in the UI
auth.ts configures NextAuth with Keycloak; the details are in
Studio Sign-In. Pages call requireSession() or
requireAccessToken() on the server, and the scope guards in lib/routing/scope-guards.ts load the
account or project context and answer "not found" when the caller may not see the page. Client components
read their capabilities — view, edit, manage, members, billing — from useProjectPermissions() and the
access state of lib/authorization/access-control.ts. While the role is still loading, editing is enabled
optimistically; the backend enforces the real rule. Staff accounts (isAdminEmail) additionally see the
Admin section.
Two Interfaces
The Studio and the AI Assistant show the same objects and may present them differently. They share what
is genuinely common — query options, mutations, form logic, types — and keep separate views where forcing
one component to serve both would hide real differences: the assistant’s views live in
app/dashboard/aiassistant/_components/*-view.tsx, and many reuse the Studio’s _forms.
The assistant view is driven by the backend: context/websocket-context.tsx keeps one Socket.IO
connection per tab, reconnects with backoff, replays the current context after a reconnect, and dispatches
render.* events to the matching view (AI Assistant). The switch in the Studio header moves
between the interfaces and keeps the open account, project or object.
Route Handlers
| Route | Purpose |
|---|---|
|
NextAuth. |
|
Starts a sign-in straight at a company’s identity provider (Studio Sign-In). |
|
The authorization server of the hosted MCP server (OAuth Authorization Server). |
|
Streams a voice preview from the backend, adding the session’s token. |
|
Proxies of the backend’s visual endpoints for requests made to the Studio’s own origin. In deployments
the ingress routes |
|
Creates a Zendesk ticket from the contact form. |
Conventions
The rules for Studio code — which components and patterns to reuse, GuardedLink, list state, picker
search, page titles, and the formatting, lint and type checks — are collected in
Code Conventions. Build and run instructions are in
Development Setup.