This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Trustify UI is a React-based web application for software supply chain security (SBOMs, advisories, vulnerabilities). It uses a monorepo structure with npm workspaces and connects to the Trustify backend API.
This monorepo uses 4 npm workspaces:
-
common/- Shared ESM module for environment config and branding- Exports
TrustificationEnvType,encodeEnv,decodeEnv, branding assets - Built with Rollup to both ESM (.mjs) and CommonJS (.cjs)
- Used by both client and server
- Exports
-
client/- React frontend application- Tech: React 19, TypeScript, Rsbuild (Rspack), PatternFly 6
- Dev server: port 3000 with proxy to backend
- Build output:
client/dist/withindex.html.ejstemplate
-
server/- Express.js production server- Serves static client assets
- Proxies
/api,/auth,/openapito backend - Injects environment variables at runtime via EJS templating
-
e2e/- Playwright end-to-end teststests/api/- API integration teststests/ui/- UI tests with Page Object Modeltests/ui/features/- BDD tests with Gherkin + playwright-bdd
Defined in common/src/environment.ts:
TRUSTIFY_API_URL- Backend API URL (default:http://localhost:8080)AUTH_REQUIRED- Enable/disable authentication (default:"true")OIDC_SERVER_URL- Keycloak/OIDC server URLOIDC_CLIENT_ID- OIDC client ID (default:"frontend")OIDC_SCOPE- OIDC scope (default:"openid")
How env vars work:
- Production: Server injects env as base64 JSON into
index.html.ejs, client decodes fromwindow._env - Development: Rsbuild injects env directly into HTML template
# Install dependencies
npm ci
# Start development server (builds common, runs client on :3000)
npm run start:dev
# Start specific workspaces
npm run start:dev:client # Client only
npm run start:dev:common # Common in watch mode# Build all workspaces
npm run build
# Regenerate OpenAPI client from spec
npm run generate# Lint and format check
npm run check
# Auto-fix lint
npm run check:write
# Auto-fix formatting
npm run format:fix
# Type checking is integrated into builds# Run unit tests (Jest)
npm test
# Run a single test file
npm test -- path/to/test.test.ts# Run e2e tests (Playwright)
npm run e2e:test
# Run a single e2e test file
npm run e2e:test -- path/to/test.test.tsThe e2e tests use custom Playwright assertions for better readability and maintainability. Always prefer these custom assertions over manual DOM queries or counts.
Custom assertions are located in: e2e/tests/ui/assertions/
Import from: e2e/tests/ui/assertions (exports typed expect with custom
matchers)
Client app follows this component hierarchy:
OidcProvider (auth)
└─ QueryClientProvider (TanStack Query)
└─ RouterProvider (React Router 7)
└─ DefaultLayout
├─ HeaderApp (masthead)
├─ SidebarApp (nav menu)
└─ Outlet (page content)
client/src/app/
├── client/ # Auto-generated OpenAPI client (hey-api)
├── queries/ # TanStack Query hooks per domain (advisories.ts, sboms.ts, etc.)
├── hooks/ # Custom React hooks
│ ├── table-controls/ # 36 hooks for table state management
│ ├── domain-controls/# Domain-specific data hooks
│ └── tab-controls/ # Tab state management
├── pages/ # Route-specific page components
│ └── [page-name]/ # Each page: component + context + table + toolbar
├── components/ # Reusable React components
├── layout/ # App shell (header, sidebar, default layout)
├── utils/ # Utility functions
├── Routes.tsx # Route definitions
└── env.ts # Environment config decoder
Every page follows this consistent structure:
pages/[page-name]/
├── [page-name].tsx # Main page component
└── components/ # Page-specific components
-
Server state: TanStack Query for all API data
- Custom hooks in
queries/(e.g.,useFetchAdvisories) - Automatic cache invalidation on mutations
- Custom hooks in
-
Table UI state: Sophisticated table controls system
- State persists to URL params, localStorage, sessionStorage, or React state
- Use
useTableControlState()+useTableControlProps()pattern - Enables shareable URLs with filters/sort/pagination state
-
Page-level state: React Context providers (per-page contexts)
-
Global state: React Context
NotificationsProvider- Toast notificationsPageDrawerContext- Drawer state
-
Generated client:
@hey-api/openapi-tsgenerates TypeScript client fromopenapi/trustd.yaml- Output:
client/src/app/client/(types, SDK methods) - Regenerate:
npm run generate(runs automatically onnpm ci)
- Output:
-
Client initialization:
axios-config/apiInit.ts- Adds Bearer token from OIDC session to all requests
- Auto-retries on 401 with silent token refresh (max 2 retries)
-
Custom REST helpers:
api/rest.tsfor special cases (file uploads, endpoints not in OpenAPI)
- React Router 7 with code-splitting
- Routes defined in
client/src/app/Routes.tsx - Lazy-loaded page components
- Type-safe params via
useRouteParams()hook
- OIDC via
react-oidc-context(configured inOidcProvider.tsx) - If not authenticated → redirect to OIDC server with state preservation
- On callback → extract relative path from state, navigate back
- Token stored in sessionStorage
- Axios interceptor adds Bearer token to all API requests
- Automatic silent token renewal
- On 401 → attempt silent refresh, retry request (max 2 times)
- Create folder in
client/src/app/pages/[page-name]/ - Create main page component (
[page-name].tsx) - Add route to
client/src/app/Routes.tsx - Update
client/src/app/Constants.tsfor path constants
- Update OpenAPI spec in
client/openapi/trustd.yaml(or get from backend) - Run
npm run generateto regenerate client - Create TanStack Query hooks in
client/src/app/queries/[domain].ts - Use hooks in page contexts
Use the table controls pattern (see pages/advisory-list/advisory-context.tsx):
const tableControlState = useTableControlState({
tableName: "advisory",
persistTo: "urlParams", // State persists in URL
columnNames: {identifier: "ID", title: "Title"},
filterCategories: [...],
sortableColumns: ["identifier", "modified"],
isPaginationEnabled: true,
});
const tableControls = useTableControlProps({
...tableControlState,
currentPageItems: advisories,
totalItemCount,
});-
Linter: Biome (replaces ESLint/Prettier)
- Config:
biome.json - Double quotes for strings
- Space indentation
- Import organization disabled (manual control)
- Config:
-
TypeScript: Strict mode enabled
- Path alias:
@app/*→src/app/* - Import Order: Group imports alphabetically and follow the order below,
with each block separated by a blank line:
- React/Router block: Dependencies from
react,react-dom,react-router,react-router-dom,react-oidc-context,react-hook-form, etc. - Package dependencies block: Any dependency declared in
package.json(e.g.,axios,dayjs,yup,lodash, etc.) - PatternFly block: Any
@patternfly/*dependency - App imports block: Any
@app/*dependency - Relative imports block: Local relative imports (
./,../, etc.)
- React/Router block: Dependencies from
- Path alias:
Example:
import React from "react";
import {Link, useNavigate} from "react-router-dom";
import type {AxiosError} from "axios";
import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport";
import {
Breadcrumb,
Tab,
} from "@patternfly/react-core";
import {PathParam, Paths, useRouteParams} from "@app/Routes";
import {LoadingWrapper} from "@app/components/LoadingWrapper";
import {Overview} from "./overview";- CSS Modules: Enabled for component-scoped styles
Development:
- Rsbuild proxies
/api→TRUSTIFY_API_URL(default:http://localhost:8080) - Rsbuild proxies
/auth→OIDC_SERVER_URL - Start local backend:
cargo run --bin trustd(in trustify repo)
Production:
- Express server proxies requests
- Environment injected at server startup
- Always read existing files before modifying them
- The OpenAPI client (
client/src/app/client/) is auto-generated - don't edit manually - Table controls provide URL persistence - users can share filtered/sorted views
- PatternFly 6 is the design system - use PF components for consistency
- Authentication is optional (controlled by
AUTH_REQUIREDenv var) - E2E tests: Always use custom assertions from
e2e/tests/ui/assertions/instead of manual DOM queries for better maintainability and type safety