Web App
The Workloom web app (apps/web) is a React SPA built with Vite that provides
a management interface for the registry. It consumes auto-generated React Query
hooks from @workloom/workloom-api and authenticates via OIDC.
Quick Start
# Start the API server (port 4050)
pnpm api:start
# Start the web dev server (port 3050, proxies API)
pnpm web:start
The Vite dev server proxies /v1/* requests to localhost:4050, avoiding CORS
issues during development.
Technology Stack
| Layer | Technology |
|---|---|
| Framework | React 19 with TypeScript |
| Build | Vite 6 with @vitejs/plugin-react |
| Routing | React Router v6 |
| Data fetching | @tanstack/react-query v5 (via @workloom/react-query) |
| API client | Kubb-generated hooks (via @workloom/workloom-api) |
| Auth | Custom AuthProvider + @supabase/supabase-js |
| Transport | @workloom/fetch-client (native fetch wrapper) |
Authentication Flow
API-mediated Supabase OIDC — the web app signs the user in to Supabase, then the
API brokers the OAuth exchange and issues a wl_ session token:
1. User signs in to Supabase (email/password) via supabase-js
2. GET /v1/auth/oidc/authorize -> Supabase authorize URL (PKCE)
3. Supabase consent at /oauth/consent (approve)
4. Supabase redirects to /callback?code=...
5. POST /v1/auth/oidc/callback -> API verifies ES256 id_token, issues wl_ token
6. Token stored in localStorage; AuthProvider syncs it via setAuthToken()
7. All API requests carry Authorization: Bearer wl_<token>
Key Auth Hooks
| Hook | Purpose |
|---|---|
useAuth | Auth context: token, isAuthenticated, login/logout |
useAccessToken | Current wl_ token + { accessToken, headers } |
useRoles | Fetches role from /v1/auth/me (React Query, 5-min stale) |
Token Injection
The WorkloomQueryProvider subscribes to OIDC auth state and calls
setAuthToken() from @workloom/fetch-client whenever the token changes. This
ensures all generated hook calls automatically include the Bearer token without
each component managing auth manually.
Pages and Routing
| Route | Page | Description |
|---|---|---|
/dashboard | Dashboard | Org health, activity, approvals |
/registry | Registry | Package browser |
/registry/:name | PackageDetail | Package versions, assets, governance |
/workspaces | Workspaces | Workspace listing |
/workspaces/:id | WorkspaceDetail | Bindings, packages, drift, audit |
/editor | Editor | Asset editing interface |
/telemetry | Telemetry | Operational dashboards |
/admin | Admin | SSO, teams, RBAC, audit logs |
Composition Hooks Pattern
The web app uses composition hooks to transform raw API data into view models. Each hook calls multiple generated React Query hooks and composes the responses into a typed model shape.
// Example: useDashboardModel composes multiple API queries
function useDashboardModel(options: { headers: Record<string, string>; enabled: boolean }) {
const packages = useListPackages({ client: { headers }, query: { enabled } });
const revisions = useListWorkspaceRevisions({ client: { headers }, query: { enabled } });
const telemetry = useTelemetryDashboard({ client: { headers }, query: { enabled } });
const approvals = useListApprovals({ client: { headers }, query: { enabled } });
const status = getCombinedStatus([packages, revisions, telemetry, approvals]);
return {
status,
data: status === "success" ? buildDashboardModel(packages.data, revisions.data, ...) : null,
};
}
Implemented Composition Hooks
| Hook | Composes | Produces |
|---|---|---|
useDashboardModel | packages, revisions, telemetry, approvals | DashboardModel |
useRegistryModel | packages | RegistryModel |
useWorkspaceListModel | workspace revisions | WorkspaceListModel |
Design Rationale
Composition happens client-side rather than via BFF endpoints because:
- The logic is pure view-model transformation (no I/O beyond what hooks provide).
- Avoids a second API surface to maintain.
- Scales naturally — adding a new page means adding a hook, not a new endpoint.
- Loading and error states are handled uniformly via
getCombinedStatus.
Production Build
# Build static assets
pnpm --filter @workloom/web build
# Output: apps/web/dist/
The production build outputs static assets that can be served by the API server
(via Hono's serveStatic middleware) or deployed to a CDN.