Skip to content

fix(web): localize dates, harden ExportModal, clear dead code + cover untested logic#180

Merged
Leon-87-7 merged 3 commits into
mainfrom
feat/web-date-localization
Jun 18, 2026
Merged

fix(web): localize dates, harden ExportModal, clear dead code + cover untested logic#180
Leon-87-7 merged 3 commits into
mainfrom
feat/web-date-localization

Conversation

@Leon-87-7

@Leon-87-7 Leon-87-7 commented Jun 18, 2026

Copy link
Copy Markdown
Owner

What

Web dashboard quality pass — three related fixes plus test coverage.

Date/time localization

  • New DateTime component renders timestamps in the viewer's locale + timezone. Previously toLocaleString() ran during SSR (server = en-US/UTC) and froze US format into the HTML; now it reformats on mount. Applied to job-card and preview-card (every date in the app).

ExportModal hardening (a11y)

  • Escape key and backdrop click now close the modal; role="dialog" + aria-modal + focus moves in on open and returns to the trigger on close.
  • Defer URL.revokeObjectURL so Firefox doesn't cancel the download.

Dead code + coverage

  • Drop unused RecoverySummary export and the unused value prop in ContentTypeTab (fallow dead-code gate).
  • Tests for the previously-untested clearAll / error-retry feed paths and getBackgroundForPath routing (clears CRAP 272 + CRAP 90 measurement gaps).
  • Fix pre-existing jobs/[id]/page.test.tsx fixture (missing summary/transcript/key_phrases) that left main red on tsc.

Verification

  • tsc --noEmit clean
  • 124 web tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added keyboard navigation support (Escape to close) and improved focus management for the export modal
    • Implemented consistent date/time formatting across the application
  • Bug Fixes

    • Fixed file download compatibility issue in Firefox
    • Enhanced export modal accessibility with improved dialog semantics and focus restoration
  • Tests

    • Added tests for feed error retry and filter clearing functionality
    • Added background image routing validation tests

Greptile Summary

This PR delivers a focused quality pass across three areas: SSR-safe date localization, ExportModal accessibility hardening, and dead-code removal with new test coverage.

  • DateTime component: Seeds the initial state with a deterministic en-US/UTC string so server and client hydration match, then reformats to the viewer's locale on mount. Applied to both job-card and preview-card.
  • ExportModal a11y: Adds role="dialog" + aria-modal, separates the focus-capture effect (mount-only) from the Escape-key listener (onClose dep), implements a full Tab trap via trapTab, and defers URL.revokeObjectURL to fix the Firefox download cancellation.
  • Cleanup + coverage: Unexports the internal RecoverySummary interface, removes the unused value prop from ContentTypeTab, adds tests for clearAll/error-retry feed paths and getBackgroundForPath routing, and fixes the tsc-breaking fixture in the jobs detail test.

Confidence Score: 5/5

All changes are additive accessibility and correctness fixes; no data paths, API contracts, or authentication boundaries are touched.

The two prior review findings (focus round-trip on parent re-render, missing Tab trap) are both resolved cleanly. The DateTime localization approach is correct: deterministic initial state prevents hydration mismatch, the effect always differs for non-en-US/non-UTC viewers and triggers the re-render. Dead-code removal and test additions are low-risk. No logic regressions are visible across the changed files.

No files require special attention.

Important Files Changed

Filename Overview
web/components/ExportModal.tsx Addresses both prior review findings: focus effect is now mount-only (no focus flicker), Tab trap added via trapTab handler. Firefox URL.revokeObjectURL deferred correctly. ARIA attributes and backdrop-click close are clean.
web/components/date-time.tsx New DateTime component correctly seeds initial state with deterministic en-US/UTC value to match SSR, then reformats on mount to viewer locale. suppressHydrationWarning appropriately covers the expected content mismatch.
web/components/page-background.test.tsx New test file covering getBackgroundForPath routing via PageBackground; uses it.each for all route cases including the fallback. Auto-cleanup between it.each test cases is handled by @testing-library/react's afterEach.
web/app/(dashboard)/page.test.tsx Adds tests for the error-banner retry and clear-all-filters empty-state paths, covering previously-untested reload and router.replace/setStFilter/setQuery call sites.
web/lib/hooks/useRecovery.ts RecoverySummary interface narrowed to package-private (unexported). No consumers remain outside the file, so this is a clean API-surface reduction.
web/app/(dashboard)/jobs/[id]/page.test.tsx Adds missing summary/transcript/key_phrases fields to the JOB fixture to satisfy the TypeScript schema and un-break the tsc check on main.
web/components/feed/filter-bar.tsx Removes the unused value prop from ContentTypeTab interface and destructuring. Straightforward dead-code cleanup with no functional change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Server as SSR Next.js
    participant React as React Hydration
    participant Effect as useEffect client
    participant DOM as time element

    Server->>DOM: "render en-US/UTC string"
    React->>DOM: "hydrate — state matches, no warning"
    Effect->>DOM: "toLocaleString() — viewer locale+tz"
    DOM-->>Effect: "displays locale-correct timestamp"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Server as SSR Next.js
    participant React as React Hydration
    participant Effect as useEffect client
    participant DOM as time element

    Server->>DOM: "render en-US/UTC string"
    React->>DOM: "hydrate — state matches, no warning"
    Effect->>DOM: "toLocaleString() — viewer locale+tz"
    DOM-->>Effect: "displays locale-correct timestamp"
Loading

Reviews (4): Last reviewed commit: "fix(web/DateTime): deterministic UTC SSR..." | Re-trigger Greptile

… untested logic

- date/time now render in the viewer's locale + timezone (DateTime component);
  fixes US-format/UTC frozen into SSR HTML
- ExportModal: Escape + backdrop-click close, role=dialog + focus return,
  defer blob URL revoke (Firefox download cancel)
- drop unused export (RecoverySummary) and unused prop (filter-bar value)
- add tests: feed clear-all/error-retry paths, getBackgroundForPath routing
  (clears CRAP 272 + CRAP 90 measurement gaps)
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vig Ready Ready Preview, Comment Jun 18, 2026 10:12am

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces a shared DateTime client component that replaces inline toLocaleString date formatting in PreviewCard and JobCard. Adds accessibility (ARIA dialog semantics, keyboard/focus management, overlay click-to-close) and a Firefox download fix to ExportModal. Removes the unused value prop from ContentTypeTab, unexports RecoverySummary, and adds test coverage for FeedPage interactions and PageBackground background selection.

Changes

DateTime, ExportModal a11y, and cleanups

Layer / File(s) Summary
DateTime component and card adoption
web/components/date-time.tsx, web/components/feed/preview-card.tsx, web/components/job-card.tsx
New "use client" DateTime component renders a <time> element using toLocaleString with suppressHydrationWarning; PreviewCard and JobCard replace their inline date formatting with it.
ExportModal accessibility and focus management
web/components/ExportModal.tsx
Adds useRef, defers URL.revokeObjectURL via setTimeout, auto-focuses the close button on mount, restores prior focus on cleanup, handles Escape key, and adds role="dialog" / aria-modal / aria-labelledby plus overlay click-to-close.
ContentTypeTab prop removal and RecoverySummary unexport
web/components/feed/filter-bar.tsx, web/lib/hooks/useRecovery.ts
Removes the unused value property from ContentTypeTab's props; changes RecoverySummary from an exported interface to a module-internal one.
Test additions
web/app/(dashboard)/jobs/[id]/page.test.tsx, web/app/(dashboard)/page.test.tsx, web/components/page-background.test.tsx
Extends the JOB mock with summary, transcript, and key_phrases null fields; adds FeedPage tests for the retry button and clear-filters navigation; adds PageBackground tests asserting correct webp background mapping and fallback.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Leon-87-7/vig#172: Directly related — this PR extends the JOB mock with summary, transcript, and key_phrases null fields that correspond to the enrichment fields introduced in that PR.

Poem

🐇 A DateTime hops into the code with grace,
No more inline toLocaleString in every place.
The modal now traps focus, minds Escape with care,
A RecoverySummary quietly hides in its lair.
Tests check retries, clear filters, and webp maps—
Clean and tidy, the rabbit takes a nap. 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title concisely and accurately captures the four main changes: date localization, ExportModal hardening, dead code removal, and test coverage expansion.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-date-localization

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.

Comment thread web/components/ExportModal.tsx
Comment thread web/components/ExportModal.tsx

@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 (1)
web/components/feed/filter-bar.tsx (1)

25-25: ⚡ Quick win

value is still part of ContentTypeTab’s prop contract.

Line 25 still types props with ContentTypeTabData, so value remains in the component surface (and is still passed via spread later). If the goal is to remove the unused prop, narrow the child props to only label/count plus control props.

Proposed refactor
 interface ContentTypeTabData {
   label: string;
   value: string;
   count: number;
 }
 
-function ContentTypeTab({ label, count, active, onClick }: ContentTypeTabData & { active: boolean; onClick: () => void }) {
+type ContentTypeTabProps = Pick<ContentTypeTabData, "label" | "count"> & {
+  active: boolean;
+  onClick: () => void;
+};
+
+function ContentTypeTab({ label, count, active, onClick }: ContentTypeTabProps) {
           {tabs.map((tab) => (
             <ContentTypeTab
               key={tab.value}
-              {...tab}
+              label={tab.label}
+              count={tab.count}
               active={ctFilter === tab.value}
               onClick={() => setCtFilter(tab.value)}
             />
           ))}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/components/feed/filter-bar.tsx` at line 25, The ContentTypeTab function
is still typed with ContentTypeTabData in its prop contract, which includes the
unused value property. Remove ContentTypeTabData from the type annotation and
explicitly define only the props that ContentTypeTab actually uses: label,
count, active, and onClick. Replace the current prop type on the ContentTypeTab
function signature to only include these specific properties without spreading
ContentTypeTabData.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/components/date-time.tsx`:
- Around line 8-13: The issue is that the component initializes state with a
locale-formatted string which can cause hydration mismatch issues in React 18 if
the server and client locales differ. When suppressHydrationWarning is used, the
recovery re-render is skipped. Fix this by initializing the text state in the
useState hook with the raw iso string value instead of new
Date(iso).toLocaleString(), so that the useEffect will always trigger a state
update with the client locale version, ensuring the mismatch is corrected. Keep
the useEffect the same so it continues to format the date string with the client
locale.

In `@web/components/ExportModal.tsx`:
- Around line 46-58: Implement a focus trap within the modal dialog to prevent
keyboard users from tabbing to background controls. In the useEffect hook where
you handle Escape key behavior and focus management with closeButtonRef, add a
keydown event listener that intercepts Tab key presses. When Tab is pressed
while focus is on the last focusable element in the modal, redirect focus to the
first focusable element, and when Shift+Tab is pressed on the first focusable
element, redirect focus to the last. This ensures focus cycles within the modal
and maintains proper accessibility semantics.

---

Nitpick comments:
In `@web/components/feed/filter-bar.tsx`:
- Line 25: The ContentTypeTab function is still typed with ContentTypeTabData in
its prop contract, which includes the unused value property. Remove
ContentTypeTabData from the type annotation and explicitly define only the props
that ContentTypeTab actually uses: label, count, active, and onClick. Replace
the current prop type on the ContentTypeTab function signature to only include
these specific properties without spreading ContentTypeTabData.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e644b86d-e492-425b-adb0-17ec8f08ce15

📥 Commits

Reviewing files that changed from the base of the PR and between 96d78c7 and d7ac6fd.

📒 Files selected for processing (9)
  • web/app/(dashboard)/jobs/[id]/page.test.tsx
  • web/app/(dashboard)/page.test.tsx
  • web/components/ExportModal.tsx
  • web/components/date-time.tsx
  • web/components/feed/filter-bar.tsx
  • web/components/feed/preview-card.tsx
  • web/components/job-card.tsx
  • web/components/page-background.test.tsx
  • web/lib/hooks/useRecovery.ts

Comment thread web/components/date-time.tsx Outdated
Comment thread web/components/ExportModal.tsx Outdated
…p iteration 1)

- separate mount-only focus capture/restore from the Escape listener so an
  inline onClose identity change can't round-trip focus on parent re-render (P1)
- trap Tab/Shift+Tab within the dialog per the APG pattern (P2)
…ly applies (greploop iteration 2)

suppressHydrationWarning skips React's recovery re-render, so initializing
state with the client-locale value made the effect a no-op (same value → bail
out) and the server format stuck. Init with a deterministic UTC string instead;
the effect's locale value now differs and re-renders. Caught by CodeRabbit.
@Leon-87-7

Copy link
Copy Markdown
Owner Author

@greptile review

@Leon-87-7
Leon-87-7 merged commit 2f0d060 into main Jun 18, 2026
4 checks passed
@Leon-87-7
Leon-87-7 deleted the feat/web-date-localization branch June 29, 2026 14: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