# Frontend Subagent Guide This guide covers frontend-focused subagents: - **frontend-specialist**: UI components, Neo-Brutalism, Core Web Vitals, accessibility - **uiux-designer**: UI/UX decisions, component design, user experience ## Quick Reference | Aspect | frontend-specialist | uiux-designer | | ----------------- | ---------------------------------------------- | -------------------------------------- | | **Primary Use** | React components, performance, accessibility | Design decisions, user flows | | **Key Files** | `src/components/`, `src/features/` | Design specs, mockups | | **Key ADRs** | ADR-012 (Design System), ADR-044 (Feature Org) | ADR-012 (Design System) | | **Design System** | Neo-Brutalism (bold borders, high contrast) | Same | | **State Mgmt** | TanStack Query (server), Zustand (client) | N/A | | **Delegate To** | `coder` (backend), `tester` (test coverage) | `frontend-specialist` (implementation) | ## The frontend-specialist Subagent ### When to Use Use the **frontend-specialist** subagent when you need to: - Build new React components - Fix CSS/styling issues - Improve Core Web Vitals performance - Implement accessibility features - Debug React rendering issues - Optimize bundle size ### What frontend-specialist Knows The frontend-specialist subagent understands: - React 18+ patterns and hooks - TanStack Query for server state - Zustand for client state - Tailwind CSS with custom design tokens - Neo-Brutalism design system - Accessibility standards (WCAG) - Performance optimization ## Design System: Neo-Brutalism The project uses a Neo-Brutalism design aesthetic characterized by: - Bold, black borders - High contrast colors - Shadow offsets for depth - Raw, honest UI elements - Playful but functional ### Design Tokens Located in `src/styles/` and documented in `docs/DESIGN_TOKENS.md`: ```css /* Core colors */ --color-primary: #ff6b35; --color-secondary: #004e89; --color-accent: #f7c548; --color-background: #fffdf7; --color-text: #1a1a1a; /* Borders */ --border-width: 3px; --border-color: #1a1a1a; /* Shadows (offset style) */ --shadow-sm: 2px 2px 0 0 #1a1a1a; --shadow-md: 4px 4px 0 0 #1a1a1a; --shadow-lg: 6px 6px 0 0 #1a1a1a; ``` ### Component Patterns **Brutal Card:** ```tsx
{children}
``` **Brutal Button:** ```tsx ``` ## Example Requests ### Building New Components ``` "Use frontend-specialist to create a PriceTag component that displays the current price and original price (if discounted) in the Neo-Brutalism style with a 'SALE' badge when applicable." ``` ### Performance Optimization ``` "Use frontend-specialist to optimize the deals list page. It's showing poor Largest Contentful Paint scores and the initial load feels sluggish." ``` ### Accessibility Fix ``` "Use frontend-specialist to audit and fix accessibility issues on the shopping list page. Screen reader users report that the checkbox states aren't being announced correctly." ``` ### Responsive Design ``` "Use frontend-specialist to make the store search component work better on mobile. The dropdown menu is getting cut off on smaller screens." ``` ## State Management ### Server State (TanStack Query) ```tsx // Fetching data with caching const { data: deals, isLoading, error, } = useQuery({ queryKey: ['deals', storeId], queryFn: () => dealsApi.getByStore(storeId), staleTime: 5 * 60 * 1000, // 5 minutes }); // Mutations with optimistic updates const mutation = useMutation({ mutationFn: dealsApi.favorite, onMutate: async (dealId) => { await queryClient.cancelQueries(['deals']); const previous = queryClient.getQueryData(['deals']); queryClient.setQueryData(['deals'], (old) => old.map((d) => (d.id === dealId ? { ...d, isFavorite: true } : d)), ); return { previous }; }, onError: (err, dealId, context) => { queryClient.setQueryData(['deals'], context.previous); }, }); ``` ### Client State (Zustand) ```tsx // Simple client-only state const useUIStore = create((set) => ({ sidebarOpen: false, toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })), })); ``` ## The uiux-designer Subagent ### When to Use Use the **uiux-designer** subagent when you need to: - Make design decisions for new features - Improve user flows - Design component layouts - Choose appropriate UI patterns - Plan information architecture ### Example Requests **Design new feature:** ``` "Use uiux-designer to design the user flow for adding items to a shopping list from the deals page. Consider both desktop and mobile experiences." ``` **Improve existing UX:** ``` "Use uiux-designer to improve the flyer upload experience. Users are confused about which file types are supported and don't understand the processing status." ``` **Component design:** ``` "Use uiux-designer to design a price comparison component that shows the same item across multiple stores." ``` ## Component Structure ### Feature-Based Organization ``` src/ ├── components/ # Shared UI components │ ├── ui/ # Basic UI primitives │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ └── Input.tsx │ ├── layout/ # Layout components │ │ ├── Header.tsx │ │ └── Sidebar.tsx │ └── shared/ # Complex shared components │ └── PriceDisplay.tsx ├── features/ # Feature-specific components │ ├── deals/ │ │ ├── components/ │ │ ├── hooks/ │ │ └── api/ │ └── shopping-list/ │ ├── components/ │ ├── hooks/ │ └── api/ └── pages/ # Route page components ├── DealsPage.tsx └── ShoppingListPage.tsx ``` ### Component Pattern ```tsx // src/components/PriceTag.tsx import { cn } from '@/utils/cn'; interface PriceTagProps { currentPrice: number; originalPrice?: number; currency?: string; className?: string; } export function PriceTag({ currentPrice, originalPrice, currency = '$', className, }: PriceTagProps) { const isOnSale = originalPrice && originalPrice > currentPrice; const discount = isOnSale ? Math.round((1 - currentPrice / originalPrice) * 100) : 0; return (
{currency} {currentPrice.toFixed(2)} {isOnSale && ( <> {currency} {originalPrice.toFixed(2)} -{discount}% )}
); } ``` ## Testing React Components ### Component Test Pattern ```tsx import { describe, it, expect, vi } from 'vitest'; import { renderWithProviders, screen } from '@/tests/utils/renderWithProviders'; import userEvent from '@testing-library/user-event'; import { PriceTag } from './PriceTag'; describe('PriceTag', () => { it('displays current price', () => { renderWithProviders(); expect(screen.getByText('$9.99')).toBeInTheDocument(); }); it('shows discount when original price is higher', () => { renderWithProviders(); expect(screen.getByText('$7.99')).toBeInTheDocument(); expect(screen.getByText('$9.99')).toBeInTheDocument(); expect(screen.getByText('-20%')).toBeInTheDocument(); }); }); ``` ### Hook Test Pattern ```tsx import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useDeals } from './useDeals'; describe('useDeals', () => { it('fetches deals for store', async () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); const { result } = renderHook(() => useDeals('store-123'), { wrapper: ({ children }) => ( {children} ), }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toHaveLength(10); }); }); ``` ## Accessibility Guidelines ### ARIA Patterns ```tsx // Proper button with loading state // Proper form field {errors.email && ( {errors.email} )} ``` ### Keyboard Navigation - All interactive elements must be focusable - Focus order should be logical - Focus traps for modals - Skip links for main content ### Color Contrast - Normal text: minimum 4.5:1 contrast ratio - Large text: minimum 3:1 contrast ratio - Use the Neo-Brutalism palette which is designed for high contrast ## Performance Optimization ### Code Splitting ```tsx // Lazy load heavy components const PdfViewer = lazy(() => import('./PdfViewer')); function FlyerPage() { return ( }> ); } ``` ### Image Optimization ```tsx // Use appropriate sizes and formats {itemName} ``` ### Memoization ```tsx // Memoize expensive computations const sortedDeals = useMemo(() => deals.slice().sort((a, b) => a.price - b.price), [deals]); // Memoize callbacks passed to children const handleSelect = useCallback((id: string) => { setSelectedId(id); }, []); ``` ## Related Documentation - [OVERVIEW.md](./OVERVIEW.md) - Subagent system overview - [CODER-GUIDE.md](./CODER-GUIDE.md) - For implementing features - [TESTER-GUIDE.md](./TESTER-GUIDE.md) - Component testing patterns - [../development/DESIGN_TOKENS.md](../development/DESIGN_TOKENS.md) - Design token reference - [../adr/0012-frontend-component-library-and-design-system.md](../adr/0012-frontend-component-library-and-design-system.md) - Design system ADR - [../adr/0005-frontend-state-management-and-server-cache-strategy.md](../adr/0005-frontend-state-management-and-server-cache-strategy.md) - State management ADR - [../adr/0044-frontend-feature-organization.md](../adr/0044-frontend-feature-organization.md) - Feature organization