---
name: audit-react-vite-tsx-codebase
description: "Critical audit of a React + Vite + TSX codebase — strict TypeScript, JSDoc, component architecture, error boundaries, a11y, performance, Vitest. Re-invoke until converged."
version: 1.0.0
---

# audit-react-vite-tsx-codebase

Perform a single critical audit pass on a React + Vite + TypeScript (TSX) codebase folder. Designed to be re-invoked until convergence (zero remaining issues).

## Instructions

You are performing a **single audit pass** on the React/Vite/TypeScript codebase at `$ARGUMENTS`. This skill is idempotent and convergent — each invocation examines the current state, fixes what it can, and reports what remains.

### Phase 0: Baseline Snapshot

1. Run the TypeScript compiler in check mode: `npx tsc --noEmit` — capture error count.
2. Run the linter: `npm run lint` (or `npx oxlint` / `npx eslint .`) — capture violation count.
3. Run tests if they exist: `npx vitest run --reporter=verbose` — capture pass/fail count.
4. Check build: `npm run build` — does it succeed?
5. Record all counts — you will compare at the end.

### Phase 1: TypeScript Strictness & Type Safety

Ensure the project uses strict TypeScript and every value is properly typed.

**tsconfig.json requirements:**
```json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "exactOptionalPropertyTypes": true,
    "noPropertyAccessFromIndexSignature": true
  }
}
```

**Rules:**
- No `any` type — use `unknown` with type guards, or define proper types.
- No `// @ts-ignore` or `// @ts-expect-error` without a comment explaining why.
- Every function has explicit parameter types and return types.
- Every component has a typed `Props` interface (not inline object types).
- Event handlers are typed: `React.MouseEvent<HTMLButtonElement>`, not `any`.
- API responses have proper interfaces (not `any` or untyped JSON).
- Use discriminated unions for state that can be in multiple shapes.
- Prefer `interface` for object shapes (extendable), `type` for unions/intersections.
- Use `satisfies` operator where appropriate for type narrowing without widening.
- Run `npx tsc --noEmit` — target zero errors.

**Pattern to enforce:**
```tsx
// Props interface above component
interface UserCardProps {
  user: User;
  onSelect: (userId: string) => void;
  variant?: "compact" | "full";
}

// Explicit return type on components with complex returns
export function UserCard({ user, onSelect, variant = "full" }: UserCardProps): React.ReactElement {
  // ...
}
```

### Phase 2: Component Documentation (JSDoc + TSDoc)

**Module/File-level:** Every `.tsx`/`.ts` file with exports gets a top-of-file JSDoc comment.

```tsx
/**
 * @module PipelineStatus
 * @description Real-time pipeline execution dashboard with SSE-driven updates.
 * Displays agent progress, HITL gates, and final decision rendering.
 */
```

**Component documentation:**
```tsx
/**
 * Displays the coverage determination result with approval/denial reasoning.
 *
 * @remarks
 * Subscribes to the pipeline SSE stream and updates in real-time.
 * Renders a skeleton loader until the first event arrives.
 *
 * @example
 * ```tsx
 * <DecisionCard caseId="PA-2024-001" onAppeal={handleAppeal} />
 * ```
 */
export function DecisionCard({ caseId, onAppeal }: DecisionCardProps): React.ReactElement {
```

**Hook documentation:**
```tsx
/**
 * Manages WebSocket connection for HITL gate interactions.
 *
 * @param caseId - Active case identifier
 * @param onGateReached - Callback when pipeline hits a HITL gate
 * @returns Connection state, gate data, and approval/rejection actions
 *
 * @example
 * ```tsx
 * const { gateData, approve, reject } = useHITLGate("PA-001", handleGate);
 * ```
 */
export function useHITLGate(caseId: string, onGateReached: GateCallback): HITLGateHook {
```

**Rules:**
- Every exported component, hook, utility function, and type gets a JSDoc block.
- Include `@remarks` for non-obvious behavior (side effects, subscriptions, performance).
- Include `@example` with a usage snippet for components and hooks.
- Include `@param` and `@returns` for hooks and utility functions.
- Internal/private helpers get at minimum a one-line `/** ... */` comment.
- Type interfaces get `/** */` on the interface AND on non-obvious properties.

### Phase 3: Component Architecture & React Best Practices

**Rules for component structure:**

| Principle | Smell | Action |
|-----------|-------|--------|
| Single Responsibility | Component > 150 LOC or renders unrelated sections | Extract sub-components |
| Separation of concerns | Business logic mixed with rendering | Extract to custom hooks |
| DRY | Same JSX pattern in 3+ places | Extract shared component |
| Pure components | Component re-renders without prop changes | Add `React.memo` or fix parent |
| Colocation | Related files scattered across folders | Co-locate component + hook + test + types |
| Composition over config | Component with 10+ boolean props | Use compound component or slots pattern |

**Specific checks:**
- No direct DOM manipulation (`document.querySelector`) — use refs.
- No `useEffect` for derived state — compute inline or use `useMemo`.
- No `useEffect` for event responses — handle in the event handler.
- `useEffect` dependencies are correct and exhaustive.
- No `eslint-disable` on exhaustive-deps without justification.
- State that is always set together lives in one `useState` or `useReducer`.
- Expensive computations are wrapped in `useMemo` with correct deps.
- Event handlers passed to children are wrapped in `useCallback` when children are memoized.
- No prop drilling beyond 2 levels — use context or composition.
- Keys on lists are stable and unique (not array index unless list is static).

**Folder structure enforcement:**
```
src/
├── components/         # Reusable UI components
│   └── ComponentName/
│       ├── ComponentName.tsx
│       ├── ComponentName.test.tsx
│       ├── useComponentLogic.ts    # (if complex logic)
│       └── index.ts
├── hooks/              # Shared custom hooks
├── pages/              # Route-level components
├── services/           # API layer
├── types/              # Shared TypeScript types
├── utils/              # Pure utility functions
└── constants/          # App-wide constants
```

### Phase 4: Error Handling & Error Boundaries

**Rules:**
- Every page/route has an Error Boundary wrapping it.
- API calls have proper error handling (try/catch or `.catch()`).
- Loading and error states are handled for every async operation (not just happy path).
- User-facing errors show meaningful messages (not raw error strings or stack traces).
- No unhandled promise rejections — every `.then()` has a `.catch()` or is `await`-ed in a try/catch.
- Network failures are retried or gracefully degraded (not silent failures).
- Form validation shows inline errors, not just console logs.

**Pattern to enforce:**
```tsx
// Error boundary at route level
<ErrorBoundary fallback={<ErrorPage />}>
  <Suspense fallback={<PageSkeleton />}>
    <PipelineDashboard />
  </Suspense>
</ErrorBoundary>

// Async state pattern
interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}
```

**Custom error types:**
```tsx
export class APIError extends Error {
  constructor(
    message: string,
    public readonly statusCode: number,
    public readonly endpoint: string,
  ) {
    super(message);
    this.name = "APIError";
  }
}
```

### Phase 5: Accessibility (a11y)

**Rules:**
- Every interactive element is keyboard-accessible (Tab, Enter, Escape).
- Every image has an `alt` attribute (empty `alt=""` for decorative images).
- Form inputs have associated `<label>` elements (or `aria-label`).
- Color is never the only way to convey information (add icons/text).
- Focus management on route changes and modal opens.
- ARIA roles on custom interactive widgets (tabs, accordions, dialogs).
- Semantic HTML: `<button>` for actions (not `<div onClick>`), `<nav>`, `<main>`, `<section>`.
- Sufficient color contrast (WCAG AA: 4.5:1 for text, 3:1 for large text).
- No `tabIndex` > 0 (disrupts natural tab order).

### Phase 6: Performance & Bundle Hygiene

**Rules:**
- No unused imports or dead code (tree-shaking can't save everything).
- Heavy dependencies are lazy-loaded: `React.lazy()` + `Suspense`.
- Images use proper formats (WebP/AVIF) and are sized appropriately.
- Lists with 50+ items use virtualization (`@tanstack/react-virtual` or similar).
- No synchronous blocking in render (heavy computation → Web Worker or `useMemo`).
- Bundle size check: `npx vite-bundle-visualizer` — flag anything > 50KB that could be lazy-loaded.
- No barrel file re-exports that defeat tree-shaking (`export * from`).
- CSS: Tailwind utility classes only (per project constraint) — no unused custom CSS.

### Phase 7: Testing (Vitest + Testing Library)

**Rules:**
- Run `npx vitest run --coverage` to identify untested components.
- Test behavior, not implementation: "when user clicks X, Y appears" — not "useState was called".
- Use `@testing-library/react` — query by role/label/text, not test-ids (unless necessary).
- Every component with conditional rendering needs tests for each branch.
- Every custom hook gets a test via `renderHook`.
- API integration tests mock at the network level (`msw`), not at the module level.
- Shared test utilities go in a `test-utils.tsx` that re-exports from `@testing-library/react` with providers.
- Snapshot tests are acceptable ONLY for small, stable components — prefer explicit assertions.

**Pattern:**
```tsx
// test-utils.tsx
function renderWithProviders(ui: React.ReactElement, options?: RenderOptions) {
  return render(ui, {
    wrapper: ({ children }) => (
      <QueryClientProvider client={testQueryClient}>
        <ThemeProvider>{children}</ThemeProvider>
      </QueryClientProvider>
    ),
    ...options,
  });
}

// Component.test.tsx
describe("DecisionCard", () => {
  it("shows approval status when case is approved", async () => {
    renderWithProviders(<DecisionCard caseId="PA-001" />);
    expect(await screen.findByRole("status")).toHaveTextContent("Approved");
  });

  it("calls onAppeal when appeal button is clicked", async () => {
    const onAppeal = vi.fn();
    renderWithProviders(<DecisionCard caseId="PA-001" onAppeal={onAppeal} />);
    await userEvent.click(screen.getByRole("button", { name: /appeal/i }));
    expect(onAppeal).toHaveBeenCalledWith("PA-001");
  });
});
```

### Phase 8: Final Clean Code Gate

Run in sequence — all must pass with zero violations:

1. `npx tsc --noEmit` — zero TypeScript errors.
2. `npm run lint` (or `npx oxlint .` / `npx eslint .`) — zero lint errors.
3. `npm run build` — builds successfully with no warnings treated as errors.
4. `npx vitest run` — all tests pass.
5. Review any `// @ts-expect-error` or `eslint-disable` — each must have justification.

### Phase 9: Smoke Tests

1. `npm run dev` — dev server starts without errors.
2. Open in browser — no console errors on initial load.
3. Navigate the primary user flow (e.g., submit a case → see pipeline → get decision).
4. Check for visual regressions (layout, responsiveness at mobile/desktop breakpoints).
5. Check network tab — no failed requests in happy path.
6. `npm run build && npm run preview` — production build serves correctly.
7. Compare final tsc/lint counts against Phase 0 baseline.

### Output: Audit Scorecard

End every invocation with this exact format:

```
── Audit Scorecard ─────────────────────────────────────────
  Pass:              N (where N is which re-invocation this is, default 1)
  Folder:            <folder_path>
  
  tsc errors:        [before] → [after]
  lint violations:   [before] → [after]
  missing docs:      [before] → [after]
  test coverage:     [before]% → [after]%
  tests passing:     [before] → [after]
  build status:      [PASS/FAIL] → [PASS/FAIL]
  a11y issues:       [before] → [after]
  
  Issues fixed this pass:    X
  Issues remaining:          Y
  Items needing human decision:
    - [list each item with file:line and reason]

  Recommendation:    RE-RUN | CONVERGED ✓
────────────────────────────────────────────────────────────
```

**Recommendation logic:**
- `RE-RUN` if any issues remain that another pass could fix.
- `CONVERGED ✓` if tsc=0, lint=0, all tests pass, build succeeds, and no new issues found.

### Important Constraints

- Commit changes at the end of each phase with a message like `audit: phase N — <description>`.
- If a refactoring might break things, run `npx tsc --noEmit` and tests immediately — do not accumulate risk.
- Do NOT add features, new routes, or speculative abstractions.
- Do NOT delete tests or weaken assertions to make them pass.
- Do NOT change the build tooling (Vite config, Tailwind setup) unless it's broken.
- Respect the existing Tailwind-only CSS constraint — do NOT introduce CSS modules or styled-components.
- If unsure whether a change is safe, flag it in "Items needing human decision" and skip it.
- Preserve existing component APIs (props interfaces) — changing them is a breaking change for consumers.