Next.js UI for managing Agentic Sessions and Projects. In local development it proxies API calls to the backend and forwards incoming auth/context headers; it does not spoof identities.
- Node.js 20+ and npm
- Go 1.24+ (to run the backend locally)
- oc/kubectl configured to your OpenShift/Kubernetes cluster
Run the backend locally while targeting your cluster.
- Install CRDs to your cluster
oc apply -f ../manifests/crd.yaml
oc apply -f ../manifests/projectsettings-crd.yaml- Create/label a project namespace (example: my-project)
oc new-project my-project || oc project my-project
oc label namespace my-project ambient-code.io/managed=true --overwrite
oc annotate namespace my-project \
ambient-code.io/display-name="My Project" --overwrite- Start the backend (defaults to port 8080)
cd ../backend
export KUBECONFIG="$HOME/.kube/config" # or your kubeconfig path
go run .
# Health: curl http://localhost:8080/healthRecommended: Use integrated local development environment:
# From repository root - single command setup
make kind-up
# Access: http://localhost:8080Alternative: Standalone frontend development:
# From this directory, install and run:
npm ci
export BACKEND_URL=http://localhost:8080/api # Adjust for your backend
npm run dev
# Open http://localhost:3000cd components/frontend
# Install dependencies
npm install
# Development server
npm run dev
# Build
npm run build
# Production server
npm start
# Linting
npm run lintPre-commit checklist:
- Run
npm run build- must pass with 0 errors, 0 warnings - See
DESIGN_GUIDELINES.mdfor comprehensive frontend development standards
Next.js API routes forward incoming headers to the backend. They do not auto-inject user identity. In development, you can optionally provide values via environment or oc:
- Forwarded when present on the request:
X-Forwarded-User,X-Forwarded-Email,X-Forwarded-Preferred-UsernameX-Forwarded-GroupsX-OpenShift-ProjectAuthorization: Bearer <token>(forwarded asX-Forwarded-Access-Token)
- Optional dev helpers:
OC_USER,OC_EMAIL,OC_TOKENENABLE_OC_WHOAMI=1to let the server calloc whoami/oc whoami -t
In production, put an OAuth/ingress proxy in front of the app to set these headers.
BACKEND_URL(default:http://localhost:8080/api)- Used by server-side API routes to reach the backend.
FEEDBACK_URL(optional)- URL for the feedback link in the masthead. If not set, the link will not appear.
- Optional dev helpers:
OC_USER,OC_EMAIL,OC_TOKEN,ENABLE_OC_WHOAMI=1
You can also put these in a .env.local file in this folder:
BACKEND_URL=http://localhost:8080/api
# Optional: URL for feedback link in masthead
# FEEDBACK_URL=https://forms.example.com/feedback
# Optional dev helpers
# OC_USER=your.name
# OC_EMAIL=your.name@example.com
# OC_TOKEN=...
# ENABLE_OC_WHOAMI=1
Backend directly (requires headers):
curl -i http://localhost:8080/api/projects/my-project/agentic-sessions \
-H "X-OpenShift-Project: my-project" \
-H "X-Forwarded-User: dev" \
-H "X-Forwarded-Groups: ambient-project:my-project:admin"Through the frontend route (forwards headers to backend):
curl -i http://localhost:3000/api/projects/my-project/agentic-sessions \
-H "X-OpenShift-Project: my-project"- 400 “Project is required …”
- Use path
/api/projects/{project}/…or includeX-OpenShift-Project.
- Use path
- 403 “Project is not managed by Ambient”
- Ensure namespace is labeled
ambient-code.io/managed=true.
- Ensure namespace is labeled
- Missing auth header
- In dev, provide
Authorization: Bearer <token>(or useOC_TOKEN/ENABLE_OC_WHOAMI).
- In dev, provide
- Do not spoof identities. Forward real headers from your OAuth/ingress proxy.
- Provide a project selection mechanism and forward it as
X-OpenShift-Project(or use project path in API URLs).
GitHubConnection.tsx: GitHub App installation and fork management- OAuth flow for per-user GitHub App installations
- Fork selection with visual interface
- Automatic fork creation capability
- Real-time connection status
RepoBrowser.tsx: Full repository navigation- File tree browsing with breadcrumb navigation
- File content display with syntax awareness
- Branch/ref switching support
- Size formatting and file type detection
SessionsDashboard.tsx: Live session management- Real-time WebSocket connections for session updates
- Grouped PR display (spec repo + submodule PRs)
- Live message streaming with partial reassembly
- Visual status indicators for all session states
- Multi-runner support (Claude, OpenAI, local execution)
rfe-workflows.tsx: Complete RFE workflow interface- Workspace creation and management
- Tabbed interface for different views
- RBAC integration with access level display
- Session creation and monitoring
- Type-safe backend communication via
apiClient - WebSocket support for real-time session updates
- Comprehensive error handling with user-friendly messages
- RBAC enforcement with access level checking
- Live Session Monitoring: WebSocket connections with automatic reconnection
- Multi-repo PR Management: Handle spec repo and submodule PRs separately
- GitHub App Integration: Streamlined per-user installation flow
- Repository Browsing: Full file tree navigation with content preview
- Runner Support: Claude Code, OpenAI, and local execution runners
- Access Control: Role-based permissions (view/edit/admin)
- Modern Interface: Tailwind CSS with shadcn/ui components
- Responsive Design: Mobile-friendly responsive layout
- Accessibility: Full keyboard navigation and screen reader support
- Real-time Updates: Live status indicators and message streaming
- Error Handling: Comprehensive error states with recovery actions
The frontend provides a complete user interface for the RFE (Request For Enhancement) workflow system, integrating GitHub repositories, AI runners, and real-time collaboration features.
466 tests across 26 files. Primary coverage metric (~74%).
# Run all tests
npx vitest run
# With coverage report
npx vitest run --coverage
open coverage/index.html
# Watch mode
npx vitest
# Single file
npx vitest run src/utils/__tests__/export-chat.test.tsConfig: vitest.config.ts — uses jsdom, Istanbul coverage, @/ path alias.
Place tests in __tests__/ next to the source:
src/
components/
chat/
ChatInputBox.tsx
__tests__/
ChatInputBox.test.tsx ← test file here
hooks/
use-session-queue.ts
__tests__/
use-session-queue.test.ts ← test file here
Pure function test:
import { describe, it, expect } from 'vitest';
import { convertEventsToMarkdown } from '../export-chat';
describe('convertEventsToMarkdown', () => {
it('renders text messages', () => {
const events = [
{ type: 'TEXT_MESSAGE_START', role: 'user' },
{ type: 'TEXT_MESSAGE_CONTENT', delta: 'Hello' },
{ type: 'TEXT_MESSAGE_END' },
];
const md = convertEventsToMarkdown(makeExport(events), makeSession());
expect(md).toContain('Hello');
});
});Component test:
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { MyComponent } from '../MyComponent';
it('handles click', () => {
const onClick = vi.fn();
render(<MyComponent onClick={onClick} />);
fireEvent.click(screen.getByText('Click me'));
expect(onClick).toHaveBeenCalled();
});Hook test:
import { renderHook, act } from '@testing-library/react';
it('updates state', () => {
const { result } = renderHook(() => useMyHook());
act(() => result.current.setValue('new'));
expect(result.current.value).toBe('new');
});Mocking:
// Module mock
vi.mock('@/services/api/sessions', () => ({
createSession: vi.fn().mockResolvedValue({ name: 'test' }),
}));
// Function stub
const onSend = vi.fn();
// DOM API spy
vi.spyOn(document, 'createElement').mockReturnValue(mockEl);
// React Query wrapper
const wrapper = ({ children }) => (
<QueryClientProvider client={new QueryClient()}>
{children}
</QueryClientProvider>
);
const { result } = renderHook(() => useMyQuery(), { wrapper });