fix(ui): Post-merge test fixes for accessibility and async handling - #371
Conversation
Accessibility (WCAG 2.1): - Add proper label associations (htmlFor + id) to all form inputs in ApprovalRulesConfig for proper screen reader support Test Fixes: - Fix ResearchResults clipboard mock to return resolved promise - Wrap async copy operations in act() for proper React testing - All tests now passing: 379/380 (99.7%) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdded reusable AlertBanner and ConfirmDialog components and barrel export; replaced native delete confirm with modal and improved form accessibility; introduced time utilities and adopted them; added notebook runtime health/sync API routes plus a client dashboard page; various UI/test updates and docker-compose env_file changes. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Extracts shared utilities and components to reduce code duplication: - formatTimeAgo: Consolidates 2 implementations (ingestion-queue, SyncStatus) - AlertBanner: Consolidates 3+ error banner patterns - ConfirmDialog: Replaces native confirm() with accessible React component Files created: - pmoves/ui/lib/timeUtils.ts - pmoves/ui/components/common/AlertBanner.tsx - pmoves/ui/components/common/ConfirmDialog.tsx - pmoves/ui/components/common/index.ts Tests updated: - SyncStatus.test.tsx: Updated for new time format behavior - ApprovalRulesConfig.test.tsx: Updated for ConfirmDialog interaction Test results: 380 passed, 1 skipped 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
pmoves/ui/components/jellyfin/SyncStatus.test.tsx (1)
165-182: Consider locale-specific test behavior.The new test uses
toLocaleDateString()to verify the formatted output (Line 180). This could produce different results in different CI environments or developer machines with different locale settings, potentially causing test flakiness.💡 Optional: Make the test locale-independent
Consider one of these approaches:
- Mock
toLocaleDateString()to return a consistent value- Use a regex pattern to verify the format rather than exact string match
- Set a specific locale in the test environment
- const formattedDate = new Date(oldDate.lastSync!).toLocaleDateString(); - expect(screen.getByText(`Last: ${formattedDate}`)).toBeInTheDocument(); + // Verify it shows a date format (not relative time) + expect(screen.getByText(/Last: \d{1,2}\/\d{1,2}\/\d{4}/)).toBeInTheDocument();pmoves/ui/components/ingestion/ApprovalRulesConfig.test.tsx (1)
305-318: Consider more robust button selection in tests.The tests use
querySelectorwith positional selectors (button:last-child,button:first-child) to find the confirm/cancel buttons in the dialog (Lines 315, 341). This approach is fragile and could break if the button order changes or if additional buttons are added to the dialog.🔎 Recommended: Use more semantic selectors
Consider using
getByRoleorgetByTextfor more robust button selection:- const dialogTitle = screen.getByText('Delete Rule'); - const dialogParent = dialogTitle.closest('div[role="dialog"]'); - const confirmButton = dialogParent?.querySelector('button:last-child'); - if (confirmButton) fireEvent.click(confirmButton); + // Get the confirm button by text within the dialog + const confirmButton = screen.getByRole('button', { name: /delete/i }); + fireEvent.click(confirmButton);This would require adding
data-testidattributes or relying on button text, both of which are more stable than DOM position.Also applies to: 331-343
pmoves/ui/lib/timeUtils.ts (1)
13-32: Add handling for invalid dates and future timestamps.The
formatTimeAgofunction doesn't handle edge cases:
- Invalid date strings will result in
Invalid Date, which when passed togetTime()returnsNaN, leading to incorrect "Just now" display- Future dates (where
diffMsis negative) will also incorrectly show "Just now"🔎 Recommended: Add edge case handling
export function formatTimeAgo(dateStr: string | null | undefined): string { if (!dateStr) return 'Never'; const date = new Date(dateStr); + // Handle invalid dates + if (isNaN(date.getTime())) return 'Invalid date'; + const now = new Date(); const diffMs = now.getTime() - date.getTime(); + + // Handle future dates + if (diffMs < 0) return 'In the future'; + const diffMins = Math.floor(diffMs / 60000); if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins}m ago`; const diffHours = Math.floor(diffMins / 60); if (diffHours < 24) return `${diffHours}h ago`; const diffDays = Math.floor(diffHours / 24); if (diffDays < 7) return `${diffDays}d ago`; // For dates older than a week, show the actual date return date.toLocaleDateString(); }pmoves/ui/components/common/ConfirmDialog.tsx (1)
61-63: Remove redundanthandleConfirmwrapper.The
handleConfirmfunction simply forwards toonConfirm()without adding any logic. This can be simplified by callingonConfirmdirectly.🔎 Proposed simplification
- const handleConfirm = () => { - onConfirm(); - }; - return ( <div className="fixed inset-0 z-50 flex items-center justify-center" // ... <button type="button" - onClick={handleConfirm} + onClick={onConfirm} className={`px-4 py-2 rounded text-white focus:outline-none focus:ring-2 focus:ring-offset-2 ${VARIANT_CLASSES[variant]}`} >
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
pmoves/ui/app/dashboard/chat/page.tsxpmoves/ui/app/dashboard/ingestion-queue/page.tsxpmoves/ui/components/common/AlertBanner.tsxpmoves/ui/components/common/ConfirmDialog.tsxpmoves/ui/components/common/index.tspmoves/ui/components/ingestion/ApprovalRulesConfig.test.tsxpmoves/ui/components/ingestion/ApprovalRulesConfig.tsxpmoves/ui/components/jellyfin/SyncStatus.test.tsxpmoves/ui/components/jellyfin/SyncStatus.tsxpmoves/ui/lib/timeUtils.ts
🧰 Additional context used
📓 Path-based instructions (1)
pmoves/ui/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (pmoves/AGENTS.md)
UI updates: run
make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"to lint the Next.js bundle and validate Supabase connectivity; referencepmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Files:
pmoves/ui/lib/timeUtils.tspmoves/ui/components/common/index.tspmoves/ui/components/ingestion/ApprovalRulesConfig.test.tsxpmoves/ui/components/common/AlertBanner.tsxpmoves/ui/components/common/ConfirmDialog.tsxpmoves/ui/app/dashboard/ingestion-queue/page.tsxpmoves/ui/components/jellyfin/SyncStatus.tsxpmoves/ui/app/dashboard/chat/page.tsxpmoves/ui/components/ingestion/ApprovalRulesConfig.tsxpmoves/ui/components/jellyfin/SyncStatus.test.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/AGENTS.md:0-0
Timestamp: 2025-12-07T11:03:53.415Z
Learning: Applies to pmoves/ui/**/*.{js,jsx,ts,tsx} : UI updates: run `make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"` to lint the Next.js bundle and validate Supabase connectivity; reference `pmoves/docs/UI_NOTEBOOK_WORKBENCH.md`
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T12:03:17.291Z
Learning: UI updates: run make -C pmoves notebook-workbench-smoke ARGS='--thread=<uuid>' to lint the Next.js bundle and validate Supabase connectivity; reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T11:02:53.362Z
Learning: When requested to summarize a pull request, provide a short (3–5 bullet) recap highlighting risky areas, test coverage, and any follow-up work; point the author back to the PR template checkboxes if key validations are missing
📚 Learning: 2025-12-07T11:03:53.415Z
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/AGENTS.md:0-0
Timestamp: 2025-12-07T11:03:53.415Z
Learning: Applies to pmoves/ui/**/*.{js,jsx,ts,tsx} : UI updates: run `make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"` to lint the Next.js bundle and validate Supabase connectivity; reference `pmoves/docs/UI_NOTEBOOK_WORKBENCH.md`
Applied to files:
pmoves/ui/components/common/index.ts
🧬 Code graph analysis (7)
pmoves/ui/lib/timeUtils.ts (2)
CATACLYSM_STUDIOS_INC/PMOVES-PROVISIONS/docker-stacks/jellyfin-ai/dashboard/src/App.js (1)
date(17-17)pmoves/services/hi-rag-gateway-v2/web/script.js (1)
now(263-263)
pmoves/ui/components/common/AlertBanner.tsx (1)
pmoves/ui/components/common/index.ts (2)
AlertBannerProps(7-7)AlertBanner(6-6)
pmoves/ui/components/common/ConfirmDialog.tsx (1)
pmoves/ui/components/common/index.ts (2)
ConfirmDialogProps(10-10)ConfirmDialog(9-9)
pmoves/ui/components/jellyfin/SyncStatus.tsx (3)
pmoves/ui/lib/timeUtils.ts (1)
formatTimeAgo(13-32)pmoves/ui/components/common/AlertBanner.tsx (1)
AlertBanner(36-65)pmoves/ui/components/common/index.ts (1)
AlertBanner(6-6)
pmoves/ui/app/dashboard/chat/page.tsx (2)
pmoves/ui/components/common/AlertBanner.tsx (1)
AlertBanner(36-65)pmoves/ui/components/common/index.ts (1)
AlertBanner(6-6)
pmoves/ui/components/ingestion/ApprovalRulesConfig.tsx (2)
pmoves/ui/components/common/ConfirmDialog.tsx (1)
ConfirmDialog(49-114)pmoves/ui/components/common/index.ts (1)
ConfirmDialog(9-9)
pmoves/ui/components/jellyfin/SyncStatus.test.tsx (2)
pmoves/ui/lib/api/jellyfin.ts (1)
JellyfinSyncStatusInfo(66-79)pmoves/ui/components/jellyfin/SyncStatus.tsx (1)
SyncStatus(42-201)
⏰ 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). (1)
- GitHub Check: Analyze (python)
🔇 Additional comments (9)
pmoves/ui/app/dashboard/chat/page.tsx (1)
12-12: LGTM! Clean refactoring to use shared AlertBanner component.The replacement of the inline error banner with the new AlertBanner component improves consistency across the UI. The onDismiss handler properly clears the error state.
Also applies to: 334-336
pmoves/ui/app/dashboard/ingestion-queue/page.tsx (1)
24-25: LGTM! Excellent refactoring to shared utilities.The adoption of
formatTimeAgoandAlertBannereliminates code duplication and ensures consistent UI patterns across the dashboard. The error handling logic remains unchanged.Also applies to: 313-313, 525-525
pmoves/ui/components/jellyfin/SyncStatus.tsx (1)
10-11: LGTM! Consistent adoption of shared utilities.The refactoring aligns with the broader pattern established in this PR. Time formatting and error display are now handled by shared utilities, reducing code duplication and improving maintainability.
Also applies to: 82-82, 142-144
pmoves/ui/components/common/index.ts (1)
1-10: LGTM! Clean barrel export pattern.This provides a centralized import surface for shared UI components, following standard Next.js/React patterns. The inclusion of type exports ensures TypeScript consumers get proper type information.
pmoves/ui/lib/timeUtils.ts (1)
40-59: LGTM! Well-structured duration formatting.The
formatDurationfunction provides comprehensive coverage of time units with clear logic for displaying the most significant units. The implementation correctly handles edge cases and provides readable output.pmoves/ui/components/ingestion/ApprovalRulesConfig.tsx (3)
10-10: Excellent UX improvement with modal confirmation.The replacement of native
window.confirm()withConfirmDialogprovides:
- Better visual consistency with the rest of the UI
- More informative messages (shows the rule name being deleted)
- Proper accessibility attributes via the ConfirmDialog component
- Non-blocking modal pattern that's more user-friendly
The state management for
deleteConfirmis clean and properly tracks both the dialog state and the rule being deleted.Also applies to: 111-115, 189-207
399-400: Outstanding accessibility improvements! 🎉The addition of
htmlForattributes on labels and correspondingidattributes on form inputs ensures proper label associations. This addresses WCAG 2.1 Level A requirements:
- SC 1.3.1 (Info and Relationships): Programmatic label relationships
- SC 4.1.2 (Name, Role, Value): Proper form control identification
These changes enable:
- Screen readers to announce field purposes correctly
- Click-to-focus behavior on labels
- Better form navigation for keyboard and assistive technology users
This aligns perfectly with the PR objectives for accessibility fixes and demonstrates strong attention to inclusive design.
Also applies to: 403-403, 415-416, 419-419, 462-465, 477-480, 490-493, 504-507, 516-519, 554-555, 558-558
681-691: Well-implemented delete confirmation dialog.The
ConfirmDialogusage includes:
- Clear, user-friendly title and message
- Rule name displayed in the confirmation message for context
- "danger" variant for appropriate visual emphasis
- Proper handlers for both confirm and cancel actions
The UX is significantly better than the native
window.confirm()approach.pmoves/ui/components/common/ConfirmDialog.tsx (1)
1-114: Run the UI smoke test to validate the changes.Per the coding guidelines, UI updates should be validated with the notebook-workbench smoke test to lint the Next.js bundle and validate Supabase connectivity.
Based on learnings, run:
make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"Replace
<uuid>with an appropriate thread identifier. Referencepmoves/docs/UI_NOTEBOOK_WORKBENCH.mdfor details.
Adds missing /dashboard/notebook/runtime page with: - Service health monitoring for notebook-sync (port 8095) - Prometheus metrics display - Manual sync trigger button - Auto-refresh option (10 seconds) - API routes for runtime status and sync trigger Also fixes gateway-agent environment configuration to use env_file instead of hardcoded environment variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
pmoves/ui/app/dashboard/notebook/runtime/page.tsx (1)
77-86: Simplify health status mapping logic.The nested ternary expressions for
healthColorandhealthTextare difficult to read. Consider extracting this logic into a helper function or using a more straightforward mapping approach.🔎 Proposed refactor
+ const getHealthStatus = (health: string | undefined) => { + if (health === 'ok' || health === 'healthy') { + return { color: 'bg-green-500', text: 'Healthy', pulse: true }; + } + if (health === 'starting') { + return { color: 'bg-yellow-500', text: 'Starting', pulse: false }; + } + if (health === 'error') { + return { color: 'bg-red-500', text: 'Error', pulse: false }; + } + return { color: 'bg-gray-400', text: 'Unknown', pulse: false }; + }; + + const healthStatus = getHealthStatus(runtime?.health); - const healthColor = { - healthy: 'bg-green-500', - starting: 'bg-yellow-500', - error: 'bg-red-500', - unknown: 'bg-gray-400', - }[runtime?.health === 'ok' || runtime?.health === 'healthy' ? 'healthy' : runtime?.health || 'unknown']; - - const healthText = runtime?.health === 'ok' || runtime?.health === 'healthy' - ? 'Healthy' - : runtime?.health || 'Unknown';Then update the usage:
- <span className={`w-2 h-2 rounded-full ${healthColor} ${runtime?.health === 'ok' || runtime?.health === 'healthy' ? 'animate-pulse' : ''}`} /> - <span className="text-neutral-500">{healthText}</span> + <span className={`w-2 h-2 rounded-full ${healthStatus.color} ${healthStatus.pulse ? 'animate-pulse' : ''}`} /> + <span className="text-neutral-500">{healthStatus.text}</span>pmoves/ui/app/api/notebook/runtime/route.ts (1)
15-35: Consider parallel fetches for better performance.The health and metrics fetches are currently sequential but are independent operations. Running them in parallel with
Promise.allwould reduce the overall response time.🔎 Proposed refactor
try { - // Fetch health status - const healthRes = await fetch(healthUrl, { + const [healthRes, metricsRes] = await Promise.all([ + fetch(healthUrl, { + cache: 'no-store', + signal: AbortSignal.timeout(5000), // 5 second timeout + }), + fetch(metricsUrl, { + cache: 'no-store', + signal: AbortSignal.timeout(5000), + }), + ]); + + const health = await healthRes.json().catch(() => ({ status: 'unknown' })); + + let metrics: Record<string, number | string> | null = null; + if (metricsRes.ok) { + const metricsText = await metricsRes.text(); + metrics = parsePrometheusMetrics(metricsText); + } - cache: 'no-store', - signal: AbortSignal.timeout(5000), // 5 second timeout - }); - - const health = await healthRes.json().catch(() => ({ status: 'unknown' })); - - // Fetch Prometheus metrics - const metricsRes = await fetch(metricsUrl, { - cache: 'no-store', - signal: AbortSignal.timeout(5000), - }); - - let metrics: Record<string, number | string> | null = null; - if (metricsRes.ok) { - const metricsText = await metricsRes.text(); - // Parse Prometheus metrics format - metrics = parsePrometheusMetrics(metricsText); - }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
pmoves/docker-compose.ymlpmoves/ui/app/api/notebook/runtime/route.tspmoves/ui/app/api/notebook/runtime/sync/route.tspmoves/ui/app/dashboard/notebook/runtime/page.tsx
🧰 Additional context used
📓 Path-based instructions (2)
pmoves/ui/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (pmoves/AGENTS.md)
UI updates: run
make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"to lint the Next.js bundle and validate Supabase connectivity; referencepmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Files:
pmoves/ui/app/api/notebook/runtime/route.tspmoves/ui/app/dashboard/notebook/runtime/page.tsxpmoves/ui/app/api/notebook/runtime/sync/route.ts
pmoves/**/docker-compose.yml
📄 CodeRabbit inference engine (pmoves/AGENTS.md)
Use Compose profiles (
data,workers) to scope what runs locally in docker-compose.yml
Files:
pmoves/docker-compose.yml
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/AGENTS.md:0-0
Timestamp: 2025-12-07T11:03:53.415Z
Learning: Applies to pmoves/ui/**/*.{js,jsx,ts,tsx} : UI updates: run `make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"` to lint the Next.js bundle and validate Supabase connectivity; reference `pmoves/docs/UI_NOTEBOOK_WORKBENCH.md`
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T12:03:17.291Z
Learning: UI updates: run make -C pmoves notebook-workbench-smoke ARGS='--thread=<uuid>' to lint the Next.js bundle and validate Supabase connectivity; reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md
📚 Learning: 2025-12-07T11:03:53.415Z
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/AGENTS.md:0-0
Timestamp: 2025-12-07T11:03:53.415Z
Learning: Applies to pmoves/ui/**/*.{js,jsx,ts,tsx} : UI updates: run `make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"` to lint the Next.js bundle and validate Supabase connectivity; reference `pmoves/docs/UI_NOTEBOOK_WORKBENCH.md`
Applied to files:
pmoves/ui/app/api/notebook/runtime/route.tspmoves/ui/app/dashboard/notebook/runtime/page.tsx
📚 Learning: 2025-12-15T12:03:17.291Z
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T12:03:17.291Z
Learning: UI updates: run make -C pmoves notebook-workbench-smoke ARGS='--thread=<uuid>' to lint the Next.js bundle and validate Supabase connectivity; reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Applied to files:
pmoves/ui/app/api/notebook/runtime/route.tspmoves/ui/app/dashboard/notebook/runtime/page.tsx
🧬 Code graph analysis (2)
pmoves/ui/app/api/notebook/runtime/route.ts (1)
pmoves/ui/lib/errorUtils.ts (1)
logError(75-98)
pmoves/ui/app/api/notebook/runtime/sync/route.ts (2)
pmoves/ui/lib/errorUtils.ts (1)
logError(75-98)pmoves/scripts/notebook_workbench_smoke.mjs (1)
data(74-74)
⏰ 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). (1)
- GitHub Check: Analyze (python)
🔇 Additional comments (7)
pmoves/docker-compose.yml (1)
1199-1214: The review comment is based on incorrect PR context and unverifiable file access claims.The commit creating this change is "feat(ui): add Notebook Runtime dashboard page" (430d6b7), not "fix(ui): Post-merge test fixes for accessibility and async handling" as cited in the review. The commit message explicitly states: "Also fixes gateway-agent environment configuration to use env_file instead of hardcoded environment variables"—this was an intentional change, not accidental scope creep.
The privilege escalation concerns cannot be substantiated:
env.sharedand.env.generateddo not exist in the repository (only.env.shared.exampleand.env.local.exampleexist). All env_file entries are markedrequired: false, so missing files will not cause failures. These are likely deployment-time or local-development-only generated files.One legitimate question remains: Why does gateway-agent load these additional env files (env.shared, .env.generated) while other agent-tier services (agent-zero, archon, mesh-agent, etc.) continue using the
<<: *env-tier-agentanchor? If this configuration difference is intentional and necessary for gateway-agent's functionality, it should be documented in a code comment explaining why.Likely an incorrect or invalid review comment.
pmoves/ui/app/api/notebook/runtime/sync/route.ts (1)
11-45: LGTM!The POST handler implementation is solid with proper timeout handling, comprehensive error logging, and appropriate HTTP status codes for different failure scenarios.
pmoves/ui/app/dashboard/notebook/runtime/page.tsx (3)
68-75: LGTM!The useEffect hook correctly manages the auto-refresh interval with proper cleanup and appropriate dependencies.
31-46: LGTM!The
fetchRuntimefunction demonstrates proper error handling by checkingres.okbefore attempting to parse JSON, and correctly manages loading and error states.
1-235:andpmoves/ui/app/api/notebook/runtime/route.ts (2)
22-22: LGTM!The defensive JSON parsing with a fallback to
{ status: 'unknown' }is appropriate for handling non-JSON responses from the health endpoint.
55-79: LGTM!The Prometheus metrics parser correctly handles the standard text format, strips labels appropriately, and safely filters non-numeric values.
The ingestion-queue page had template literals that were causing Turbopack to fail parsing. Replaced all template literals with string concatenation or a cn() helper function. Changes: - Added cn() helper function for className concatenation - Replaced all template literals in error messages - Replaced all template literals in JSX className attributes - Replaced CSV escaping backticks with String.fromCharCode() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes accessibility and error handling issues identified in PR #371: - AlertBanner: Make aria-live conditional (assertive for errors, polite for other variants) - ConfirmDialog: Add WCAG 2.1 compliant focus management - ConfirmDialog: Add Escape key handler for keyboard accessibility - Notebook runtime: Check res.ok before parsing JSON in handleSync - Notebook runtime: Add htmlFor/id to checkbox for label association All changes maintain backward compatibility and improve WCAG 2.1 compliance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
pmoves/ui/components/common/ConfirmDialog.tsx (1)
51-159: Implement focus trap to prevent keyboard users from escaping the modal.While focus management (storing and restoring focus) has been correctly implemented, a focus trap is still missing. Without it, keyboard users can press Tab and reach elements behind the modal, which violates WCAG 2.1 SC 2.1.2 (Keyboard) and SC 2.4.3 (Focus Order).
🔎 Recommended solution using react-focus-lock
Install the library:
npm install react-focus-lockThen wrap the dialog content:
+import FocusLock from "react-focus-lock"; + export function ConfirmDialog({ isOpen, title, message, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, variant = "danger", }: ConfirmDialogProps) { // ... existing code ... if (!isOpen) return null; return ( <div ref={dialogRef} tabIndex={-1} className="fixed inset-0 z-50 flex items-center justify-center" role="dialog" aria-modal="true" aria-labelledby="confirm-dialog-title" aria-describedby="confirm-dialog-message" > {/* Backdrop */} <div className="fixed inset-0 bg-black/50 transition-opacity" onClick={onCancel} aria-hidden="true" /> + <FocusLock> {/* Dialog */} <div className="relative z-10 bg-white rounded-lg shadow-xl max-w-md w-full mx-4 p-6"> {/* ... existing dialog content ... */} </div> + </FocusLock> </div> ); }This library is widely used, well-tested, and handles edge cases like nested focus traps and disabled elements.
🧹 Nitpick comments (1)
pmoves/ui/components/common/ConfirmDialog.tsx (1)
103-105: Remove unnecessary wrapper function.The
handleConfirmfunction simply callsonConfirm()without adding any additional logic. You can callonConfirmdirectly in the button'sonClickhandler.🔎 Proposed simplification
- const handleConfirm = () => { - onConfirm(); - }; - return ( <div // ... props > {/* ... */} <button type="button" - onClick={handleConfirm} + onClick={onConfirm} className={`px-4 py-2 rounded text-white focus:outline-none focus:ring-2 focus:ring-offset-2 ${VARIANT_CLASSES[variant]}`} > {confirmLabel} </button> </div> );
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
pmoves/ui/app/dashboard/notebook/runtime/page.tsxpmoves/ui/components/common/AlertBanner.tsxpmoves/ui/components/common/ConfirmDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- pmoves/ui/app/dashboard/notebook/runtime/page.tsx
- pmoves/ui/components/common/AlertBanner.tsx
🧰 Additional context used
📓 Path-based instructions (1)
pmoves/ui/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (pmoves/AGENTS.md)
UI updates: run
make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"to lint the Next.js bundle and validate Supabase connectivity; referencepmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Files:
pmoves/ui/components/common/ConfirmDialog.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/AGENTS.md:0-0
Timestamp: 2025-12-07T11:03:53.415Z
Learning: Applies to pmoves/ui/**/*.{js,jsx,ts,tsx} : UI updates: run `make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"` to lint the Next.js bundle and validate Supabase connectivity; reference `pmoves/docs/UI_NOTEBOOK_WORKBENCH.md`
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T12:03:17.291Z
Learning: UI updates: run make -C pmoves notebook-workbench-smoke ARGS='--thread=<uuid>' to lint the Next.js bundle and validate Supabase connectivity; reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-07T11:02:53.362Z
Learning: When requested to summarize a pull request, provide a short (3–5 bullet) recap highlighting risky areas, test coverage, and any follow-up work; point the author back to the PR template checkboxes if key validations are missing
🔇 Additional comments (2)
pmoves/ui/components/common/ConfirmDialog.tsx (2)
66-99: Excellent accessibility improvements for focus management and keyboard navigation.The focus management effect (lines 66-82) correctly stores and restores focus, and the Escape key handler (lines 85-99) provides proper keyboard dismissal. Both implementations address the accessibility requirements raised in previous reviews.
1-159: Verify UI changes with notebook workbench smoke test.As per coding guidelines, UI updates should be validated with the notebook workbench smoke test to lint the Next.js bundle and validate Supabase connectivity.
Run the following command:
make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"Replace
<uuid>with a valid thread identifier. Referencepmoves/docs/UI_NOTEBOOK_WORKBENCH.mdfor details.Based on coding guidelines for pmoves/ui/**/*.{js,jsx,ts,tsx} files.
Fixes "e.map is not a function" error by ensuring all state operations properly guard against undefined/null values: - fetchIngestionQueue: Wrap data in Array.isArray before setItems - Realtime callbacks: Check item validity + ensure prev is array - handleExport: Defensive check on items before filter/map Root cause: Supabase realtime may send unexpected payload formats during connection edge cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
pmoves/ui/app/dashboard/ingestion-queue/page.tsx
🧰 Additional context used
📓 Path-based instructions (1)
pmoves/ui/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (pmoves/AGENTS.md)
UI updates: run
make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"to lint the Next.js bundle and validate Supabase connectivity; referencepmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Files:
pmoves/ui/app/dashboard/ingestion-queue/page.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/AGENTS.md:0-0
Timestamp: 2025-12-07T11:03:53.415Z
Learning: Applies to pmoves/ui/**/*.{js,jsx,ts,tsx} : UI updates: run `make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"` to lint the Next.js bundle and validate Supabase connectivity; reference `pmoves/docs/UI_NOTEBOOK_WORKBENCH.md`
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T12:03:17.291Z
Learning: UI updates: run make -C pmoves notebook-workbench-smoke ARGS='--thread=<uuid>' to lint the Next.js bundle and validate Supabase connectivity; reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/docs/ARTSTUFF/realtime/.cursorrules:0-0
Timestamp: 2025-12-15T12:01:03.100Z
Learning: Applies to pmoves/docs/ARTSTUFF/realtime/**/*.js : Execute the Non-Negotiable Execution Workflow checklist before making any edits: (1) AGENTS Snapshot - write down exact sections relevant to the task, (2) Example Lock-in - identify closest matching script in D:\pinokio\prototype\system\examples and keep it open, (3) Pre-flight Checklist - convert applicable rules into task-specific checklist, (4) Mid-task Verification - cross-check example line to ensure syntax and structure match, (5) Exit Checklist - revisit pre-flight checklist before responding.
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-15T12:03:17.291Z
Learning: Mandatory context before changes: read pmoves/docs/PMOVES.AI PLANS/ROADMAP.md and pmoves/docs/NEXT_STEPS.md to align with current sprint focus
📚 Learning: 2025-12-07T11:03:53.415Z
Learnt from: CR
Repo: POWERFULMOVES/PMOVES.AI PR: 0
File: pmoves/AGENTS.md:0-0
Timestamp: 2025-12-07T11:03:53.415Z
Learning: Applies to pmoves/ui/**/*.{js,jsx,ts,tsx} : UI updates: run `make -C pmoves notebook-workbench-smoke ARGS="--thread=<uuid>"` to lint the Next.js bundle and validate Supabase connectivity; reference `pmoves/docs/UI_NOTEBOOK_WORKBENCH.md`
Applied to files:
pmoves/ui/app/dashboard/ingestion-queue/page.tsx
🧬 Code graph analysis (1)
pmoves/ui/app/dashboard/ingestion-queue/page.tsx (1)
pmoves/ui/components/common/AlertBanner.tsx (1)
AlertBanner(36-67)
⏰ 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). (1)
- GitHub Check: Analyze (python)
🔇 Additional comments (13)
pmoves/ui/app/dashboard/ingestion-queue/page.tsx (13)
7-10: LGTM: Clean className utility.The
cn()helper is a lightweight, correct implementation for joining class strings. This pattern avoids template literals for Turbopack compatibility and provides a clean API for conditional classes.
29-30: LGTM: Good refactoring to shared utilities.The imports of
formatTimeAgoandAlertBannerreduce code duplication and align with the broader UI consistency effort mentioned in the AI summary.
54-60: LGTM: Turbopack-compatible string formatting.The string concatenation approach on line 59 maintains correct logic while avoiding template literals for Turbopack compatibility.
96-115: Good defensive programming with appropriate guards.The
Array.isArraychecks on lines 97-98 and 102-104 prevent runtime errors when Supabase returns unexpected payload formats. The defensive approach is sound, especially given the commit message noting that "Supabase realtime may send unexpected payloads."Consider investigating the root cause of unexpected Supabase payloads to determine if there's a client configuration issue or API contract mismatch that should be addressed upstream.
118-158: Excellent defensive realtime handlers.The defensive checks across all three realtime callbacks (onInsert, onUpdate, onDelete) are well-implemented:
- Validates
item && item.idbefore processing- Ensures
previs always an array viasafePrevpattern- Maintains filter logic correctly after validation
This prevents crashes from malformed or unexpected realtime payloads.
180-216: LGTM: Consistent error handling pattern.The string concatenation approach for error messages (lines 188, 207) maintains the same functionality while adhering to Turbopack compatibility requirements.
226-260: LGTM: Consistent bulk action error handling.The bulk approve/reject handlers follow the same defensive and Turbopack-compatible patterns established elsewhere in the file.
277-300: LGTM: Good defensive checks and date formatting.The defensive array guard (lines 278-280) and string-based filename construction (lines 296-297) are correct and consistent with the file's patterns.
Note: This function calls
escapeCSVCell, which has a critical bug flagged in a separate comment.
303-319: LGTM: Simple and correct rule management.The rule handlers are straightforward, and the string-based ID generation on line 306 is appropriate for client-side rule management.
335-335: LGTM: Consistent error display using shared component.Replacing inline error rendering with the
AlertBannercomponent improves consistency across the UI, as mentioned in the AI summary.
342-342: LGTM: Consistent className composition throughout.All
cn()usages for dynamic className composition are correct and improve readability while maintaining Turbopack compatibility. The pattern is consistently applied across status indicators, buttons, badges, and container elements.Also applies to: 351-354, 400-400, 470-473, 518-518
545-545: LGTM: Clean time formatting and consistent string handling.The use of imported
formatTimeAgo(line 545) reduces code duplication, and the string concatenations for UI text (lines 358, 463) maintain Turbopack compatibility.Also applies to: 358-358, 463-463
1-598:and
| const escapeCSVCell = (cell: string): string => { | ||
| const cellStr = String(cell); | ||
| // Check if cell starts with formula-inducing characters | ||
| if (/^[=+\-@]/.test(cellStr)) { | ||
| // Prepend with single quote to prevent formula execution | ||
| return `"'" + cellStr.replace(/"/g, '""') + '"'; | ||
| const singleQuote = String.fromCharCode(39); | ||
| const doubleQuote = String.fromCharCode(34); | ||
| return singleQuote + singleQuote + cellStr.split(doubleQuote).join(doubleQuote + doubleQuote) + doubleQuote; | ||
| } | ||
| return '"' + cellStr.replace(/"/g, '""') + '"'; | ||
| }, []); | ||
| const doubleQuote = String.fromCharCode(34); | ||
| return doubleQuote + cellStr.split(doubleQuote).join(doubleQuote + doubleQuote) + doubleQuote; | ||
| }; |
There was a problem hiding this comment.
Fix CSV escaping logic for formula-injection prevention.
Line 271 has incorrect CSV escaping logic. The current implementation produces: ''<content>" which is not valid CSV format.
Issue: For cells starting with formula characters (=, +, -, @), the function should either:
- Prepend a single quote and wrap in double quotes:
"'=FORMULA" - Or just wrap in quotes with proper escaping:
"=FORMULA"
The current logic mixes both approaches incorrectly and produces malformed CSV.
🔎 Proposed fix for CSV escaping
- const escapeCSVCell = (cell: string): string => {
- const cellStr = String(cell);
- // Check if cell starts with formula-inducing characters
- if (/^[=+\-@]/.test(cellStr)) {
- // Prepend with single quote to prevent formula execution
- const singleQuote = String.fromCharCode(39);
- const doubleQuote = String.fromCharCode(34);
- return singleQuote + singleQuote + cellStr.split(doubleQuote).join(doubleQuote + doubleQuote) + doubleQuote;
- }
- const doubleQuote = String.fromCharCode(34);
- return doubleQuote + cellStr.split(doubleQuote).join(doubleQuote + doubleQuote) + doubleQuote;
- };
+ const escapeCSVCell = (cell: string): string => {
+ const cellStr = String(cell);
+ const doubleQuote = String.fromCharCode(34);
+ const singleQuote = String.fromCharCode(39);
+
+ // Check if cell starts with formula-inducing characters
+ if (/^[=+\-@]/.test(cellStr)) {
+ // Prepend with single quote to prevent formula execution, then escape and wrap
+ const safeCell = singleQuote + cellStr;
+ return doubleQuote + safeCell.split(doubleQuote).join(doubleQuote + doubleQuote) + doubleQuote;
+ }
+
+ // Standard CSV escaping: wrap in quotes and escape internal quotes
+ return doubleQuote + cellStr.split(doubleQuote).join(doubleQuote + doubleQuote) + doubleQuote;
+ };🤖 Prompt for AI Agents
In pmoves/ui/app/dashboard/ingestion-queue/page.tsx around lines 264 to 275, the
CSV escaping for cells that start with formula characters incorrectly produces
malformed output (''<content>") by doubling the single quote and mixing quoting
strategies; change the logic so that for formula-starting cells you prepend a
single quote once and then wrap the escaped cell in double quotes (i.e., "'" +
"\"" + escapedContent + "\""), and for non-formula cells just wrap the escaped
content in double quotes; ensure double quotes inside the cell are escaped by
doubling them in both cases and keep using String.fromCharCode(34) / 39 if
preferred for quotes.
…371) * fix(ui): post-merge test fixes - accessibility and async handling Accessibility (WCAG 2.1): - Add proper label associations (htmlFor + id) to all form inputs in ApprovalRulesConfig for proper screen reader support Test Fixes: - Fix ResearchResults clipboard mock to return resolved promise - Wrap async copy operations in act() for proper React testing - All tests now passing: 379/380 (99.7%) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(ui): consolidate duplicate UI patterns Extracts shared utilities and components to reduce code duplication: - formatTimeAgo: Consolidates 2 implementations (ingestion-queue, SyncStatus) - AlertBanner: Consolidates 3+ error banner patterns - ConfirmDialog: Replaces native confirm() with accessible React component Files created: - pmoves/ui/lib/timeUtils.ts - pmoves/ui/components/common/AlertBanner.tsx - pmoves/ui/components/common/ConfirmDialog.tsx - pmoves/ui/components/common/index.ts Tests updated: - SyncStatus.test.tsx: Updated for new time format behavior - ApprovalRulesConfig.test.tsx: Updated for ConfirmDialog interaction Test results: 380 passed, 1 skipped 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(ui): add Notebook Runtime dashboard page Adds missing /dashboard/notebook/runtime page with: - Service health monitoring for notebook-sync (port 8095) - Prometheus metrics display - Manual sync trigger button - Auto-refresh option (10 seconds) - API routes for runtime status and sync trigger Also fixes gateway-agent environment configuration to use env_file instead of hardcoded environment variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): avoid template literals for Turbopack compatibility The ingestion-queue page had template literals that were causing Turbopack to fail parsing. Replaced all template literals with string concatenation or a cn() helper function. Changes: - Added cn() helper function for className concatenation - Replaced all template literals in error messages - Replaced all template literals in JSX className attributes - Replaced CSV escaping backticks with String.fromCharCode() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): address CodeRabbit PR review comments Fixes accessibility and error handling issues identified in PR #371: - AlertBanner: Make aria-live conditional (assertive for errors, polite for other variants) - ConfirmDialog: Add WCAG 2.1 compliant focus management - ConfirmDialog: Add Escape key handler for keyboard accessibility - Notebook runtime: Check res.ok before parsing JSON in handleSync - Notebook runtime: Add htmlFor/id to checkbox for label association All changes maintain backward compatibility and improve WCAG 2.1 compliance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): add defensive Array.isArray checks to prevent map/reduce errors Fixes "e.map is not a function" error by ensuring all state operations properly guard against undefined/null values: - fetchIngestionQueue: Wrap data in Array.isArray before setItems - Realtime callbacks: Check item validity + ensure prev is array - handleExport: Defensive check on items before filter/map Root cause: Supabase realtime may send unexpected payload formats during connection edge cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Codex Agent <codex-agent@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Fixes test failures and accessibility issues identified after PR #370 merge.
Accessibility (WCAG 2.1)
Test Fixes
Test Results
Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes / Accessibility
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.