Web console for the Open Sovereign AI Cloud (OSAC) project — a self-service platform for deploying OpenShift clusters, virtual machines, and bare metal hosts. pnpm monorepo with React 19 + PatternFly 6 (frontend) and a Go chi reverse proxy (BFF with OIDC authentication).
console.log — ESLint enforces thisfunc-style: expression is enforcedPrerequisites: Node.js 20+, pnpm 9+, Go 1.23+
pnpm install # Install dependencies
# Start development servers
FULFILLMENT_API_URL=https://... pnpm dev # Go proxy and Vite on :5173
# Build
pnpm build # TypeScript check + Vite build + Go binary
# Test
pnpm test # Vitest unit tests
pnpm e2e:ci # Cypress E2E tests
# Lint and format
pnpm lint # ESLint + Prettier + i18n sync check
pnpm format # Prettier write
# Type generation
pnpm gen-types # Regenerate TS types from protobuf
# Translations
pnpm i18n # Extract t() keys and update libs/i18n/locales/en/translation.json
# Validate PatternFly usage
pnpm check:pf-primitives
# Regenerate API diff manifest (also runs automatically on postinstall)
pnpm gen:api-diff
# Container build
podman build -t osac:latest -f Containerfile .
osac-ui/
├── apps/
│ ├── app-frontend/ # React 19 SPA (Vite + TypeScript + PatternFly 6)
│ └── e2e/ # Cypress end-to-end tests
├── libs/
│ ├── i18n/ # Translation extraction config + generated locale files
│ ├── api-contracts/ # Shared TS types + wire normalizers
│ ├── types/ # Generated types from protobuf (do not edit)
│ └── ui-components/ # Shared PatternFly 6 component library
├── proxy/ # Go chi reverse proxy (OIDC auth + API forwarding)
├── deploy/
│ └── chart/ # Helm chart for Kubernetes deployment
├── docs/
│ ├── specs/ # Feature specs, statecharts, flow definitions
│ └── runbook.md # Development and deployment instructions
├── scripts/ # Helper scripts (PF validation, statechart graphs)
└── pnpm-workspace.yaml # Workspace: apps/*, libs/*
| Package | Purpose |
|---|---|
@osac/app-frontend |
React SPA — Vite, React 19, TanStack Query, react-router-dom 7 |
@osac/e2e |
Cypress E2E tests |
@osac/i18n |
Translation extraction config (i18next.config.ts) + generated locale files |
@osac/api-contracts |
Shared TS types + wire normalizers (single source of truth for API types) |
@osac/types |
Generated protobuf types (do not edit) |
@osac/ui-components |
Shared PatternFly 6 components (consumed at source, no build step) |
sort-importsprefer-arrow-callback + func-style: expression^_ ignore patternfunction keyword for named pure helpers when it improves hoisting and stack tracesfeature-name/SubView.tsx); keep page files focused on composition, data wiring, and layout. Exception: a tiny non-exported helper may stay if the file remains shortstyle=) except for dynamic values that cannot be expressed in CSSdata-testid unless the team standard requires itmemoTreat memoization as an optimization, not a correctness tool:
memo to fix broken behavior — fix purity, state placement, or data flow firstmemo only when justified: the child re-renders often with referentially stable props and its render work is measurably expensivememo does nothing if props are always new (inline objects/arrays/functions) — prefer narrower props, children as JSX, and local statememo/useCallback/useMemomemoThe app uses i18next + react-i18next with English string keys (the English text itself is the key). Translation files are generated by scanning source code for t() calls and <Trans> components.
libs/i18n/locales/en/translation.json is the single source of truth for translations. It is generated by i18next-cli and must not be edited manually. Update it by modifying source strings and running:
pnpm i18n
Commit the updated translation.json alongside the source changes. pnpm lint runs the same check in CI mode (--ci) and fails if the file is out of sync with the source.
Always import useTranslation from @osac/ui-components/hooks/useTranslation — never directly from react-i18next. ESLint enforces this.
import { useTranslation } from '@osac/ui-components/hooks/useTranslation';
const MyComponent = () => {
const { t } = useTranslation();
return <h1>{t('Page title')}</h1>;
};
For pure (non-component) utilities that need translations, accept t: TFunction as a parameter (imported from i18next) and call useTranslation in the component that invokes them:
import type { TFunction } from 'i18next';
export const getLabels = (t: TFunction) => ({
save: t('Save'),
cancel: t('Cancel'),
});
t('This is OK', { someVar }) — hardcoded string key, extractable by the parsert(usingAVariable) — dynamic key, not extractable; the CI i18n check will not catch missing translationsvite-plugin-static-copy copies libs/i18n/locales/ into the Vite build output (dist/locales/). The Go proxy serves these as static files. The i18next HTTP backend loads them at runtime from /locales//.json.
apps/e2e/)AC-1, AC-2, …) in PR text and tie tests to ACsThe Go reverse proxy handles OIDC authentication and forwards API requests to the fulfillment service.
| Env Var | Required | Description |
|---|---|---|
FULFILLMENT_API_URL |
Yes | Upstream API base URL |
PORT |
No | Listen port (default: 8080) |
HOST |
No | Listen host (default: 0.0.0.0) |
BASE_UI_URL |
No | Public base URL of the UI — used to compute the /callback redirect URI; derived from the SPA’s redirect_base query parameter if unset |
OIDC_CLIENT_ID |
No | OIDC client ID (default: osac-ui) |
OIDC_TLS_CA_FILE |
No | Custom CA bundle for the OIDC IdP |
OIDC_TLS_INSECURE |
No | Skip TLS verification for the OIDC IdP (dev only) |
FULFILLMENT_TLS_CA_FILE |
No | Custom CA bundle for the fulfillment service |
FULFILLMENT_TLS_INSECURE |
No | Skip TLS verification for the fulfillment service (dev only) |
Proxied paths: /api/fulfillment/v1/*, /api/events/v1/*, /api/osac/public/v1/*
GitHub Actions (.github/workflows/):
podman build on PRs (no push)ghcr.io/ on main and v* tagsv* tags| Area | Location |
|---|---|
| Feature specs and acceptance criteria | docs/specs/ |
| Statechart definitions (XState) | docs/specs/statecharts/ |
| Development and deployment runbook | docs/runbook.md |