to help guide dev and coderabbit reviews. Claude users can symlink the file or mention the file to have it loaded Signed-off-by: Brady Pratt <bpratt@redhat.com>
7.7 KiB
AGENTS.md
AI-optimized guide for working with Quay's React frontend.
Project Overview
Quay container registry React UI - replaces legacy AngularJS interface. Runs as standalone SPA or OpenShift Console plugin.
Stack: React 18, TypeScript, PatternFly 5, React Query v4, React Router v6, Axios
Legacy Migration: Migrating from Recoil → React Query (server state) + Context API (UI state). Avoid Recoil for new code.
Backend: Flask API at http://localhost:8080, proxied via webpack-dev-server
API Routes: /api/v1/*, /csrf_token, /config, /oauth (NOT /api/form/route format)
Directory Structure
web/
├── src/
│ ├── routes/ # Top-level page components (React Router v6)
│ │ ├── StandaloneMain.tsx # Main layout + routing
│ │ ├── PluginMain.tsx # OpenShift Console plugin entry
│ │ ├── NavigationPath.tsx # Route path constants
│ │ ├── OrganizationsList/
│ │ ├── RepositoriesList/
│ │ ├── RepositoryDetails/
│ │ └── ...
│ ├── components/ # Reusable UI components
│ │ ├── header/ # QuayHeader
│ │ ├── sidebar/ # QuaySidebar
│ │ ├── footer/ # QuayFooter
│ │ ├── modals/ # Reusable modals
│ │ ├── toolbar/ # Table toolbars
│ │ └── errors/ # ErrorBoundary, error pages
│ ├── hooks/ # Custom React hooks (UseRepositories.ts, UseOrganizations.ts)
│ ├── resources/ # API client layer (Axios HTTP calls)
│ │ ├── RepositoryResource.ts
│ │ ├── OrganizationResource.ts
│ │ └── ErrorHandling.ts
│ ├── contexts/ # React Context providers (SidebarContext, AlertContext, AuthContext)
│ ├── atoms/ # Legacy Recoil atoms (avoid for new code)
│ ├── libs/ # Utilities
│ │ ├── axios.ts # Configured Axios instance (CSRF tokens)
│ │ └── utils.ts # Common utilities
│ └── assets/ # Static assets
├── cypress/
│ ├── e2e/ # Integration tests
│ └── fixtures/ # Test data
├── webpack.dev.js # Dev server + proxy config
├── webpack.prod.js # Production build
└── webpack.plugin.js # OpenShift Console plugin build
Development Commands
# Local Development
npm install # Install dependencies
npm start # Dev server on http://localhost:9000 (hot-reload)
MOCK_API=true npm start # Mock API (no backend required)
npm run format # Prettier formatting
# Testing
npm test # Unit tests (watch mode)
npm run test:integration # Cypress e2e tests (requires app on :9000)
npm run start:integration # Serve production build for testing
# Building
npm run build # Production build → dist/
npm run build-plugin # OpenShift Console plugin build
npm run start-plugin # Plugin dev server
# Database Seeding (for integration tests)
npm run quay:dump # Dump current DB state
npm run quay:seed # Seed test DB + storage
# Single Test
npx cypress run --spec "cypress/e2e/test-name.cy.ts"
npm test -- --testPathPattern=ComponentName
Quay-Specific Patterns
Data Flow
Standard Pattern:
Component → Hook (src/hooks/UseX.ts) → Resource (src/resources/XResource.ts) → Axios → Quay API
Example:
// Component uses hook
import { useRepositories } from 'src/hooks/UseRepositories';
// Hook uses React Query + Resource
const { data } = useQuery(['repos'], () => RepositoryResource.getRepositories());
// Resource makes HTTP call
export const RepositoryResource = {
getRepositories: () => axios.get('/api/v1/repositories')
};
Modern Pattern (New Code):
- Use
useSuspenseQuerydirectly in components - Wrap component in
<SuspenseLoader>boundary - No manual
isLoadingchecks
Backend Integration
API Client: src/libs/axios.ts
- Configured Axios instance with CSRF token interceptor
- Auth interceptors for cookie/token handling
- Error handling middleware
Proxy Routes (webpack.dev.js):
/api/v1/*→ Backend (e.g.,http://localhost:8080)/csrf_token→ Backend/config→ Backend/oauth→ Backend
Routing
React Router v6 in src/routes/StandaloneMain.tsx:
- Nested routes with lazy loading
- Route paths in
NavigationPath.tsx - Breadcrumbs via
use-react-router-breadcrumbs
Example:
import { lazy } from 'react';
const RepositoriesList = lazy(() => import('./RepositoriesList'));
// In Routes
<Route path="/repository" element={<SuspenseLoader><RepositoriesList /></SuspenseLoader>} />
State Management
Server State: React Query (useSuspenseQuery, useMutation)
- Repositories, organizations, users, tags
- Automatic caching, refetching, invalidation
UI State: React Context
SidebarContext: Sidebar open/closedAlertContext: Toast notificationsAuthContext: Current user
Legacy: Recoil atoms in src/atoms/ (avoid for new code)
Critical Rules
-
No Early Returns with Loading Spinners
// ❌ WRONG - Causes layout shift if (isLoading) return <Spinner />; // ✅ CORRECT - Use Suspense <SuspenseLoader> <Component /> {/* Uses useSuspenseQuery */} </SuspenseLoader> -
PatternFly Components
- Use PatternFly 5 components (not MUI, not custom)
- Alert, Card, Button, Table, Modal, Toolbar, etc.
- Follow PatternFly design patterns
-
TypeScript Standards
React.FC<Props>for components- Explicit return types on functions
- Type imports:
import type { User } from 'src/types/user' - No
anytypes
-
React Query Patterns
- New code:
useSuspenseQuery(no loading states) - Legacy code:
useQuerywithisLoadingchecks - Mutations:
useMutationwith cache invalidation - Query keys:
['resource', ...params]
- New code:
Quick Reference
Common Imports
import React, { useState, useCallback, useMemo } from 'react';
import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import {
Card, CardBody, Button, Alert, Toolbar, ToolbarContent,
Table, Thead, Tbody, Tr, Th, Td
} from '@patternfly/react-core';
import { SuspenseLoader } from 'src/components/SuspenseLoader';
import type { Repository } from 'src/types/Repository';
Component Checklist
- Use
React.FC<Props>with TypeScript - Lazy load if heavy:
React.lazy(() => import()) - Wrap in
<SuspenseLoader>for loading states - Use
useSuspenseQueryfor data fetching - Use
useCallbackfor event handlers passed to children - Use
useMemofor expensive computations - Default export at bottom
Testing
Unit Tests: Jest + React Testing Library
- Co-located with source files
- Mock API calls with
axios-mock-adapter
Integration Tests: Cypress e2e
- Located in
cypress/e2e/ - Requires running app on
:9000 - Backend at
:8080with seeded test data - Base URL configurable in
cypress.config.ts
Environment Variables
MOCK_API=true: Use mocked APIREACT_QUAY_APP_API_URL: Override backend URLNODE_ENV: development/production (set by webpack)
Performance Tips
- Lazy load routes and heavy components
- Use
useMemofor filter/sort/map operations on large arrays - Use
useCallbackfor event handlers passed as props - Debounce search inputs (300-500ms)
- Clean up effects to prevent memory leaks