The web application

The React dashboard's structure and data flow, from route families to the project shell's live status.

What it is

The web application in apps/web is a React and Vite single-page app. It is the dashboard, the project workbench and the admin console. In development it runs on the Vite dev server and proxies API requests to the backend; in production the backend serves the built bundle on the manager port.

The app is organised by route and by surface. Top-level pages cover the dashboard, projects, templates, profile and updates. A project shell wraps the per-project pages and provides the sidebar, status polling and the project context. Admin pages sit behind an admin route guard.

Why the structure matters

A management UI over many live processes has to be honest about state. The frontend's structure reflects that: a shared shell owns project status and lifecycle actions, each page loads its own data, and a failed request produces an error state rather than a plausible substitute. The division also keeps the bundle navigable: a user can deep-link to a project's SQL editor or a specific database tab.

Application structure

Web app data flow The React single-page app routes to page components. Page components call the typed API client, which attaches the session cookie and CSRF token, normalizes errors and updates local state. Live logs use EventSource and realtime inspection uses a WebSocket. From a route to a request Routerreact-router routesguarded pages Page componentproject pages, adminloading + empty states API clientcookie + CSRF headererror normalization Express API/api routesJSON responses Local stateZustand auth storepage state + contexts Live channelsEventSource for logsWebSocket for realtime Reading path The page is the unit of composition; the API client is the only transport; live channels feed the pages that show logs and realtime frames. A failed request renders an honest error or unknown state instead of a fabricated healthy state.

Diagram: the React app renders top-level pages and a project shell; the shell provides project context and status polling to the per-project pages; all data comes from the manager API over fetch and a WebSocket.

React surfaceShared contextBackend call

What it contains

The dashboard page loads statistics, activity, system health and front-door status. The projects page is the register and the embedded workbench card. The project shell renders a section sidebar and an outlet for overview, database, editor, migrations, backups, logs, SQL, auth, storage, advisors, realtime, functions, observability, API, integrations and settings. Admin pages cover manager users, system and proxy settings and auth configuration. Shared components cover the SQL editor, the table grid, modals, toasts, skeletons and an error boundary.

Route families

Subsystem component map The browser app talks to one Express backend. The backend owns the manager API, the front door, the subdomain proxy, the shared pooler and the SQLite manager database, and drives the Supabase CLI for each project stack. Browser React SPA (apps/web) manager UI + Studio links Express backend packages/supabase-wrapper manager API + WebSocket front door :6000 / proxy :9000 shared PgBouncer :6432 Manager SQLite (sql.js) projects / users / settings port allocations / vault metadata Supabase CLI start / stop / db driven per project no Docker API client Docker stack Kong / Postgres / Auth Storage / Realtime Functions / Studio one per project /api /ws state spawn containers Responsibilities Web app: pages, forms, live streams. Backend: auth, tenancy, lifecycle, proxying, introspection. Manager database: durable product state. CLI: the stack itself. Docker: containers and volumes. The front door and subdomain proxy share one access evaluator; neither forwards before a decision. backend state

Diagram: the web application sits above the manager API; its route families map to the dashboard, project workbench, templates, admin and profile surfaces, and every data path ends at the backend.

Route familyShared serviceExternal call

Data flow

Pages call the manager API with a session cookie. The cookie is httpOnly, so the app does not read the token; it relies on same-origin requests and a CSRF token for state changes. A global WebSocket delivers project status updates. During a lifecycle action the shell defers to its own poll rather than accepting a transient WebSocket state, because Docker can report a contradictory status mid-teardown.

Theme preference is stored in the browser and applied to the document. Route state carries small pieces of context such as a return path. Larger state lives on the server, which keeps the app reload-safe and avoids duplicating the backend's truth.

Failure and recovery

An error boundary catches a rendering failure and offers a recovery path. Individual page loads handle their own errors, so one failing panel does not blank the page. A malformed project id renders a not-found panel without a network call. A 401 sends the user back to sign-in with a session-expired notice. A stopped project shows an honest empty state on data pages instead of cached content.

Configuration

<code>VITE_BACKEND_PORT</code>
Development-only proxy target, read when Vite starts.
<code>FRONTEND_URL</code>
The allowed origin for API CORS and the dev proxy.
<code>PORT</code>
The manager port that serves the built bundle in production.
Start the app through the root launcher in development. Vite reads its environment from its working directory, so launching it from the wrong folder silently points the proxy at the wrong port.

Where the boundaries are

The app owns presentation and transient interaction state; the backend owns truth. A page that shows a project's status asks for it rather than inferring it from a cached list. A form that changes a setting sends the change and then re-reads the effective value. This is why a refresh rarely produces a surprise.

The trade-off is more requests. The app mitigates it by loading independent panels in parallel and by using the WebSocket for status pushes, while keeping the authoritative read on the server. When a panel fails, the failure is local to that panel, and the rest of the page remains usable.

Where state lives

Server state stays on the server. The app keeps only the UI state a reload can rebuild: the open tab, the current theme, an unsaved form draft. That keeps two browsers looking at the same project from disagreeing about the truth, which matters when one of them has just stopped a stack.

Where you see the app

Manager dashboard in dark theme with project cards and quick actions.
dashboard
A compact view of every project and the most recent manager activity.