ADR-0148: Portal frontend stack (design system, charts, graphs, forms, API client)
Context
The architecture reference (A4.4) names for the portal Next.js + React + TypeScript, MUI, an API client generated from OpenAPI, SSE or WebSocket, Monaco, React Hook Form + Zod, React Flow, Apache ECharts and OIDC Keycloak. The client decided not to use MUI.
Decision
- Design system: Tailwind CSS + radix primitives (
@radix-ui/*) + webui tokens (portal/src/app/globals.css,portal/tailwind.config.ts), components inportal/src/components. MUI is never added (client decision: one visual system, shared with the navin-manager console). - Forms: React Hook Form + Zod 4 (
portal/src/components/common/field-form.tsx,portal/src/lib/zod.ts); configuration forms are generated from a JSON Schema ofcontracts/v1(schemaFields,portal/src/lib/components.ts). - API client: the typed operation table
portal/src/lib/api/operations.gen.tsis generated fromdocs/api/openapi-v1.jsonbyportal/src/lib/api/generate-operations.mjs(--checkrun byportal/tests/openapi-client.test.ts);callOperationcalls an operation by its operationId. The domain wrappers ofresources.tsstay readable helpers, checked against the specification byportal/tests/api-conformance.test.ts. - Charts: Recharts (MIT, already present and used by FinOps) instead of Apache ECharts. Both are OSI; Recharts avoids a second chart library and covers the time series and bar charts of the portal. ECharts (Apache-2.0) may be added, pinned, when a view needs a chart type Recharts lacks.
- Graphs:
DependencyGraphdraws a layered graph (Kahn algorithm, cycles listed apart) fromGET /v1/lineage/graph, without React Flow. React Flow (MIT) stays reserved to topology and execution views if it becomes needed; the Developer Studio stays code first (Monaco). - Editor: Monaco (
monaco-editor0.52.2, bundled locally, no CDN) for Python, TypeScript, JSON, YAML and Rego. - Authentication: OIDC with Keycloak through
oidc-client-ts(portal/src/lib/auth.ts).
Consequences
No non OSI dependency; no library added by this decision. Any OpenAPI change requires regenerating operations.gen.ts, otherwise the test fails.
Evidence
portal/tests/openapi-client.test.ts, portal/tests/components.test.ts, portal/tests/api-conformance.test.ts, npm run typecheck, npm run lint.