# 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 ```tree 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 ```bash # 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:** ```text Component → Hook (src/hooks/UseX.ts) → Resource (src/resources/XResource.ts) → Axios → Quay API ``` Example: ```typescript // 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 `useSuspenseQuery` directly in components - Wrap component in `` boundary - No manual `isLoading` checks ### 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: ```typescript import { lazy } from 'react'; const RepositoriesList = lazy(() => import('./RepositoriesList')); // In Routes } /> ``` ### State Management **Server State:** React Query (`useSuspenseQuery`, `useMutation`) - Repositories, organizations, users, tags - Automatic caching, refetching, invalidation **UI State:** React Context - `SidebarContext`: Sidebar open/closed - `AlertContext`: Toast notifications - `AuthContext`: Current user **Legacy:** Recoil atoms in `src/atoms/` (avoid for new code) ## Critical Rules 1. **No Early Returns with Loading Spinners** ```typescript // ❌ WRONG - Causes layout shift if (isLoading) return ; // ✅ CORRECT - Use Suspense {/* Uses useSuspenseQuery */} ``` 2. **PatternFly Components** - Use PatternFly 5 components (not MUI, not custom) - Alert, Card, Button, Table, Modal, Toolbar, etc. - Follow PatternFly design patterns 3. **TypeScript Standards** - `React.FC` for components - Explicit return types on functions - Type imports: `import type { User } from 'src/types/user'` - No `any` types 4. **React Query Patterns** - New code: `useSuspenseQuery` (no loading states) - Legacy code: `useQuery` with `isLoading` checks - Mutations: `useMutation` with cache invalidation - Query keys: `['resource', ...params]` ## Quick Reference ### Common Imports ```typescript 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` with TypeScript - [ ] Lazy load if heavy: `React.lazy(() => import())` - [ ] Wrap in `` for loading states - [ ] Use `useSuspenseQuery` for data fetching - [ ] Use `useCallback` for event handlers passed to children - [ ] Use `useMemo` for 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 `:8080` with seeded test data - Base URL configurable in `cypress.config.ts` ### Environment Variables - `MOCK_API=true`: Use mocked API - `REACT_QUAY_APP_API_URL`: Override backend URL - `NODE_ENV`: development/production (set by webpack) ### Performance Tips - Lazy load routes and heavy components - Use `useMemo` for filter/sort/map operations on large arrays - Use `useCallback` for event handlers passed as props - Debounce search inputs (300-500ms) - Clean up effects to prevent memory leaks