-
-
Notifications
You must be signed in to change notification settings - Fork 809
chore(webui): refactor model-audit store architecture #6532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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>
|
⏩ PR identified as a refactor (fd07fa3) View output ↗ View check history
|
|
Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughWalkthroughThis 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
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this 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 availableThe type layering here (type-only imports,
ScanResultextendingModelAuditScanResults, stricterScanIssue.details, and newRecentScan/InstallationStatus/HistoricalScan) lines up well with the new config/history stores and the usages in the pages.If
@promptfoo/types/modelAuditalready exposes a history/record type analogous toHistoricalScan, 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-rendersSwitching to
useModelAuditConfigStoreand wiringremoveRecentPath(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
PathSelectoron 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/persistenceThe 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()inbeforeEachforvi.resetAllMocks()in anafterEach(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?.()orlocalStorage.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 }withoutsuccess: falseHowever, 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 }usingres.json({ success: true, data: ... })orres.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
getManyandcount. 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
setTimeouton 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
useRefanduseEffectto 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
formatDataGridDatefrom@app/utils/date, but this component usestoLocaleString(). 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
📒 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 usefetch()directly. ALWAYS usecallApi()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.tsxsrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.tssrc/app/src/pages/model-audit-result/page.tsxsrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/page.tsxsrc/app/src/pages/model-audit/components/PathSelector.tsxsrc/app/src/pages/model-audit/stores/index.tssrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.tsxsrc/app/src/pages/model-audit/components/InstallationGuide.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/app/src/App.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.tsxsrc/app/src/pages/model-audit/components/ConfigurationTab.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.tssrc/app/src/components/Navigation.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.tssrc/app/src/pages/model-audit-setup/page.tsxsrc/app/src/pages/model-audit-history/page.tsxsrc/app/src/pages/model-audit/ModelAudit.types.tssrc/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 thecallApifunction from@app/utils/apiinstead of directfetch()calls to ensure proper API base URL handling
UseuseMemowhen computing a value that doesn't accept arguments (a non-callable) in React hooks
UseuseCallbackwhen 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.tsxsrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.tssrc/app/src/pages/model-audit-result/page.tsxsrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/page.tsxsrc/app/src/pages/model-audit/components/PathSelector.tsxsrc/app/src/pages/model-audit/stores/index.tssrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.tsxsrc/app/src/pages/model-audit/components/InstallationGuide.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/app/src/App.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.tsxsrc/app/src/pages/model-audit/components/ConfigurationTab.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.tssrc/app/src/components/Navigation.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.tssrc/app/src/pages/model-audit-setup/page.tsxsrc/app/src/pages/model-audit-history/page.tsxsrc/app/src/pages/model-audit/ModelAudit.types.tssrc/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.tsxsrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.tssrc/app/src/pages/model-audit-result/page.tsxsrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/page.tsxsrc/app/src/pages/model-audit/components/PathSelector.tsxsrc/app/src/pages/model-audit/stores/index.tssrc/server/routes/modelAudit.tssrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.tsxsrc/app/src/pages/model-audit/components/InstallationGuide.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/app/src/App.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.tsxsrc/app/src/pages/model-audit/components/ConfigurationTab.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.tssrc/app/src/components/Navigation.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.test.tsxsrc/models/modelAudit.tssrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.tssrc/app/src/pages/model-audit-setup/page.tsxsrc/app/src/pages/model-audit-history/page.tsxsrc/app/src/pages/model-audit/ModelAudit.types.tssrc/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 thesanitizeObjectfunction from./util/sanitizerwhich sanitizes recursively up to 4 levels deep
Files:
src/app/src/pages/model-audit/components/ModelAuditSkeleton.tsxsrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.tssrc/app/src/pages/model-audit-result/page.tsxsrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/page.tsxsrc/app/src/pages/model-audit/components/PathSelector.tsxsrc/app/src/pages/model-audit/stores/index.tssrc/server/routes/modelAudit.tssrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.tsxsrc/app/src/pages/model-audit/components/InstallationGuide.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/app/src/App.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.tsxsrc/app/src/pages/model-audit/components/ConfigurationTab.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.tssrc/app/src/components/Navigation.tsxsrc/app/src/pages/model-audit-result/ModelAuditResult.test.tsxsrc/models/modelAudit.tssrc/app/src/pages/model-audit/stores/useModelAuditConfigStore.tssrc/app/src/pages/model-audit-setup/page.tsxsrc/app/src/pages/model-audit-history/page.tsxsrc/app/src/pages/model-audit/ModelAudit.types.tssrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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 insrc/app/
Files:
src/app/src/pages/model-audit/stores/useModelAuditConfigStore.test.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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 - importdband use query methods likedb.select().from(table).where()
Use Socket.io for WebSocket real-time communication - usesocket.join()for subscribing to namespaces andio.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 }usingres.json({ success: true, data: ... })orres.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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tssrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/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.tssrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tssrc/app/src/pages/model-audit-setup/ModelAuditSetupPage.test.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/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.tsxsrc/app/src/pages/model-audit-latest/ModelAuditResultLatestPage.test.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/app/src/pages/model-audit/components/PathSelector.test.tsxsrc/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.tsxsrc/app/src/pages/model-audit-history/ModelAuditHistory.test.tsxsrc/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.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.tssrc/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.tsxsrc/app/src/pages/model-audit/stores/useModelAuditHistoryStore.test.tssrc/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 consistentIncluding
/model-audit/setupinisActiveand 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/apias required by the coding guidelines, and properly resets store state between tests. ThecreateMockScanhelper provides consistent test data.
43-145: Comprehensive test coverage forfetchHistoricalScans.Tests properly cover success, error handling, abort scenarios, and request parameter construction (search query, pagination). The async patterns with
actandwaitForare used correctly.
147-196: Good coverage forfetchScanByIdedge 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, andsetSearchQueryresetcurrentPageto 0, whilesetCurrentPageonly updates the page. TheresetFilterstest 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/apias 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 usesRouteswithRouteto correctly simulate route parameters. TherenderComponenthelper 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
callApifrom@app/utils/apias 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
fetchHistoricalScansaction 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
currentPageto 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
getDefaultHistoryStatehelper 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
beforeEachto 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/:idwon't accidentally match "setup" or "history" because those are defined earlier- Both
/model-audit/history/:idand/model-audit/:idpoint toModelAuditResultPage, providing flexible access to resultssrc/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
sortFieldand defaults tocreatedAtfor 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"securitysrc/app/src/pages/model-audit/components/ConfigurationTab.tsx (1)
1-145: LGTM!The component properly extends the props interface with optional
errorfield andonRetryInstallationCheckcallback. The conditional rendering logic at lines 133-141 correctly shows theInstallationGuideonly 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
useMemowith an empty dependency array, which is correct here since column definitions are static and the event handlers use stable state setters fromuseState. 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, andsortModel, which trigger re-runs when pagination or sorting changes. ThefetchHistoricalScansfunction reads the current state viaget()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 usescallApifor API calls rather than directfetchcalls.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
useCallbackdependency onscanensures the handler updates when scan data changes.src/app/src/pages/model-audit-setup/ModelAuditSetupPage.tsx (2)
61-111: LGTM!The
handleScancallback is well-structured with proper error handling, correctly usescallApifrom@app/utils/apiper 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
checkInstallationin 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
callApifrom@app/utils/apifor 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
checkInstallationaction 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-storeto the newmodel-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.
| paths={((latestScan.metadata?.originalPaths as string[] | undefined) ?? []).map( | ||
| (p: string) => ({ | ||
| path: p, | ||
| type: 'file' as const, | ||
| name: p.split('/').pop() || p, | ||
| }), | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| paths={((scan.metadata?.originalPaths as string[] | undefined) ?? []).map( | ||
| (p: string) => ({ | ||
| path: p, | ||
| type: 'file' as const, | ||
| name: p.split('/').pop() || p, | ||
| }), | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
- 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>
There was a problem hiding this 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) anduseModelAuditHistoryStore(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/scansendpoint - 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
checkInstallationwhich 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:
- Removing
checkInstallationfrom the dependency array and using a ref - Ensuring
checkInstallationis wrapped inuseCallbackin the store - 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.
| query = query.where( | ||
| or( | ||
| like(modelAuditsTable.name, `%${search}%`), | ||
| like(modelAuditsTable.modelPath, `%${search}%`), | ||
| like(modelAuditsTable.id, `%${search}%`), | ||
| ), | ||
| ) as typeof query; |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| const abortController = new AbortController(); | ||
| fetchHistoricalScans(abortController.signal); | ||
| return () => abortController.abort(); | ||
| }, [fetchHistoricalScans, pageSize, currentPage, sortModel]); |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| }, [fetchHistoricalScans, pageSize, currentPage, sortModel]); | |
| }, []); |
| query = query.where( | ||
| or( | ||
| like(modelAuditsTable.name, `%${search}%`), | ||
| like(modelAuditsTable.modelPath, `%${search}%`), | ||
| like(modelAuditsTable.id, `%${search}%`), |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| 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), |
src/models/modelAudit.ts
Outdated
| // Determine the sort column | ||
| const sortColumn = | ||
| sortField === 'name' | ||
| ? modelAuditsTable.name | ||
| : sortField === 'modelPath' |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| // 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' |
| 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; |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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'.
| <code>{installCommand}</code> | ||
| <Tooltip title={copied ? 'Copied!' : 'Copy command'}> | ||
| <IconButton size="small" onClick={handleCopy}> | ||
| {copied ? <CheckCircleIcon color="success" /> : <ContentCopyIcon />} | ||
| </IconButton> | ||
| </Tooltip> |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| <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> |
- 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>
Summary
Comprehensive refactoring of the Model Audit UI architecture:
Multi-page architecture - Split monolithic component into dedicated pages:
Store architecture - Split into specialized Zustand stores:
Route structure - Now mirrors the eval pattern for consistency
Code quality - Added comprehensive tests, fixed TypeScript errors, addressed review feedback
Changes
Architecture
Stores
Routes (mirrors eval)
Fixes
Test plan
🤖 Generated with Claude Code