Skip to content

fix(ui): Post-merge test fixes for accessibility and async handling - #371

Merged
POWERFULMOVES merged 6 commits into
mainfrom
fix/post-merge-ui-test-fixes
Dec 29, 2025
Merged

POWERFULMOVES merged 6 commits into
mainfrom
fix/post-merge-ui-test-fixes

Conversation

@POWERFULMOVES

@POWERFULMOVES POWERFULMOVES commented Dec 28, 2025

Copy link
Copy Markdown
Owner

Summary

Fixes test failures and accessibility issues identified after PR #370 merge.

Accessibility (WCAG 2.1)

  • Add proper htmlFor + id label associations to all form inputs in ApprovalRulesConfig
  • Ensures screen readers can properly announce field purposes
  • Fixes Testing Library getByLabelText queries

Test Fixes

  • Fix ResearchResults clipboard mock to return resolved promise
  • Wrap async copy operations in act() for proper React testing

Test Results

  • All tests passing: 379/380 (99.7%)
  • ApprovalRulesConfig: 30 tests passing
  • ResearchResults: 24 tests passing

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Confirmation modal for deleting rules and a reusable dismissible alert banner; both are exported for app-wide use.
    • Human-friendly time & duration formatting utilities.
    • Notebook Runtime dashboard with status/metrics panels, auto-refresh, manual sync, and new endpoints to report health and trigger sync.
  • Bug Fixes / Accessibility

    • Linked labels to inputs for improved form accessibility and unified error UI using the alert banner.
  • Tests

    • Updated tests for modal-based deletion and async clipboard/UI behaviors.
  • Chores

    • Compose config updated to load env files for gateway agent.

✏️ Tip: You can customize this high-level summary in your review settings.

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>
@coderabbitai

coderabbitai Bot commented Dec 28, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Added 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

Cohort / File(s) Change Summary
Submodule Updates
PMOVES-DoX, Pmoves-hyperdimensions
Updated submodule pointers to newer commits; no behavioral changes.
Common UI Components
pmoves/ui/components/common/AlertBanner.tsx, pmoves/ui/components/common/ConfirmDialog.tsx, pmoves/ui/components/common/index.ts
New AlertBanner and ConfirmDialog components with typed props, accessibility, variants, dismiss handling; added barrel exports.
Ingestion — Approval Rules
pmoves/ui/components/ingestion/ApprovalRulesConfig.tsx, pmoves/ui/components/ingestion/ApprovalRulesConfig.test.tsx
Replaced window.confirm with ConfirmDialog modal flow; added deleteConfirm state/handlers; improved label htmlFor/id attributes; tests adjusted to interact with modal.
Time utilities & usage
pmoves/ui/lib/timeUtils.ts, pmoves/ui/components/jellyfin/SyncStatus.tsx, pmoves/ui/components/jellyfin/SyncStatus.test.tsx
Added formatTimeAgo and formatDuration; SyncStatus now uses formatTimeAgo and AlertBanner; tests extended for relative vs locale date behavior.
AlertBanner adoption in pages
pmoves/ui/app/dashboard/chat/page.tsx, pmoves/ui/app/dashboard/ingestion-queue/page.tsx
Replaced inline error banners with AlertBanner (with onDismiss where used); ingestion-queue refactor includes cn(...) helper, formatTimeAgo usage, CSV/export tweaks, defensive guards and className changes.
Notebook runtime API + UI
pmoves/ui/app/api/notebook/runtime/route.ts, pmoves/ui/app/api/notebook/runtime/sync/route.ts, pmoves/ui/app/dashboard/notebook/runtime/page.tsx
New GET route reporting health and parsed Prometheus metrics, POST sync proxy to /sync, and NotebookRuntimePage client UI with health/metrics display, trigger-sync, auto-refresh, and AlertBanner error handling.
Async test fixes
pmoves/ui/components/research/ResearchResults.test.tsx
Mocked clipboard.writeText to return a resolved Promise; updated tests to use act/async-await to flush microtasks.
CSV / UI refactors
pmoves/ui/app/dashboard/ingestion-queue/page.tsx
Reworked CSV escaping (escapeCSVCell), filename date construction, status ID generation, and migrated className compositions to cn(...) helper for Turbopack compatibility.
Docker compose env changes
pmoves/docker-compose.yml
gateway-agent switched from anchor inheritance to explicit env_file list; removed inline API key env entries (now loaded via env_file).

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant Browser
participant NotebookPage as Notebook Page (client)
participant NextSync as /api/notebook/runtime/sync
participant NextAPI as /api/notebook/runtime
participant NotebookSvc as Notebook Sync Service
Note over Browser,NotebookPage: User opens page, may Trigger Sync or enable auto-refresh
Browser->>NotebookPage: load page / click "Trigger Sync"
NotebookPage->>NextSync: POST /api/notebook/runtime/sync
Note right of NextSync: builds external URL and forwards (30s timeout)
NextSync->>NotebookSvc: POST /sync
alt success
NotebookSvc-->>NextSync: 200 + JSON
NextSync-->>NotebookPage: { ok: true, ...data }
NotebookPage->>NextAPI: GET /api/notebook/runtime (refresh)
else failure
NotebookSvc-->>NextSync: non-200 / error
NextSync-->>NotebookPage: 502 { ok:false, error }
end
NextAPI->>NotebookSvc: GET /healthz and /metrics (parallel, 5s timeout)
NotebookSvc-->>NextAPI: health + prometheus text
Note right of NextAPI: parsePrometheusMetrics -> key/value map
NextAPI-->>NotebookPage: JSON { service, health, endpoint, metrics }
NotebookPage-->>Browser: render status, metrics, and AlertBanner (on error)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop to guard the delete and cheer,
A modal asks — "Are you sure, my dear?"
Banners bloom when errors find the light,
Times tick friendly, metrics shining bright.
Submodules nudged — the garden's tidy, bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description addresses Summary and Testing sections but lacks required Testing command output, CHIT Contract Check status, and other mandatory checklist items from the template. Complete the Testing section with actual command outputs, verify/document CHIT Contract Check results, and check all required items in the template checklist before merge.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing post-merge test failures for accessibility improvements and async handling in React tests.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/post-merge-ui-test-fixes

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.

❤️ Share

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

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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (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:

  1. Mock toLocaleDateString() to return a consistent value
  2. Use a regex pattern to verify the format rather than exact string match
  3. 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 querySelector with 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 getByRole or getByText for 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-testid attributes 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 formatTimeAgo function doesn't handle edge cases:

  1. Invalid date strings will result in Invalid Date, which when passed to getTime() returns NaN, leading to incorrect "Just now" display
  2. Future dates (where diffMs is 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 redundant handleConfirm wrapper.

The handleConfirm function simply forwards to onConfirm() without adding any logic. This can be simplified by calling onConfirm directly.

🔎 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

📥 Commits

Reviewing files that changed from the base of the PR and between 618edd4 and 860648e.

📒 Files selected for processing (10)
  • pmoves/ui/app/dashboard/chat/page.tsx
  • pmoves/ui/app/dashboard/ingestion-queue/page.tsx
  • pmoves/ui/components/common/AlertBanner.tsx
  • pmoves/ui/components/common/ConfirmDialog.tsx
  • pmoves/ui/components/common/index.ts
  • pmoves/ui/components/ingestion/ApprovalRulesConfig.test.tsx
  • pmoves/ui/components/ingestion/ApprovalRulesConfig.tsx
  • pmoves/ui/components/jellyfin/SyncStatus.test.tsx
  • pmoves/ui/components/jellyfin/SyncStatus.tsx
  • pmoves/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; reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md

Files:

  • pmoves/ui/lib/timeUtils.ts
  • pmoves/ui/components/common/index.ts
  • pmoves/ui/components/ingestion/ApprovalRulesConfig.test.tsx
  • pmoves/ui/components/common/AlertBanner.tsx
  • pmoves/ui/components/common/ConfirmDialog.tsx
  • pmoves/ui/app/dashboard/ingestion-queue/page.tsx
  • pmoves/ui/components/jellyfin/SyncStatus.tsx
  • pmoves/ui/app/dashboard/chat/page.tsx
  • pmoves/ui/components/ingestion/ApprovalRulesConfig.tsx
  • pmoves/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 formatTimeAgo and AlertBanner eliminates 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 formatDuration function 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() with ConfirmDialog provides:

  • 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 deleteConfirm is 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 htmlFor attributes on labels and corresponding id attributes 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 ConfirmDialog usage 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. Reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md for details.

Comment thread pmoves/ui/components/common/AlertBanner.tsx
Comment thread pmoves/ui/components/common/ConfirmDialog.tsx
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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 healthColor and healthText are 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.all would 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

📥 Commits

Reviewing files that changed from the base of the PR and between 860648e and 430d6b7.

📒 Files selected for processing (4)
  • pmoves/docker-compose.yml
  • pmoves/ui/app/api/notebook/runtime/route.ts
  • pmoves/ui/app/api/notebook/runtime/sync/route.ts
  • pmoves/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; reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md

Files:

  • pmoves/ui/app/api/notebook/runtime/route.ts
  • pmoves/ui/app/dashboard/notebook/runtime/page.tsx
  • pmoves/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.ts
  • pmoves/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.ts
  • pmoves/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.shared and .env.generated do not exist in the repository (only .env.shared.example and .env.local.example exist). All env_file entries are marked required: 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-agent anchor? 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 fetchRuntime function demonstrates proper error handling by checking res.ok before attempting to parse JSON, and correctly manages loading and error states.


1-235: and

pmoves/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.

Comment thread pmoves/ui/app/dashboard/notebook/runtime/page.tsx
Comment thread pmoves/ui/app/dashboard/notebook/runtime/page.tsx Outdated
Codex Agent and others added 2 commits December 28, 2025 13:14
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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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-lock

Then 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 handleConfirm function simply calls onConfirm() without adding any additional logic. You can call onConfirm directly in the button's onClick handler.

🔎 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

📥 Commits

Reviewing files that changed from the base of the PR and between 07038f8 and 842a99b.

📒 Files selected for processing (3)
  • pmoves/ui/app/dashboard/notebook/runtime/page.tsx
  • pmoves/ui/components/common/AlertBanner.tsx
  • pmoves/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; reference pmoves/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. Reference pmoves/docs/UI_NOTEBOOK_WORKBENCH.md for 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 842a99b and 9caa6dc.

📒 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; reference pmoves/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 formatTimeAgo and AlertBanner reduce 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.isArray checks 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.id before processing
  • Ensures prev is always an array via safePrev pattern
  • 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 AlertBanner component 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

Comment on lines +264 to +275
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;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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:

  1. Prepend a single quote and wrap in double quotes: "'=FORMULA"
  2. 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.

@POWERFULMOVES
POWERFULMOVES merged commit 6c97210 into main Dec 29, 2025
6 checks passed
POWERFULMOVES added a commit that referenced this pull request Jan 18, 2026
…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>
@POWERFULMOVES
POWERFULMOVES deleted the fix/post-merge-ui-test-fixes branch March 7, 2026 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant