Skip to content

Conversation

@mldangelo
Copy link
Member

@mldangelo mldangelo commented Dec 6, 2025

Summary

Comprehensive refactoring of the Model Audit UI architecture:

  1. Multi-page architecture - Split monolithic component into dedicated pages:

    • `/model-audit` - Latest scan result
    • `/model-audits` - Scan history list (mirrors `/evals`)
    • `/model-audit/:id` - Individual scan result (mirrors `/eval/:evalId`)
    • `/model-audit/setup` - Scan configuration
  2. Store architecture - Split into specialized Zustand stores:

    • `useModelAuditConfigStore` - Configuration, paths, options, scanning state
    • `useModelAuditHistoryStore` - History list, pagination, search, delete operations
  3. Route structure - Now mirrors the eval pattern for consistency

  4. Code quality - Added comprehensive tests, fixed TypeScript errors, addressed review feedback

Changes

Architecture

  • Split `ModelAudit.tsx` into 4 dedicated page components
  • Created centralized route constants in `src/app/src/constants/routes.ts`
  • Added skeleton loading states for better UX
  • Added InstallationGuide component for better onboarding

Stores

  • `useModelAuditConfigStore`: Scanning config, paths, installation status
  • `useModelAuditHistoryStore`: Pagination, sorting, search, CRUD operations

Routes (mirrors eval)

Model Audit Eval Equivalent
`/model-audit` `/eval`
`/model-audits` `/evals`
`/model-audit/:id` `/eval/:evalId`
`/model-audit/setup` `/setup`

Fixes

  • Fixed check count inconsistency between history and detail pages
  • Fixed scan ID handling in URLs (colons are valid in paths per RFC 3986)
  • Fixed React StrictMode race condition with aborted requests showing "Scan not found"
  • Added input validation for sort/order params (SQL injection prevention)
  • Added accessibility attributes to InstallationGuide

Test plan

  • All 136 model-audit tests pass
  • Manually test navigation between pages
  • Verify scan creation and viewing works
  • Verify history list pagination and search
  • Test delete functionality with confirmation

🤖 Generated with Claude Code

mldangelo and others added 3 commits December 5, 2025 00:18
Implement the first phase of the Model Audit UI multi-page architecture with:

**New Pages:**
- `/model-audit` - Shows latest scan results or empty state for first-time users
- `/model-audit/setup` - Dedicated setup page for configuring new scans
- `/model-audit/history` - DataGrid-based history with pagination, sorting, search
- `/model-audit/result/:id` - Individual scan detail view

**State Management:**
- Add Zustand stores for config and history management
- useModelAuditConfigStore for scan configuration state
- useModelAuditHistoryStore for paginated history with server-side operations

**Backend Enhancements:**
- Add pagination support to `/api/model-audit/scans` endpoint
- Add `getMany` method with offset-based pagination to ModelAudit model
- Add `count` method for total record count

**Navigation:**
- Add Model Audit to Security dropdown in main navigation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Remove deprecated monolithic store in favor of specialized stores:
- Delete store.ts, ModelAudit.tsx, HistoryTab.tsx, page.tsx (unused old components)
- Add comprehensive tests for stores and pages
- Add InstallationGuide component for CLI setup assistance
- Add skeleton loaders for improved loading UX
- Implement optimistic deletes with rollback
- Add localStorage migration from old store

The model-audit feature now uses:
- useModelAuditConfigStore: config, scan state, recent scans, installation
- useModelAuditHistoryStore: historical scans with pagination

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@use-tusk
Copy link
Contributor

use-tusk bot commented Dec 6, 2025

⏩ PR identified as a refactor (fd07fa3) View output ↗


View check history

Commit Status Output Created (UTC)
ae3ee68 ⏩ PR identified as a refactor Output Dec 6, 2025 7:45AM
7529651 ⏩ PR identified as a refactor Output Dec 6, 2025 8:11AM
b1ef331 ⏩ PR identified as a refactor Output Dec 6, 2025 8:19AM
fa5fdaf ⏩ PR identified as a refactor Output Dec 6, 2025 8:21AM
f6576a3 ⏩ PR identified as a refactor Output Dec 6, 2025 8:33AM
fca52fd ⏩ PR identified as a refactor Output Dec 6, 2025 8:39AM
1271e75 ⏩ PR identified as a refactor Output Dec 6, 2025 8:47AM
24083b2 ⏩ PR identified as a refactor Output Dec 6, 2025 8:59AM
800f7d4 ⏩ PR identified as a refactor Output Dec 6, 2025 9:12AM
fd07fa3 ⏩ PR identified as a refactor Output Dec 6, 2025 9:24AM

View output in GitHub ↗

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This PR refactors the Model Audit feature from a monolithic page into a modular multi-page architecture. It introduces four specialized pages (latest, setup, history, result detail), splits the centralized store into configuration and history-focused stores with legacy data migration, and enhances backend endpoints with pagination and search capabilities. New skeleton components improve loading states, and an InstallationGuide assists with CLI setup. Infrastructure files for tests and type definitions are updated accordingly. The changes decompose a single page component and store into a scalable structure while preserving existing functionality.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Store migration logic in useModelAuditConfigStore.ts — migration from legacy model-audit-store with data merging and cleanup requires careful verification
  • Multi-store coordination — interaction patterns between useModelAuditConfigStore and useModelAuditHistoryStore across multiple new pages need validation
  • Backend pagination/search implementation — ensure query construction with dynamic filtering, sorting, and count logic is correct and performant
  • Page integration — verify navigation flow and state passing between setup, history, latest, and result detail pages
  • Test coverage alignment — confirm new test suites adequately replace removed ModelAudit.test.tsx coverage

Possibly related PRs

  • feat: Static model scanning UI #4368 — Introduces the initial Model Audit feature that this PR refactors and decomposes into separate pages, stores, and API endpoints with pagination support.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore(webui): refactor model-audit store architecture' accurately and clearly summarizes the main change of the pull request—refactoring the model-audit store from a monolithic architecture to specialized stores.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing a comprehensive refactoring of the Model Audit UI architecture including multi-page architecture, store separation, and route restructuring.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/model-audit-store-cleanup

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (10)
src/app/src/pages/model-audit/ModelAudit.types.ts (1)

1-104: Frontend scan/history types look coherent; consider reusing shared history type if available

The type layering here (type-only imports, ScanResult extending ModelAuditScanResults, stricter ScanIssue.details, and new RecentScan/InstallationStatus/HistoricalScan) lines up well with the new config/history stores and the usages in the pages.

If @promptfoo/types/modelAudit already exposes a history/record type analogous to HistoricalScan, you might consider aliasing or extending that instead of redefining the whole shape here to reduce drift between frontend and backend models over time. Otherwise this is fine as-is.

src/app/src/pages/model-audit/components/PathSelector.tsx (1)

34-35: Store migration and per-path removal behavior look correct; consider selector to limit re-renders

Switching to useModelAuditConfigStore and wiring removeRecentPath(path.scanId, path.path) matches the new store API and the intended “remove just this path, and drop the scan if it’s empty” behavior.

To avoid re-rendering PathSelector on unrelated store changes, you could narrow the subscription to just what this component needs, e.g.:

const { recentScans, clearRecentScans, removeRecentPath } = useModelAuditConfigStore((state) => ({
  recentScans: state.recentScans,
  clearRecentScans: state.clearRecentScans,
  removeRecentPath: state.removeRecentPath,
}));

Not required, but it keeps this fairly heavy component from reacting to config-state updates it doesn’t use.

Also applies to: 77-77, 435-457

src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts (1)

1-347: Store tests are thorough; consider stronger test isolation for mocks/persistence

The test coverage here is solid and aligns well with the new config-store surface (paths, recent scans including removeRecentPath, scan state, installation checks with dedup, UI flags, and scan options).

Two small isolation tweaks you might consider:

  • Swap vi.clearAllMocks() in beforeEach for vi.resetAllMocks() in an afterEach (or keep both) to also reset mocked implementations between tests, per the existing testing guidelines.
  • If you later add tests that exercise persistence/rehydration, it may help to clear the persisted slice in a global hook, e.g. via useModelAuditConfigStore.persist.clearStorage?.() or localStorage.clear() to avoid cross-test coupling.

Not blockers, just to keep the suite robust as it grows.

src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts (1)

224-242: Missing rollback verification in delete error test.

The error test doesn't set up initial state with scans, so the optimistic update rollback behavior isn't verified. Consider adding initial state similar to the success test to verify the rollback actually restores the scans.

Apply this diff to properly test rollback behavior:

   it('should handle delete error', async () => {
+    const mockScans = [createMockScan('1', 'Scan 1'), createMockScan('2', 'Scan 2')];
+
+    // Set up initial state
+    useModelAuditHistoryStore.setState({
+      historicalScans: mockScans,
+      totalCount: 2,
+    });
+
     mockCallApi.mockResolvedValueOnce({
       ok: false,
     } as Response);

     const { result } = renderHook(() => useModelAuditHistoryStore());

     let thrownError: Error | null = null;
     await act(async () => {
       try {
         await result.current.deleteHistoricalScan('1');
       } catch (error) {
         thrownError = error as Error;
       }
     });

     expect(thrownError?.message).toBe('Failed to delete scan');
     expect(result.current.historyError).toBe('Failed to delete scan');
+    // Verify rollback restored the original scans
+    expect(result.current.historicalScans).toHaveLength(2);
+    expect(result.current.totalCount).toBe(2);
   });
src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx (1)

224-229: Fragile button selection logic.

Selecting the confirm button by checking className.includes('contained') is brittle and may break with MUI updates. Consider using a more reliable selector.

Consider using a more robust selector, such as querying within the dialog:

-    // Get all delete buttons and find the one in the dialog (MuiButton-containedError)
-    const deleteButtons = screen.getAllByRole('button', { name: /Delete/i });
-    const confirmButton = deleteButtons.find(
-      (btn) => btn.className.includes('contained') || btn.getAttribute('variant') === 'contained',
-    );
-    await user.click(confirmButton || deleteButtons[deleteButtons.length - 1]);
+    // Find the confirm button within the dialog by its unique text
+    const confirmButton = screen.getByRole('button', { name: 'Delete' });
+    // The confirm button should be the one inside the dialog (not the toolbar button)
+    const dialogButtons = screen.getAllByRole('button', { name: /Delete/i });
+    // Click the last one which is in the dialog
+    await user.click(dialogButtons[dialogButtons.length - 1]);
src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx (1)

147-169: Defensive fallback may mask test failures.

The fallback at lines 165-168 only verifies the element exists without testing navigation, which could hide real failures if the DataGrid structure changes. Consider making the test more robust or adding a skip condition with a clear message.

Consider making the test behavior more explicit:

     // Click on the row (the scan name is in the row)
     const scanNameCell = screen.getByText('Test Scan');
     const row = scanNameCell.closest('[data-rowindex]');
-    if (row) {
-      await user.click(row);
-      expect(mockNavigate).toHaveBeenCalledWith('/model-audit/scan-123');
-    } else {
-      // Fallback: verify the scan name is rendered correctly
-      expect(scanNameCell).toBeInTheDocument();
-    }
+    expect(row).toBeTruthy(); // Fail explicitly if row structure changes
+    if (row) {
+      await user.click(row);
+      expect(mockNavigate).toHaveBeenCalledWith('/model-audit/scan-123');
+    }
src/server/routes/modelAudit.ts (1)

423-462: API responses don't follow standard format.

The coding guidelines specify: "Wrap all API responses in the standard format: { success: boolean; data?: T; error?: string }"

Current responses:

  • Line 435-440: Returns { scans, total, limit, offset } directly
  • Line 457: Returns audits[0].toJSON() directly
  • Line 453: Returns { error } without success: false

However, this pattern is consistent with the rest of the file (lines 23, 59-64, 403-408, 474). Consider updating all endpoints in a follow-up refactor.

Based on coding guidelines: "Wrap all API responses in the standard format: { success: boolean; data?: T; error?: string } using res.json({ success: true, data: ... }) or res.status(code).json({ success: false, error: ... })"

Apply this diff to standardize the response format:

     const audits = await ModelAudit.getMany(limit, offset, sort, order, search);
     const total = await ModelAudit.count(search);

-    res.json({
-      scans: audits.map((audit) => audit.toJSON()),
-      total,
-      limit,
-      offset,
-    });
+    res.json({
+      success: true,
+      data: {
+        scans: audits.map((audit) => audit.toJSON()),
+        total,
+        limit,
+        offset,
+      },
+    });
   } catch (error) {
     logger.error(`Error fetching model audits: ${error}`);
-    res.status(500).json({ error: String(error) });
+    res.status(500).json({ success: false, error: String(error) });
   }
 });

 // Get the latest/most recent model scan
 modelAuditRouter.get('/scans/latest', async (_req: Request, res: Response): Promise<void> => {
   try {
     const audits = await ModelAudit.getMany(1, 0, 'createdAt', 'desc');

     if (audits.length === 0) {
-      res.status(404).json({ error: 'No scans found' });
+      res.status(404).json({ success: false, error: 'No scans found' });
       return;
     }

-    res.json(audits[0].toJSON());
+    res.json({ success: true, data: audits[0].toJSON() });
   } catch (error) {
     logger.error(`Error fetching latest model audit: ${error}`);
-    res.status(500).json({ error: String(error) });
+    res.status(500).json({ success: false, error: String(error) });
   }
 });
src/models/modelAudit.ts (1)

255-317: Consider extracting search filter logic to reduce duplication.

The search filter construction (lines 268-276 and 305-312) is duplicated between getMany and count. Consider extracting it into a private helper function.

Apply this diff to reduce duplication:

+  private static buildSearchFilter(search: string) {
+    return or(
+      like(modelAuditsTable.name, `%${search}%`),
+      like(modelAuditsTable.modelPath, `%${search}%`),
+      like(modelAuditsTable.id, `%${search}%`),
+    );
+  }
+
   static async getMany(
     limit: number = 100,
     offset: number = 0,
     sortField: string = 'createdAt',
     sortOrder: string = 'desc',
     search?: string,
   ): Promise<ModelAudit[]> {
     const db = getDb();

     // Build the base query
     let query = db.select().from(modelAuditsTable);

     // Apply search filter if provided
     if (search) {
-      query = query.where(
-        or(
-          like(modelAuditsTable.name, `%${search}%`),
-          like(modelAuditsTable.modelPath, `%${search}%`),
-          like(modelAuditsTable.id, `%${search}%`),
-        ),
-      ) as typeof query;
+      query = query.where(this.buildSearchFilter(search)) as typeof query;
     }

     // ... rest of method

   static async count(search?: string): Promise<number> {
     const db = getDb();

     let query = db.select({ value: count() }).from(modelAuditsTable);

     // Apply search filter if provided
     if (search) {
-      query = query.where(
-        or(
-          like(modelAuditsTable.name, `%${search}%`),
-          like(modelAuditsTable.modelPath, `%${search}%`),
-          like(modelAuditsTable.id, `%${search}%`),
-        ),
-      ) as typeof query;
+      query = query.where(this.buildSearchFilter(search)) as typeof query;
     }

     const result = await query.get();
     return result?.value || 0;
   }
src/app/src/pages/model-audit/components/InstallationGuide.tsx (1)

39-47: Clean up setTimeout on component unmount.

The setTimeout on line 43 isn't cleaned up if the component unmounts within 2 seconds. While unlikely to cause issues, it's better to clean up timers properly.

Apply this diff to add cleanup:

-  const [copied, setCopied] = useState(false);
+  const [copied, setCopied] = useState(false);
+  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+    };
+  }, []);

   const handleCopy = async () => {
     try {
       await navigator.clipboard.writeText(installCommand);
       setCopied(true);
-      setTimeout(() => setCopied(false), 2000);
+      timeoutRef.current = setTimeout(() => setCopied(false), 2000);
     } catch (err) {
       console.error('Failed to copy:', err);
     }
   };

Don't forget to add useRef and useEffect to the imports on line 18.

src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx (1)

180-183: Inconsistent date formatting compared to other model-audit pages.

Other pages in this PR (ModelAuditHistory, ModelAuditResult) use formatDataGridDate from @app/utils/date, but this component uses toLocaleString(). For consistency across the model-audit feature, consider using the same formatting utility.

+import { formatDataGridDate } from '@app/utils/date';
 import { callApi } from '@app/utils/api';
               <Typography variant="body2" color="text.secondary">
                 {latestScan.name || 'Model Security Scan'} •{' '}
-                {new Date(latestScan.createdAt).toLocaleString()}
+                {formatDataGridDate(latestScan.createdAt)}
               </Typography>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3d4a85 and ae3ee68.

📒 Files selected for processing (31)
  • src/app/src/App.tsx (2 hunks)
  • src/app/src/components/Navigation.tsx (2 hunks)
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx (1 hunks)
  • src/app/src/pages/model-audit-history/ModelAuditHistory.tsx (1 hunks)
  • src/app/src/pages/model-audit-history/page.tsx (1 hunks)
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx (1 hunks)
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx (1 hunks)
  • src/app/src/pages/model-audit-latest/page.tsx (1 hunks)
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx (1 hunks)
  • src/app/src/pages/model-audit-result/ModelAuditResult.tsx (1 hunks)
  • src/app/src/pages/model-audit-result/page.tsx (1 hunks)
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx (1 hunks)
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx (5 hunks)
  • src/app/src/pages/model-audit-setup/page.tsx (1 hunks)
  • src/app/src/pages/model-audit/ModelAudit.test.tsx (0 hunks)
  • src/app/src/pages/model-audit/ModelAudit.types.ts (3 hunks)
  • src/app/src/pages/model-audit/components/ConfigurationTab.tsx (4 hunks)
  • src/app/src/pages/model-audit/components/HistoryTab.test.tsx (0 hunks)
  • src/app/src/pages/model-audit/components/HistoryTab.tsx (0 hunks)
  • src/app/src/pages/model-audit/components/InstallationGuide.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx (4 hunks)
  • src/app/src/pages/model-audit/components/PathSelector.tsx (2 hunks)
  • src/app/src/pages/model-audit/page.tsx (0 hunks)
  • src/app/src/pages/model-audit/stores/index.ts (1 hunks)
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts (1 hunks)
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts (5 hunks)
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts (1 hunks)
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts (1 hunks)
  • src/models/modelAudit.ts (2 hunks)
  • src/server/routes/modelAudit.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • src/app/src/pages/model-audit/components/HistoryTab.tsx
  • src/app/src/pages/model-audit/components/HistoryTab.test.tsx
  • src/app/src/pages/model-audit/page.tsx
  • src/app/src/pages/model-audit/ModelAudit.test.tsx
🧰 Additional context used
📓 Path-based instructions (9)
src/app/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/app/AGENTS.md)

src/app/src/**/*.{ts,tsx}: NEVER use fetch() directly. ALWAYS use callApi() from '@app/utils/api' for API calls to handle dev/prod API URL differences
Avoid unnecessary memoization with useMemo() unless performance is verified as a problem
Use @app/* path alias (maps to src/*) configured in vite.config.ts for all imports

Files:

  • src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-result/page.tsx
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/page.tsx
  • src/app/src/pages/model-audit/components/PathSelector.tsx
  • src/app/src/pages/model-audit/stores/index.ts
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.tsx
  • src/app/src/pages/model-audit/components/InstallationGuide.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/App.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.tsx
  • src/app/src/pages/model-audit/components/ConfigurationTab.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts
  • src/app/src/components/Navigation.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts
  • src/app/src/pages/model-audit-setup/page.tsx
  • src/app/src/pages/model-audit-history/page.tsx
  • src/app/src/pages/model-audit/ModelAudit.types.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/app/**/*.{ts,tsx}: When making API calls from the React app, use the callApi function from @app/utils/api instead of direct fetch() calls to ensure proper API base URL handling
Use useMemo when computing a value that doesn't accept arguments (a non-callable) in React hooks
Use useCallback when creating a stable function reference that accepts arguments and will be called later in React hooks

Files:

  • src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-result/page.tsx
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/page.tsx
  • src/app/src/pages/model-audit/components/PathSelector.tsx
  • src/app/src/pages/model-audit/stores/index.ts
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.tsx
  • src/app/src/pages/model-audit/components/InstallationGuide.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/App.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.tsx
  • src/app/src/pages/model-audit/components/ConfigurationTab.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts
  • src/app/src/components/Navigation.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts
  • src/app/src/pages/model-audit-setup/page.tsx
  • src/app/src/pages/model-audit-history/page.tsx
  • src/app/src/pages/model-audit/ModelAudit.types.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript with strict type checking

Files:

  • src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-result/page.tsx
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/page.tsx
  • src/app/src/pages/model-audit/components/PathSelector.tsx
  • src/app/src/pages/model-audit/stores/index.ts
  • src/server/routes/modelAudit.ts
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.tsx
  • src/app/src/pages/model-audit/components/InstallationGuide.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/App.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.tsx
  • src/app/src/pages/model-audit/components/ConfigurationTab.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts
  • src/app/src/components/Navigation.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
  • src/models/modelAudit.ts
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts
  • src/app/src/pages/model-audit-setup/page.tsx
  • src/app/src/pages/model-audit-history/page.tsx
  • src/app/src/pages/model-audit/ModelAudit.types.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Follow consistent import order using Biome for import sorting
Use consistent curly braces for all control statements
Prefer const over let; avoid var
Use object shorthand syntax whenever possible
Use async/await for asynchronous code
Use consistent error handling with proper type checks
Always sanitize sensitive data before logging to prevent exposing secrets, API keys, passwords, and other credentials in logs
Use the sanitized logging method by passing context objects as a second parameter to logger methods (debug, info, warn, error) which will automatically sanitize sensitive fields
For manual sanitization outside of logging contexts, use the sanitizeObject function from ./util/sanitizer which sanitizes recursively up to 4 levels deep

Files:

  • src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-result/page.tsx
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/page.tsx
  • src/app/src/pages/model-audit/components/PathSelector.tsx
  • src/app/src/pages/model-audit/stores/index.ts
  • src/server/routes/modelAudit.ts
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.tsx
  • src/app/src/pages/model-audit/components/InstallationGuide.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/App.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.tsx
  • src/app/src/pages/model-audit/components/ConfigurationTab.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts
  • src/app/src/components/Navigation.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
  • src/models/modelAudit.ts
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts
  • src/app/src/pages/model-audit-setup/page.tsx
  • src/app/src/pages/model-audit-history/page.tsx
  • src/app/src/pages/model-audit/ModelAudit.types.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
src/app/src/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (src/app/AGENTS.md)

src/app/src/**/*.test.{ts,tsx}: Import mocking utilities from 'vitest' (describe, it, expect, vi, beforeEach, afterEach) for test files
Use @testing-library/react (render, screen, fireEvent) for component testing instead of Jest utilities
Mock the '@app/utils/api' module and callApi function in tests using vi.mock()

Files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for all tests in both backend tests in test/ and frontend tests in src/app/

Files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
src/server/**/*.ts

📄 CodeRabbit inference engine (src/server/AGENTS.md)

src/server/**/*.ts: Always sanitize when logging request/response data - use logger.debug() with object parameters to auto-redact API keys and auth headers instead of string interpolation
Always validate request bodies with Zod schemas from apiSchemas.ts before processing
Use Drizzle ORM for database access with the schema from src/database/schema.ts - import db and use query methods like db.select().from(table).where()
Use Socket.io for WebSocket real-time communication - use socket.join() for subscribing to namespaces and io.to().emit() for broadcasting updates

Files:

  • src/server/routes/modelAudit.ts
src/server/routes/**/*.ts

📄 CodeRabbit inference engine (src/server/AGENTS.md)

src/server/routes/**/*.ts: Wrap all API responses in the standard format: { success: boolean; data?: T; error?: string } using res.json({ success: true, data: ... }) or res.status(code).json({ success: false, error: ... })
Use try-catch error handling with proper HTTP status codes (200, 201, 400, 404, 500) - return 400 for ValidationError and 500 for other errors

Files:

  • src/server/routes/modelAudit.ts
src/app/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/app/AGENTS.md)

src/app/src/components/**/*.{ts,tsx}: Use icons from @mui/icons-material for UI icons
Use MUI's styled() function with theme access for custom styled components instead of inline styles

Files:

  • src/app/src/components/Navigation.tsx
🧠 Learnings (27)
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/providers/**/*.test.{ts,tsx,js} : Provider tests must cover: success case (normal API response), error cases (4xx, 5xx, rate limits), configuration validation, and token usage tracking

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-11-29T00:26:16.694Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/redteam/AGENTS.md:0-0
Timestamp: 2025-11-29T00:26:16.694Z
Learning: Applies to src/redteam/test/redteam/**/*.ts : Add tests for new red team plugins in the `test/redteam/` directory

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:18:56.517Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/providers/AGENTS.md:0-0
Timestamp: 2025-12-01T18:18:56.517Z
Learning: Applies to src/providers/test/providers/**/*.test.ts : Every provider MUST have test coverage in `test/providers/` directory with mocked API responses, success and error case testing, rate limit and timeout testing

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:18:45.551Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/app/AGENTS.md:0-0
Timestamp: 2025-12-01T18:18:45.551Z
Learning: Applies to src/app/src/**/*.test.{ts,tsx} : Import mocking utilities from 'vitest' (describe, it, expect, vi, beforeEach, afterEach) for test files

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/**/*.test.{ts,tsx,js} : Organize tests with nested `describe()` and `it()` blocks to structure test suites logically

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/**/*.test.{ts,tsx,js} : Call `vi.resetAllMocks()` in `afterEach()` hook to prevent test pollution

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/**/*.test.{ts,tsx,js} : Mock minimally - only mock external dependencies (APIs, databases), not code under test

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:18:45.551Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/app/AGENTS.md:0-0
Timestamp: 2025-12-01T18:18:45.551Z
Learning: Applies to src/app/src/**/*.test.{ts,tsx} : Mock the 'app/utils/api' module and callApi function in tests using vi.mock()

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/**/*.test.{ts,tsx,js} : Use Vitest's mocking utilities (`vi.mock`, `vi.fn`, `vi.spyOn`) for mocking in tests

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/**/*.test.{ts,tsx,js} : Never use `.only()` or `.skip()` in committed Vitest test code

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
📚 Learning: 2025-10-06T03:43:01.653Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:43:01.653Z
Learning: Applies to test/**/*.{ts,tsx,js,jsx} : Follow Jest best practices using describe and it blocks in tests

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/**/*.test.{ts,tsx,js} : Prefer shallow mocking over deep mocking in Vitest tests

Applied to files:

  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts
  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
📚 Learning: 2025-12-01T18:18:45.551Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/app/AGENTS.md:0-0
Timestamp: 2025-12-01T18:18:45.551Z
Learning: Applies to src/app/src/**/*.test.{ts,tsx} : Use testing-library/react (render, screen, fireEvent) for component testing instead of Jest utilities

Applied to files:

  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-12-04T23:54:13.885Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T23:54:13.885Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use Vitest for all tests in both backend tests in `test/` and frontend tests in `src/app/`

Applied to files:

  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx
  • src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
📚 Learning: 2025-12-01T18:18:45.551Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/app/AGENTS.md:0-0
Timestamp: 2025-12-01T18:18:45.551Z
Learning: Applies to src/app/src/store/**/*.{ts,tsx} : Use Zustand for state management, not Redux

Applied to files:

  • src/app/src/pages/model-audit/components/PathSelector.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts
  • src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts
📚 Learning: 2025-12-01T18:18:56.517Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/providers/AGENTS.md:0-0
Timestamp: 2025-12-01T18:18:56.517Z
Learning: Applies to src/providers/test/providers/**/*.test.ts : Mock API responses in provider tests and do not call real APIs

Applied to files:

  • src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx
  • src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts
  • src/app/src/pages/model-audit/components/PathSelector.test.tsx
📚 Learning: 2025-12-01T18:18:45.551Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/app/AGENTS.md:0-0
Timestamp: 2025-12-01T18:18:45.551Z
Learning: Applies to src/app/src/components/**/*.{ts,tsx} : Use icons from mui/icons-material for UI icons

Applied to files:

  • src/app/src/pages/model-audit/components/ConfigurationTab.tsx
📚 Learning: 2025-12-01T18:19:09.570Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2025-12-01T18:19:09.570Z
Learning: Applies to test/**/*.test.{ts,tsx,js} : Test entire objects with `expect(result).toEqual({...})` rather than individual fields

Applied to files:

  • src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx
📚 Learning: 2025-11-29T00:26:26.078Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/server/AGENTS.md:0-0
Timestamp: 2025-11-29T00:26:26.078Z
Learning: Applies to src/server/**/*.ts : Use Drizzle ORM for database access with the schema from src/database/schema.ts - import `db` and use query methods like `db.select().from(table).where()`

Applied to files:

  • src/models/modelAudit.ts
📚 Learning: 2025-10-06T03:43:01.653Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:43:01.653Z
Learning: Use Drizzle ORM for database operations

Applied to files:

  • src/models/modelAudit.ts
📚 Learning: 2025-11-29T00:25:00.914Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: drizzle/AGENTS.md:0-0
Timestamp: 2025-11-29T00:25:00.914Z
Learning: Applies to drizzle/test/**/*.{js,ts,test.ts,spec.ts} : Use in-memory database configuration in test files to avoid database side effects

Applied to files:

  • src/models/modelAudit.ts
📚 Learning: 2025-07-18T17:25:57.700Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: .cursor/rules/gh-cli-workflow.mdc:0-0
Timestamp: 2025-07-18T17:25:57.700Z
Learning: Applies to **/*.{ts,tsx} : Prefer not to introduce new TypeScript types; use existing interfaces whenever possible

Applied to files:

  • src/app/src/pages/model-audit/ModelAudit.types.ts
📚 Learning: 2025-11-29T00:24:17.021Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: src/redteam/CLAUDE.md:0-0
Timestamp: 2025-11-29T00:24:17.021Z
Learning: Applies to src/redteam/**/*agent*.{ts,tsx,js,jsx} : Maintain clear agent interface definitions and usage patterns

Applied to files:

  • src/app/src/pages/model-audit/ModelAudit.types.ts
📚 Learning: 2025-12-04T23:54:13.885Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T23:54:13.885Z
Learning: Applies to src/app/**/*.{ts,tsx} : Use `useCallback` when creating a stable function reference that accepts arguments and will be called later in React hooks

Applied to files:

  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
📚 Learning: 2025-10-06T03:43:01.653Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:43:01.653Z
Learning: Applies to src/app/**/*.{ts,tsx} : In the React app (src/app), use callApi from app/utils/api for all API calls instead of fetch()

Applied to files:

  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
📚 Learning: 2025-12-04T23:54:13.885Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T23:54:13.885Z
Learning: Applies to src/app/**/*.{ts,tsx} : When making API calls from the React app, use the `callApi` function from `app/utils/api` instead of direct `fetch()` calls to ensure proper API base URL handling

Applied to files:

  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
📚 Learning: 2025-12-04T23:54:13.885Z
Learnt from: CR
Repo: promptfoo/promptfoo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T23:54:13.885Z
Learning: Applies to src/app/**/*.{ts,tsx} : Use `useMemo` when computing a value that doesn't accept arguments (a non-callable) in React hooks

Applied to files:

  • src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx
🧬 Code graph analysis (20)
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.ts (1)
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts (1)
  • useModelAuditConfigStore (76-267)
src/app/src/pages/model-audit-result/page.tsx (1)
src/app/src/pages/model-audit-result/ModelAuditResult.tsx (1)
  • ModelAuditResult (35-350)
src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx (2)
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts (1)
  • useModelAuditConfigStore (76-267)
src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts (1)
  • useModelAuditHistoryStore (40-173)
src/app/src/pages/model-audit-latest/page.tsx (1)
src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx (1)
  • ModelAuditResultLatestPage (17-230)
src/app/src/pages/model-audit/components/PathSelector.tsx (1)
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts (1)
  • useModelAuditConfigStore (76-267)
src/server/routes/modelAudit.ts (1)
src/models/modelAudit.ts (1)
  • ModelAudit (38-429)
src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx (2)
src/app/src/components/ErrorBoundary.tsx (1)
  • render (55-133)
src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx (1)
  • ModelAuditResultLatestPage (17-230)
src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx (1)
src/app/src/pages/model-audit-history/ModelAuditHistory.tsx (1)
  • ModelAuditHistory (80-443)
src/app/src/pages/model-audit-history/ModelAuditHistory.tsx (2)
src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts (2)
  • useModelAuditHistoryStore (40-173)
  • HistoricalScan (7-7)
src/app/src/utils/date.ts (1)
  • formatDataGridDate (12-32)
src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx (4)
src/app/src/pages/model-audit/ModelAudit.types.ts (1)
  • HistoricalScan (90-104)
src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx (1)
  • LatestScanSkeleton (120-152)
src/app/src/pages/model-audit/components/ResultsTab.tsx (1)
  • ResultsTab (26-140)
src/app/src/pages/model-audit/components/ScannedFilesDialog.tsx (1)
  • ScannedFilesDialog (26-132)
src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts (1)
src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts (1)
  • useModelAuditHistoryStore (40-173)
src/app/src/pages/model-audit/components/PathSelector.test.tsx (1)
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts (1)
  • useModelAuditConfigStore (76-267)
src/app/src/App.tsx (1)
src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx (1)
  • ModelAuditSetupPage (30-234)
src/app/src/pages/model-audit-result/ModelAuditResult.tsx (5)
src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts (2)
  • useModelAuditHistoryStore (40-173)
  • HistoricalScan (7-7)
src/app/src/pages/model-audit/ModelAudit.types.ts (1)
  • HistoricalScan (90-104)
src/app/src/utils/date.ts (1)
  • formatDataGridDate (12-32)
src/app/src/pages/model-audit/components/ResultsTab.tsx (1)
  • ResultsTab (26-140)
src/app/src/pages/model-audit/components/ScannedFilesDialog.tsx (1)
  • ScannedFilesDialog (26-132)
src/app/src/pages/model-audit/components/ConfigurationTab.tsx (1)
src/app/src/pages/model-audit/components/InstallationGuide.tsx (1)
  • InstallationGuide (30-163)
src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx (2)
src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts (1)
  • useModelAuditHistoryStore (40-173)
src/app/src/pages/model-audit-result/ModelAuditResult.tsx (1)
  • ModelAuditResult (35-350)
src/models/modelAudit.ts (1)
src/database/tables.ts (1)
  • modelAuditsTable (339-389)
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts (2)
src/app/src/pages/model-audit/stores/index.ts (1)
  • useModelAuditConfigStore (1-1)
src/models/modelAudit.ts (1)
  • create (106-164)
src/app/src/pages/model-audit-setup/page.tsx (1)
src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx (1)
  • ModelAuditSetupPage (30-234)
src/app/src/pages/model-audit-history/page.tsx (1)
src/app/src/pages/model-audit-history/ModelAuditHistory.tsx (1)
  • ModelAuditHistory (80-443)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: security-scan
  • GitHub Check: Share Test
  • GitHub Check: Redteam (Production API)
  • GitHub Check: Redteam (Staging API)
  • GitHub Check: webui tests
  • GitHub Check: Test on Node 24.x and ubuntu-latest
  • GitHub Check: Test on Node 24.x and windows-latest
  • GitHub Check: Build Docs
  • GitHub Check: Test on Node 22.x and macOS-latest
  • GitHub Check: Test on Node 22.x and windows-latest
  • GitHub Check: Test on Node 20.x and windows-latest
  • GitHub Check: Test on Node 20.x and ubuntu-latest
  • GitHub Check: Test on Node 20.x and macOS-latest
  • GitHub Check: Test on Node 22.x and ubuntu-latest
  • GitHub Check: Build on Node 24.x
  • GitHub Check: Build on Node 22.x
  • GitHub Check: Build on Node 20.x
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (39)
src/app/src/components/Navigation.tsx (1)

148-168: Create dropdown wiring for Model Audit setup looks consistent

Including /model-audit/setup in isActive and adding the corresponding menu item aligns with the new Model Audit setup route and should keep highlighting behavior consistent with Eval/Red Team entries.

src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.ts (4)

1-41: LGTM! Well-structured test setup.

The test file correctly imports vitest utilities and @testing-library/react, mocks @app/utils/api as required by the coding guidelines, and properly resets store state between tests. The createMockScan helper provides consistent test data.


43-145: Comprehensive test coverage for fetchHistoricalScans.

Tests properly cover success, error handling, abort scenarios, and request parameter construction (search query, pagination). The async patterns with act and waitFor are used correctly.


147-196: Good coverage for fetchScanById edge cases.

Tests correctly verify the three main code paths: successful fetch, 404 response returning null, and abort handling.


245-322: LGTM! Thorough pagination and filtering tests.

Tests properly verify that setPageSize, setSortModel, and setSearchQuery reset currentPage to 0, while setCurrentPage only updates the page. The resetFilters test comprehensively verifies all defaults are restored.

src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsx (2)

1-27: LGTM! Proper test setup with appropriate mocks.

The test file correctly mocks @app/utils/api as per coding guidelines and uses shallow mocks for child components (ResultsTab, ScannedFilesDialog, LatestScanSkeleton), which is a good testing practice.


49-167: LGTM! Comprehensive component state testing.

Tests cover all key states: loading, empty, data loaded, error, and navigation. The test at line 152-167 properly verifies the fallback title behavior when scan name is null.

src/app/src/pages/model-audit-result/ModelAuditResult.test.tsx (2)

1-98: LGTM! Well-structured test setup with route parameter support.

The test file properly mocks the store module and useNavigate, and uses Routes with Route to correctly simulate route parameters. The renderComponent helper allows testing with different scan IDs.


237-254: LGTM! Breadcrumb navigation test.

The test correctly verifies the presence of breadcrumb links for navigation hierarchy.

src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts (5)

1-49: LGTM! Well-structured Zustand store with proper type exports.

The store correctly uses callApi from @app/utils/api as required by coding guidelines, follows Zustand patterns, and provides clean type exports. Initial state values are sensible defaults.


51-94: LGTM! Robust fetch implementation with abort handling.

The fetchHistoricalScans action properly handles the AbortError case without setting error state, builds query parameters correctly, and provides clear error messages.


96-112: LGTM! Clean fetch-by-ID implementation.

The action correctly handles 404 by returning null (not an error), treats AbortError as a non-error path, and re-throws actual errors for caller handling.


114-147: LGTM! Well-implemented optimistic delete with rollback.

The optimistic update pattern is correctly implemented: previous state is captured, UI updates immediately, and on failure, the previous state is restored. The error is both stored in state and re-thrown for caller handling.


149-173: LGTM! Proper pagination reset behavior.

The setters correctly reset currentPage to 0 when page size, sort, or search changes, which is the expected UX behavior to avoid showing an invalid page after filter changes.

src/app/src/pages/model-audit-history/ModelAuditHistory.test.tsx (2)

1-79: LGTM! Well-structured test setup.

The test file correctly mocks the store module and navigation, with a comprehensive getDefaultHistoryState helper that provides all store properties needed by the component.


171-196: LGTM! Good coverage of empty state and status rendering.

Tests correctly verify the empty state messaging and status chip display for different scan states.

src/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsx (1)

1-220: LGTM! Comprehensive test coverage with proper mocking.

The test suite follows best practices:

  • Uses Vitest and React Testing Library correctly
  • Properly mocks external dependencies (stores, child components, router)
  • Provides default state factories for maintainability
  • Resets mocks in beforeEach to prevent test pollution
  • Covers key behaviors: installation checks, status display, error handling, and conditional rendering
src/app/src/pages/model-audit/components/PathSelector.test.tsx (1)

5-26: LGTM! Store migration handled correctly.

The test properly migrates from the old monolithic store to the new useModelAuditConfigStore, updating all mock setups and references consistently.

src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx (1)

1-152: LGTM! Clean skeleton implementations.

The skeleton components provide appropriate loading states with responsive layouts and follow Material-UI conventions.

src/server/routes/modelAudit.ts (1)

426-430: Good parameter validation with proper clamping.

The limit is correctly bounded to [1, 100] and offset to [0, ∞), preventing invalid pagination parameters. Default values are sensible.

src/app/src/App.tsx (1)

22-78: LGTM! Route structure correctly handles specificity.

The route ordering prevents conflicts:

  • Exact matches (/model-audit, /model-audit/setup, /model-audit/history) are defined before parameterized routes
  • /model-audit/:id won't accidentally match "setup" or "history" because those are defined earlier
  • Both /model-audit/history/:id and /model-audit/:id point to ModelAuditResultPage, providing flexible access to results
src/app/src/pages/model-audit/stores/index.ts (1)

1-5: LGTM! Clean barrel export.

Consolidates store exports into a single entry point, following standard patterns for module organization.

src/models/modelAudit.ts (1)

255-294: Good parameter validation and SQL injection protection.

The implementation correctly:

  • Validates sortField and defaults to createdAt for unknown values
  • Uses drizzle-orm's parameterized queries to prevent SQL injection
  • Applies proper pagination with limit/offset
src/app/src/pages/model-audit/components/InstallationGuide.tsx (1)

30-162: Well-structured installation guide with good UX.

The component provides clear installation instructions with:

  • Copy-to-clipboard functionality with visual feedback
  • Comprehensive troubleshooting steps
  • Helpful external documentation links with proper rel="noopener" security
src/app/src/pages/model-audit/components/ConfigurationTab.tsx (1)

1-145: LGTM!

The component properly extends the props interface with optional error field and onRetryInstallationCheck callback. The conditional rendering logic at lines 133-141 correctly shows the InstallationGuide only when installation has failed and a retry handler is available. Clean separation of concerns with presentational component receiving state from parent.

src/app/src/pages/model-audit-history/ModelAuditHistory.tsx (3)

155-288: LGTM!

The columns definition uses useMemo with an empty dependency array, which is correct here since column definitions are static and the event handlers use stable state setters from useState. The per-column renderers properly handle null/undefined values and display appropriate fallbacks.


318-415: LGTM!

DataGrid is properly configured for server-side pagination and sorting. The custom slots for loading and no-rows overlays provide good UX with appropriate messaging for error vs empty states. Row click navigation and delete action with stopPropagation are correctly implemented.


102-106: Effect re-triggers correctly on pagination/sort changes.

The useEffect dependency array correctly includes pageSize, currentPage, and sortModel, which trigger re-runs when pagination or sorting changes. The fetchHistoricalScans function reads the current state via get() inside the store (line 56 of useModelAuditHistoryStore.ts), so it always operates with fresh values. The abort signal is properly managed for cleanup. The store correctly uses callApi for API calls rather than direct fetch calls.

src/app/src/pages/model-audit-result/ModelAuditResult.tsx (2)

48-82: LGTM!

The effect correctly manages async data fetching with proper abort handling. The AbortController is created and aborted on cleanup, and AbortError is properly handled as a non-error case. Error and loading states are managed appropriately.


101-114: LGTM!

The download handler correctly creates a blob from JSON, triggers a download via a temporary anchor element, and properly revokes the object URL to prevent memory leaks. The useCallback dependency on scan ensures the handler updates when scan data changes.

src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx (2)

61-111: LGTM!

The handleScan callback is well-structured with proper error handling, correctly uses callApi from @app/utils/api per coding guidelines, and has a complete dependency array. The navigation to result page after successful persisted scan is a good UX improvement.


56-59: LGTM!

The effect correctly rehydrates persisted store state and checks installation status on mount. Including checkInstallation in the dependency array is appropriate for safety, though the function reference should be stable from Zustand.

src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx (2)

23-62: LGTM!

The effect correctly fetches the latest scan on mount with proper abort handling. The empty dependency array is appropriate since this should only run once on component mount. Error states and abort errors are handled correctly.


112-155: LGTM!

The empty state provides helpful onboarding with clear messaging about what Model Audit does and a prominent CTA to run the first scan. Good UX for new users.

src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts (5)

1-14: LGTM!

Clean imports following the coding guidelines: uses callApi from @app/utils/api for API calls and Zustand for state management as per learnings. Type re-exports provide convenient access for consumers.


16-64: LGTM!

The state interface is well-organized with clear categorization of state fields and actions. Good separation between persisted state (recentScans, scanOptions) and transient state (isScanning, scanResults, etc.).


66-74: LGTM!

Good use of module-scoped singleton for request deduplication in checkInstallationPromise. The pattern prevents duplicate concurrent API calls effectively.


164-216: LGTM!

Excellent implementation of the checkInstallation action with proper request deduplication via the singleton promise pattern. The finally block correctly clears the promise to allow future checks, and error handling is comprehensive.


236-264: Migration logic is well-implemented.

The migration correctly handles transitioning from the legacy model-audit-store to the new model-audit-config-store. Good defensive coding with try-catch and proper cleanup of the old store key after successful migration.

One minor note: the type assertions on lines 257 and 263 are necessary due to Zustand's migrate function typing.

Comment on lines 218 to 224
paths={((latestScan.metadata?.originalPaths as string[] | undefined) ?? []).map(
(p: string) => ({
path: p,
type: 'file' as const,
name: p.split('/').pop() || p,
}),
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same issue: all paths hardcoded as 'file' type.

Same issue as in ModelAuditResult.tsx - consider detecting type based on path pattern for correct icon rendering.

             paths={((latestScan.metadata?.originalPaths as string[] | undefined) ?? []).map(
               (p: string) => ({
                 path: p,
-                type: 'file' as const,
+                type: p.endsWith('/') ? ('directory' as const) : ('file' as const),
                 name: p.split('/').pop() || p,
               }),
             )}
🤖 Prompt for AI Agents
In src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx around
lines 218 to 224, the paths array maps every entry to type: 'file' which forces
the wrong icon; update the mapping to detect the type instead (reuse the same
detection logic from ModelAuditResult.tsx) — e.g., if the path string
endsWith('/') or otherwise matches a directory pattern mark type 'directory' (or
'dir') else mark 'file', or if you have extension-based rules use those; ensure
the resulting type matches the component's expected union so icons render
correctly.

Comment on lines 314 to 320
paths={((scan.metadata?.originalPaths as string[] | undefined) ?? []).map(
(p: string) => ({
path: p,
type: 'file' as const,
name: p.split('/').pop() || p,
}),
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

All paths hardcoded as 'file' type may show incorrect icons for directories.

The mapping assumes all originalPaths are files, but directories could be in this list. The ScannedFilesDialog uses path.type to render different icons (FolderIcon vs FileIcon). Consider detecting type based on path ending with / or storing the type in metadata.

             paths={((scan.metadata?.originalPaths as string[] | undefined) ?? []).map(
               (p: string) => ({
                 path: p,
-                type: 'file' as const,
+                type: p.endsWith('/') ? ('directory' as const) : ('file' as const),
                 name: p.split('/').pop() || p,
               }),
             )}
🤖 Prompt for AI Agents
In src/app/src/pages/model-audit-result/ModelAuditResult.tsx around lines 314 to
320, the mapping unconditionally sets type: 'file' for every originalPath which
causes directories to render with file icons; change the mapping to prefer any
explicit type on the metadata (e.g., scan.metadata?.originalPathTypes or
similar) and if not present detect directories by path string (treat paths that
end with '/' as directory) — set type to 'directory' for those and 'file'
otherwise, and normalize the name by trimming a trailing '/' for directory names
so icons and labels render correctly.

Comment on lines 187 to 200
<ConfigurationTab
paths={paths}
isScanning={isScanning}
onAddPath={(path) => {
setPaths([...paths, path]);
}}
onRemovePath={handleRemovePath}
onShowOptions={() => setShowOptionsDialog(true)}
onScan={handleScan}
error={error}
onClearError={() => setError(null)}
currentWorkingDir={installationStatus.cwd || ''}
installationStatus={installationStatus}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing onRetryInstallationCheck prop prevents InstallationGuide from rendering.

ConfigurationTab conditionally renders InstallationGuide when isNotInstalled && onRetryInstallationCheck. Since onRetryInstallationCheck is not passed here, users won't see the installation guide even when ModelAudit is not installed. Consider passing the retry handler.

             currentWorkingDir={installationStatus.cwd || ''}
             installationStatus={installationStatus}
+            onRetryInstallationCheck={checkInstallation}
           />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ConfigurationTab
paths={paths}
isScanning={isScanning}
onAddPath={(path) => {
setPaths([...paths, path]);
}}
onRemovePath={handleRemovePath}
onShowOptions={() => setShowOptionsDialog(true)}
onScan={handleScan}
error={error}
onClearError={() => setError(null)}
currentWorkingDir={installationStatus.cwd || ''}
installationStatus={installationStatus}
/>
<ConfigurationTab
paths={paths}
isScanning={isScanning}
onAddPath={(path) => {
setPaths([...paths, path]);
}}
onRemovePath={handleRemovePath}
onShowOptions={() => setShowOptionsDialog(true)}
onScan={handleScan}
error={error}
onClearError={() => setError(null)}
currentWorkingDir={installationStatus.cwd || ''}
installationStatus={installationStatus}
onRetryInstallationCheck={checkInstallation}
/>
🤖 Prompt for AI Agents
In src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx around lines 187
to 200, the ConfigurationTab is missing the onRetryInstallationCheck prop so
InstallationGuide never renders when isNotInstalled; add an
onRetryInstallationCheck prop and wire it to the component method that re-checks
installation (e.g., an existing checkInstallationStatus or
retryInstallationCheck function) — if no such function exists, implement a small
handler that triggers the installation status check and pass it as
onRetryInstallationCheck={retryInstallationCheck} to ConfigurationTab.

mldangelo and others added 3 commits December 6, 2025 00:11
- Fix path type detection to use trailing slash heuristic instead of
  hardcoded 'file' type in ModelAuditResult.tsx and
  ModelAuditResultLatestPage.tsx
- Add missing onRetryInstallationCheck prop to ConfigurationTab in
  ModelAuditSetupPage.tsx to enable InstallationGuide rendering
- Add setTimeout cleanup on unmount in InstallationGuide.tsx to prevent
  memory leaks
- Use formatDataGridDate for consistent date formatting across pages
- Fix delete error test to verify rollback behavior
- Apply formatting fixes from linter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix type error in ModelAuditResultLatestPage.test.tsx where null
  was not assignable to string (use proper type assertion)
- Fix type error in useModelAuditHistoryStore.test.ts where message
  property did not exist on type never (use undefined type and
  non-null assertion)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the model-audit feature from a monolithic single-page architecture to a modern multi-page application with specialized state management stores. The changes improve code organization, add comprehensive test coverage (135 tests), and introduce several UX improvements including skeleton loaders, optimistic updates, and an installation guide.

Key Changes

  • Replaced single monolithic store with two specialized stores: useModelAuditConfigStore (configuration/recent scans) and useModelAuditHistoryStore (server-side paginated history)
  • Migrated from tab-based UI to separate pages: Setup, History, Result, and Latest Scan
  • Added server-side pagination, sorting, and search to the /model-audit/scans endpoint
  • Implemented localStorage migration to preserve user data from the old store format

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/server/routes/modelAudit.ts Enhanced /scans endpoint with pagination/search, added /scans/latest endpoint
src/models/modelAudit.ts Added getMany() and count() methods with pagination, sorting, and search support
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts New store for scan configuration, recent scans, and installation status with migration logic
src/app/src/pages/model-audit/stores/useModelAuditHistoryStore.ts New store for historical scans with server-side pagination and optimistic deletes
src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx New setup page replacing the Configuration tab
src/app/src/pages/model-audit-result/ModelAuditResult.tsx New result detail page for viewing individual scans
src/app/src/pages/model-audit-history/ModelAuditHistory.tsx New history page with DataGrid for browsing all scans
src/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsx New landing page showing the most recent scan
src/app/src/pages/model-audit/components/InstallationGuide.tsx New component with CLI installation instructions and troubleshooting
src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsx New skeleton loaders for improved loading UX
src/app/src/App.tsx Updated routing to support new multi-page structure
src/app/src/components/Navigation.tsx Added Model Audit to the Create dropdown menu
Comments suppressed due to low confidence (2)

src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx:59

  • The dependency array includes checkInstallation which is a function from the store. If this function's reference changes on every render, it will cause an infinite re-render loop.

Consider either:

  1. Removing checkInstallation from the dependency array and using a ref
  2. Ensuring checkInstallation is wrapped in useCallback in the store
  3. Using an empty dependency array with an ESLint disable comment if this should only run once
    src/app/src/pages/model-audit/stores/useModelAuditConfigStore.ts:259
  • The console.info and console.warn statements in production code can expose implementation details and potentially sensitive information to end users.

Consider using a proper logging library that can be configured to suppress logs in production, or guard these with environment checks.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +306 to +312
query = query.where(
or(
like(modelAuditsTable.name, `%${search}%`),
like(modelAuditsTable.modelPath, `%${search}%`),
like(modelAuditsTable.id, `%${search}%`),
),
) as typeof query;
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential SQL injection vulnerability: The search parameter is directly interpolated into the SQL LIKE clause without sanitization. The same issue exists in the count method.

Consider using parameterized queries or sanitizing the search input to prevent SQL injection attacks.

Copilot uses AI. Check for mistakes.
const abortController = new AbortController();
fetchHistoricalScans(abortController.signal);
return () => abortController.abort();
}, [fetchHistoricalScans, pageSize, currentPage, sortModel]);
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useEffect dependency array includes fetchHistoricalScans which is a store function. If this reference changes, it will trigger unnecessary fetches. Additionally, the effect depends on pageSize, currentPage, and sortModel but these changes will already trigger fetchHistoricalScans through the store's internal logic.

Consider using an empty dependency array or ensuring the store function is stable. The current implementation may cause unnecessary refetches.

Suggested change
}, [fetchHistoricalScans, pageSize, currentPage, sortModel]);
}, []);

Copilot uses AI. Check for mistakes.
Comment on lines +269 to +273
query = query.where(
or(
like(modelAuditsTable.name, `%${search}%`),
like(modelAuditsTable.modelPath, `%${search}%`),
like(modelAuditsTable.id, `%${search}%`),
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential SQL injection vulnerability: The search parameter is directly interpolated into the SQL LIKE clause without sanitization. Although the drizzle-orm library may provide some protection, it's safer to explicitly parameterize the search term.

Consider using parameterized queries or sanitizing the search input to prevent SQL injection attacks.

Suggested change
query = query.where(
or(
like(modelAuditsTable.name, `%${search}%`),
like(modelAuditsTable.modelPath, `%${search}%`),
like(modelAuditsTable.id, `%${search}%`),
const searchPattern = `%${search}%`;
query = query.where(
or(
like(modelAuditsTable.name, searchPattern),
like(modelAuditsTable.modelPath, searchPattern),
like(modelAuditsTable.id, searchPattern),

Copilot uses AI. Check for mistakes.
Comment on lines 278 to 282
// Determine the sort column
const sortColumn =
sortField === 'name'
? modelAuditsTable.name
: sortField === 'modelPath'
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sortField parameter allows arbitrary column selection without validation. An attacker could specify non-existent or unintended columns.

Consider validating the sortField against an allowlist of permitted columns (e.g., ['name', 'modelPath', 'createdAt']) before using it in the query.

Suggested change
// Determine the sort column
const sortColumn =
sortField === 'name'
? modelAuditsTable.name
: sortField === 'modelPath'
// Determine the sort column with allowlist validation
const allowedSortFields = ['name', 'modelPath', 'createdAt'];
const validatedSortField = allowedSortFields.includes(sortField) ? sortField : 'createdAt';
const sortColumn =
validatedSortField === 'name'
? modelAuditsTable.name
: validatedSortField === 'modelPath'

Copilot uses AI. Check for mistakes.
Comment on lines 426 to 430
const limit = Math.min(Math.max(1, parseInt(req.query.limit as string) || 100), 100);
const offset = Math.max(0, parseInt(req.query.offset as string) || 0);
const sort = (req.query.sort as string) || 'createdAt';
const order = (req.query.order as string) || 'desc';
const search = req.query.search as string | undefined;
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation for sort and order parameters. The route accepts these as query parameters but doesn't validate them against permitted values before passing to the model layer.

Add validation to ensure sort is one of the allowed fields (e.g., 'name', 'modelPath', 'createdAt') and order is either 'asc' or 'desc'.

Copilot uses AI. Check for mistakes.
Comment on lines 94 to 99
<code>{installCommand}</code>
<Tooltip title={copied ? 'Copied!' : 'Copy command'}>
<IconButton size="small" onClick={handleCopy}>
{copied ? <CheckCircleIcon color="success" /> : <ContentCopyIcon />}
</IconButton>
</Tooltip>
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing accessibility: The code example blocks in the installation guide should have proper ARIA labels or role attributes to improve screen reader support.

Add role="code" or wrap the code in a properly labeled container for better accessibility.

Suggested change
<code>{installCommand}</code>
<Tooltip title={copied ? 'Copied!' : 'Copy command'}>
<IconButton size="small" onClick={handleCopy}>
{copied ? <CheckCircleIcon color="success" /> : <ContentCopyIcon />}
</IconButton>
</Tooltip>
<Box
role="region"
aria-label="Installation command"
sx={{ display: 'flex', alignItems: 'center', width: '100%' }}
>
<code>{installCommand}</code>
<Tooltip title={copied ? 'Copied!' : 'Copy command'}>
<IconButton size="small" onClick={handleCopy}>
{copied ? <CheckCircleIcon color="success" /> : <ContentCopyIcon />}
</IconButton>
</Tooltip>
</Box>

Copilot uses AI. Check for mistakes.
mldangelo and others added 6 commits December 6, 2025 00:33
- Add input validation for sort/order params in modelAudit route
  with explicit allowlist preventing invalid values
- Add explicit TypeScript union types for sortField and sortOrder
  parameters in model layer
- Add ARIA accessibility attributes to InstallationGuide component
  (aria-label on copy button, role="region" on command container)
- Add JSDoc comments explaining Drizzle ORM parameterized query safety

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…racters

Scan IDs contain colons from ISO timestamps (e.g. scan-abc-2025-12-06T10:30:45)
which caused 404 errors when fetching individual scans. This fix properly
encodes the ID in API URLs using encodeURIComponent().

Also adds a comment to clarify the route order requirement for /scans/latest.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Colons in scan IDs (from ISO timestamps like "scan-abc-2025-12-06T10:30:45")
don't require URL encoding since they're valid in URL paths per RFC 3986.
The encodeURIComponent() was causing issues when the browser/fetch would
double-encode the percent signs (%3A -> %253A), making database lookups fail.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
… pages

The history page used DB column values (totalChecks, passedChecks, failedChecks)
while the detail page used nested results JSON (results.total_checks, etc.).
These could differ if the scanner outputs both snake_case and camelCase fields.

Now ResultsTab accepts optional DB column value props that take priority over
nested results values, ensuring consistent display across all model audit pages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Route structure now matches eval:
- /model-audit → Latest scan (like /eval)
- /model-audits → List/history (like /evals)
- /model-audit/:id → Specific scan (like /eval/:evalId)
- /model-audit/setup → Setup page

Changes:
- Rename /model-audit/history to /model-audits
- Add redirect for legacy /model-audit/history route
- Add centralized route constants in src/app/src/constants/routes.ts
- Update all navigation links to use new routes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fix React StrictMode race condition where aborted API requests would
cause false 'Scan not found' errors. Now properly re-throws AbortError
from fetchScanById and checks signal.aborted before updating state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants