Skip to content

fix(a11y): Correct color contrast for WCAG AA compliance (#3797)#3798

Draft
Srizdebnath wants to merge 3 commits intoOWASP:mainfrom
Srizdebnath:fix/a11y-color-contrast
Draft

fix(a11y): Correct color contrast for WCAG AA compliance (#3797)#3798
Srizdebnath wants to merge 3 commits intoOWASP:mainfrom
Srizdebnath:fix/a11y-color-contrast

Conversation

@Srizdebnath
Copy link

@Srizdebnath Srizdebnath commented Feb 6, 2026

Description

This PR fixes accessibility issues related to color contrast across the application, addressing failures in WCAG AA standards.

Related Issue(s)

Closes #3797

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

Summary by CodeRabbit

  • Style
    • Updated color tokens and text/link contrast across the app for improved light/dark theme consistency; numerous UI elements and pages now use darker light-mode text and refined dark-mode variants.
  • Tests
    • Adjusted unit test expectations and minor test formatting to align with the updated visual styling and mock refinements.

Walkthrough

This PR updates Tailwind color utilities across many frontend pages, components, and tests to improve contrast (e.g., text-gray-600text-gray-800, dark:text-gray-400dark:text-gray-200, text-blue-400text-blue-600/700), plus minor template/formatting tweaks and small timestamp handling robustness in candidates page. No API or runtime logic changes.

Changes

Cohort / File(s) Summary
Test Expectations
frontend/__tests__/unit/components/CalendarButton.test.tsx, frontend/__tests__/unit/components/Card.test.tsx, frontend/__tests__/unit/components/CardDetailsPage.test.tsx, frontend/__tests__/unit/components/ContributionStats.test.tsx, frontend/__tests__/unit/components/ContributorsList.test.tsx, frontend/__tests__/unit/components/DisplayIcon.test.tsx, frontend/__tests__/unit/components/LeadersList.test.tsx, frontend/__tests__/unit/pages/About.test.tsx, frontend/__tests__/unit/pages/ModuleIssueDetailsPage.test.tsx
Updated className assertions and mock formatting to match revised Tailwind tokens; added keyed useQuery mocks and expanded About page test scenarios.
Page Components
frontend/src/app/about/page.tsx, frontend/src/app/board/[year]/candidates/page.tsx, frontend/src/app/chapters/[chapterKey]/page.tsx, frontend/src/app/committees/[committeeKey]/page.tsx, frontend/src/app/community/snapshots/[id]/page.tsx, frontend/src/app/community/snapshots/page.tsx, frontend/src/app/members/[memberKey]/page.tsx, frontend/src/app/my/mentorship/page.tsx, frontend/src/app/my/mentorship/programs/.../issues/[issueId]/page.tsx, frontend/src/app/my/mentorship/programs/.../issues/page.tsx, frontend/src/app/my/mentorship/programs/.../mentees/[menteeKey]/page.tsx, frontend/src/app/organizations/[organizationKey]/page.tsx, frontend/src/app/organizations/.../repositories/[repositoryKey]/page.tsx, frontend/src/app/page.tsx, frontend/src/app/settings/api-keys/page.tsx
Applied color-token updates across page-level UIs; candidates page also includes timestamp input handling robustness and small structural consolidation. No data-fetch or API changes.
UI Components
frontend/src/components/BreadCrumbs.tsx, frontend/src/components/BreadCrumbsWrapper.tsx, frontend/src/components/Card.tsx, frontend/src/components/CardDetailsPage.tsx, frontend/src/components/ContributionStats.tsx, frontend/src/components/ContributorsList.tsx, frontend/src/components/DisplayIcon.tsx, frontend/src/components/InfoItem.tsx, frontend/src/components/IssuesTable.tsx, frontend/src/components/ItemCardList.tsx, frontend/src/components/LeadersList.tsx, frontend/src/components/LoginPageContent.tsx, frontend/src/components/MenteeIssues.tsx, frontend/src/components/MentorshipPullRequest.tsx, frontend/src/components/MetricsCard.tsx, frontend/src/components/Milestones.tsx, frontend/src/components/Modal.tsx, frontend/src/components/ModuleCard.tsx, frontend/src/components/ModuleForm.tsx, frontend/src/components/MultiSearch.tsx, frontend/src/components/Pagination.tsx, frontend/src/components/ProgramCard.tsx, frontend/src/components/ProgramForm.tsx, frontend/src/components/ProjectsDashboardDropDown.tsx, frontend/src/components/ProjectsDashboardNavBar.tsx, frontend/src/components/RecentIssues.tsx, frontend/src/components/RecentPullRequests.tsx, frontend/src/components/Release.tsx, frontend/src/components/RepositoryCard.tsx, frontend/src/components/ShowMoreButton.tsx, frontend/src/components/SingleModuleCard.tsx, frontend/src/components/SnapshotCard.tsx, frontend/src/components/SortBy.tsx, frontend/src/components/SponsorCard.tsx, frontend/src/components/UserCard.tsx
Standardized Tailwind color changes across reusable components (text, links, icons); a few className template string reformatting changes; behavior unchanged.
Form Components
frontend/src/components/forms/shared/FormButtons.tsx, frontend/src/components/forms/shared/FormDateInput.tsx, frontend/src/components/forms/shared/FormTextInput.tsx, frontend/src/components/forms/shared/FormTextarea.tsx
Label and container text color updates from lighter to darker gray tokens; minor formatting tweaks only.
Skeleton Components
frontend/src/components/skeletons/AboutSkeleton.tsx, frontend/src/components/skeletons/sharedSkeletons.tsx
Skeleton/wrapper text color updated to darker gray for consistent contrast across light/dark modes.
Minor Tests & Misc
frontend/__tests__/unit/components/CalendarButton.test.tsx, frontend/__tests__/unit/components/DisplayIcon.test.tsx (already listed above)
Minor mock implementation formatting and spacing adjustments in tests; no behavioral change.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR partially addresses the linked issue objectives; however, the PR objectives note that regressions were identified where light-mode links (text-blue-300/400) still fail WCAG AA standards and require darker shades (text-blue-600/700). The fixes acknowledge but do not fully resolve all identified contrast issues. Complete the outstanding fixes: revert light-mode link colors from text-blue-300/400 to darker text-blue-600/700 variants while maintaining dark:text-blue-300 for dark mode to fully meet WCAG AA 4.5:1 contrast requirements.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing color contrast for WCAG AA compliance accessibility standards.
Description check ✅ Passed The description relates to the changeset, referencing the linked issue #3797 and stating the PR fixes accessibility color contrast issues for WCAG AA compliance.
Out of Scope Changes check ✅ Passed All changes are within scope: they consist of color class updates (gray and blue shade adjustments) across components and test files to improve WCAG AA contrast compliance, with minor test assertion updates and formatting adjustments related to the color changes.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

11 issues found across 66 files

Confidence score: 3/5

  • Multiple link color changes to text-blue-300 reduce contrast on light backgrounds and fail WCAG AA, which is a concrete accessibility regression for users (e.g., frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx, frontend/src/app/committees/[committeeKey]/page.tsx).
  • These contrast drops are repeated across shared components like frontend/src/components/Release.tsx, frontend/src/components/ModuleCard.tsx, and frontend/src/components/ContributorsList.tsx, so the impact is broad and user-facing despite being straightforward to fix.
  • Pay close attention to frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx, frontend/src/app/committees/[committeeKey]/page.tsx, frontend/src/components/Release.tsx, frontend/src/components/ModuleCard.tsx, frontend/src/components/ContributorsList.tsx, frontend/src/components/CardDetailsPage.tsx, frontend/src/app/chapters/[chapterKey]/page.tsx, frontend/src/app/board/[year]/candidates/page.tsx - link/icon contrast regressions from lighter blue text.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="frontend/src/app/chapters/[chapterKey]/page.tsx">

<violation number="1" location="frontend/src/app/chapters/[chapterKey]/page.tsx:64">
P2: This change makes the link text lighter (blue-300), which reduces contrast and can worsen WCAG AA compliance on light backgrounds. Consider using a darker shade (e.g., blue-600/700) to improve contrast.</violation>
</file>

<file name="frontend/src/app/board/[year]/candidates/page.tsx">

<violation number="1" location="frontend/src/app/board/[year]/candidates/page.tsx:353">
P2: `text-blue-300` on the white card background has insufficient contrast (≈1.7:1), so this change fails WCAG AA and reduces contrast relative to the previous color. Use a darker blue for light mode and keep a lighter shade in dark mode.</violation>
</file>

<file name="frontend/src/components/ModuleCard.tsx">

<violation number="1" location="frontend/src/components/ModuleCard.tsx:49">
P2: The new text color is lighter (text-blue-300), which lowers contrast against the light background. This runs counter to the WCAG AA contrast goal; use a darker blue shade instead to preserve contrast.</violation>
</file>

<file name="frontend/src/components/Release.tsx">

<violation number="1" location="frontend/src/components/Release.tsx:53">
P2: This change makes the link text lighter (blue-300), which reduces contrast against the light background and undermines the WCAG AA contrast fix. Use a darker blue shade here.</violation>

<violation number="2" location="frontend/src/components/Release.tsx:72">
P2: This change lightens the link text (blue-300), reducing contrast on the light background. Use a darker blue shade to meet WCAG AA contrast.</violation>
</file>

<file name="frontend/src/components/ContributorsList.tsx">

<violation number="1" location="frontend/src/components/ContributorsList.tsx:63">
P2: The new `text-blue-300` is lighter than the previous `text-blue-400`, and on the unchanged `bg-gray-200` background it reduces contrast (≈1.46:1 vs ≈2.05:1). This makes the link less legible and moves contrast in the wrong direction. Use a darker shade for the link text on this light background (e.g., blue-700).</violation>
</file>

<file name="frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx">

<violation number="1" location="frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx:72">
P2: Switching this link to `text-blue-300` reduces contrast on the white background and is still below the 4.5:1 WCAG AA requirement for normal text. This is likely a regression from the accessibility fixes; use a darker blue that meets AA (e.g., blue-600).</violation>
</file>

<file name="frontend/src/app/committees/[committeeKey]/page.tsx">

<violation number="1" location="frontend/src/app/committees/[committeeKey]/page.tsx:65">
P2: The updated link color uses a lighter shade (text-blue-300) on a white background, dropping contrast to ~1.8:1 and failing WCAG AA. Use a darker shade (e.g., text-blue-600) so the URL remains accessible on light backgrounds.</violation>
</file>

<file name="frontend/src/components/CardDetailsPage.tsx">

<violation number="1" location="frontend/src/components/CardDetailsPage.tsx:426">
P2: `text-blue-300` is too light on the light-theme milestone card (`bg-gray-200`) and fails WCAG AA contrast. Use a darker light-theme color and keep a light dark-mode override instead of a single light shade.</violation>

<violation number="2" location="frontend/src/components/CardDetailsPage.tsx:447">
P2: The milestone title link color `text-blue-300` fails WCAG AA on the light gray card background. Use a darker light-theme shade with a dark-mode override.</violation>

<violation number="3" location="frontend/src/components/CardDetailsPage.tsx:526">
P2: Social link icons use `text-blue-300` on the light theme’s white background, which fails WCAG AA contrast. Use a darker light-theme color with a dark-mode override.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
frontend/src/components/MultiSearch.tsx (1)

287-296: ⚠️ Potential issue | 🟡 Minor

Dark-mode hover on Algolia link is now a no-op.

dark:text-gray-200 and dark:hover:text-gray-200 resolve to the same color, so hovering provides no visual feedback in dark mode. The original likely had dark:text-gray-400 → dark:hover:text-gray-200 which gave a visible lightening effect. Now that the base was bumped to gray-200 for contrast, the hover lost its differentiation.

Consider changing the hover to dark:hover:text-white to restore the feedback while keeping the improved base contrast.

Proposed fix
-              className="flex items-center justify-center gap-2 bg-white py-2 text-gray-500 hover:text-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:text-gray-200"
+              className="flex items-center justify-center gap-2 bg-white py-2 text-gray-500 hover:text-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:text-white"
frontend/src/components/BreadCrumbs.tsx (1)

41-49: ⚠️ Potential issue | 🟡 Minor

Missing dark:hover text color — hover contrast may fail AA in dark mode.

hover:text-blue-700 applies in both light and dark modes. In dark mode, blue-700 (#1d4ed8) on a dark background would have very low contrast (~2–3:1), failing AA. Consider adding a dark-mode hover variant to maintain contrast:

Suggested fix
-                    className="hover:text-blue-700 hover:underline dark:text-blue-300"
+                    className="hover:text-blue-700 hover:underline dark:text-blue-300 dark:hover:text-blue-200"
frontend/src/app/board/[year]/candidates/page.tsx (1)

348-358: ⚠️ Potential issue | 🔴 Critical

Bug: text-blue-300 in light mode severely fails WCAG AA contrast.

On the white card background (bg-white at Line 311), text-blue-300 (#93c5fd) yields roughly a 2.4:1 contrast ratio — well below the 4.5:1 minimum this PR is trying to enforce. Every other link in this file uses the text-blue-600 ... dark:text-blue-300 pattern (e.g., Lines 705, 714, 733). This line is missing the dark: prefix and is using a light-mode color that's worse than what was there before.

🐛 Proposed fix
               <Link
                 href={`https://github.com/${candidate.member.login}`}
                 target="_blank"
                 rel="noopener noreferrer"
-                className="truncate text-sm text-blue-300 hover:underline"
+                className="truncate text-sm text-blue-600 hover:underline dark:text-blue-300"
                 onClick={(e) => e.stopPropagation()}
               >
frontend/src/app/members/[memberKey]/page.tsx (1)

69-76: ⚠️ Potential issue | 🟠 Major

text-blue-300 fails WCAG AA contrast in light mode.

Tailwind's blue-300 (#93c5fd) on white or light backgrounds yields a 1.80:1 contrast ratio—far below the 4.5:1 AA minimum. The previous text-blue-400 was also insufficient at 2.54:1.

Use separate light/dark tokens so both modes pass:

Proposed fix
-            className="text-blue-300 hover:underline"
+            className="text-blue-600 hover:underline dark:text-blue-300"

Light mode: text-blue-600 achieves 5.17:1 on white backgrounds. Dark mode: dark:text-blue-300 achieves 8.14:1 on dark backgrounds.

🤖 Fix all issues with AI agents
In `@frontend/src/app/about/page.tsx`:
- Around line 144-145: The text color class text-blue-300 fails WCAG AA on light
backgrounds across many components; update every bare occurrence of
"text-blue-300" (e.g., in the JSX line rendering feature.title in about/page.tsx
and in Card, RepositoryCard, SingleModuleCard, CardDetailsPage,
ContributorsList, ItemCardList, Release, ShowMoreButton, SnapshotCard, SortBy,
UserCard, ModuleCard, and app routes) to use a light-mode safe color with the
blue-300 shade only in dark mode — i.e., replace bare "text-blue-300" with a
combination that applies blue-700 for light mode and blue-300 for dark mode
(dark:text-blue-300 plus a light-mode class such as text-blue-700) everywhere it
appears.

In `@frontend/src/app/chapters/`[chapterKey]/page.tsx:
- Line 64: Replace the low-contrast Tailwind class "text-blue-300" with a
dark-mode-aware pair so light-mode uses a darker blue and dark-mode keeps the
lighter blue, e.g., change usages like the Link component's className from
"text-blue-300" to "text-blue-700 dark:text-blue-300"; search for other
occurrences (ItemCardList, Release, RepositoryCard, UserCard, SingleModuleCard,
ShowMoreButton, ModuleCard, chapters, organizations, committees, members pages,
etc.) and apply the same replacement to each component or element using
"text-blue-300".

In `@frontend/src/components/ItemCardList.tsx`:
- Line 64: The link text class "text-blue-300" in ItemCardList.tsx (the element
using className "shrink-0 text-blue-300 hover:underline") has insufficient
contrast on its parent "bg-gray-200"; replace it with separate light/dark color
classes so light mode uses a higher-contrast blue (e.g., a darker blue token for
light mode) while keeping the lighter blue for dark mode—update both occurrences
(the one shown and the similar use around line 83) to use conditional classes
like a darker blue for light and text-blue-300 for dark (e.g., text-blue-600 /
dark:text-blue-300) and keep the hover:underline behavior intact.

In `@frontend/src/components/Release.tsx`:
- Line 53: The link text color is too low-contrast for light mode; update the
Tailwind color classes to use a light/dark split that meets WCAG AA (prefer the
suggested combination): replace occurrences like className="shrink-0
text-blue-300 hover:underline" with a variant that uses text-blue-600 for light
and dark:text-blue-400 for dark (e.g., text-blue-600 dark:text-blue-400
hover:underline) in Release.tsx and apply the same pattern to the other affected
components (ItemCardList, ContributorsList, ModuleCard, ShowMoreButton,
SingleModuleCard, Card, RepositoryCard, UserCard, app/page) so links meet
contrast requirements across modes while preserving hover/underline behavior.

In `@frontend/src/components/RepositoryCard.tsx`:
- Line 61: Replace the low-contrast "text-blue-300" in the RepositoryCard
component's className with an accessible light-mode color and keep blue-300 for
dark mode (e.g., use "text-blue-700 dark:text-blue-300" or "text-blue-600
dark:text-blue-300"); update the className on the element inside
RepositoryCard.tsx that currently contains "text-blue-300" so the
higher-contrast blue is used in light mode and blue-300 only applies under the
dark: prefix, then run the provided verification script to confirm no other
occurrences of unprefixed "text-blue-300" remain.
🟠 Major comments (16)
frontend/src/components/Pagination.tsx-83-86 (1)

83-86: ⚠️ Potential issue | 🟠 Major

bg-[#83a6cc] with text-white fails WCAG AA contrast (2.53:1; needs 4.5:1 minimum).

The active-page button uses a contrast ratio of approximately 2.53:1, well below WCAG AA requirements. The dark mode pair (white background + black text) passes at 21:1, so the issue is light mode only.

The proposed color bg-[#4a7ab5] yields only 4.43:1 and still fails. Either darken the background further (e.g., to achieve ≥4.5:1 with white text), or switch to dark text on the current background and adjust accordingly.

frontend/src/app/members/[memberKey]/page.tsx-144-149 (1)

144-149: ⚠️ Potential issue | 🟠 Major

Same text-blue-300 light-mode contrast issue as the bio mention links.

This profile link has the same problem identified on Line 73 — blue-300 fails AA in light mode. Apply the same split-token fix:

Proposed fix
-              className="text-xl font-bold text-blue-300 hover:underline"
+              className="text-xl font-bold text-blue-600 hover:underline dark:text-blue-300"
frontend/src/app/committees/[committeeKey]/page.tsx-65-65 (1)

65-65: ⚠️ Potential issue | 🟠 Major

text-blue-300 without dark: prefix regresses light-mode contrast — use conditional light/dark classes.

On the white background of this CardDetailsPage, text-blue-300 (#93c5fd) delivers only ~1.80:1 contrast ratio, failing WCAG AA (requires 4.5:1) and worsening the prior text-blue-400 (~2.54:1). Dark mode incorrectly benefits (5.72:1 on dark:bg-[#212529]), but applying the lighter blue unconditionally sacrifices light-mode accessibility.

Split into light/dark variants: text-blue-600 achieves 5.17:1 on white (passes AA), while dark:text-blue-300 achieves 5.72:1 on dark backgrounds (passes AA). This pattern has been applied site-wide across multiple components (CardDetailsPage, RepositoryCard, SingleModuleCard, etc.); some files (IssuesTable, MentorshipPullRequest) already use the correct dual-class approach.

Suggested fix
-        <Link href={committee.url} className="text-blue-300 hover:underline">
+        <Link href={committee.url} className="text-blue-600 hover:underline dark:text-blue-300">
frontend/src/app/organizations/[organizationKey]/page.tsx-62-62 (1)

62-62: ⚠️ Potential issue | 🟠 Major

Same light-mode contrast regression as the repository page link.

text-blue-300 without a dark: variant will have poor contrast on light backgrounds. Apply the same split-color fix here.

Proposed fix
-        <Link href={organization.url} className="text-blue-300 hover:underline">
+        <Link href={organization.url} className="text-blue-600 hover:underline dark:text-blue-300">
frontend/src/components/ShowMoreButton.tsx-19-19 (1)

19-19: ⚠️ Potential issue | 🟠 Major

text-blue-300 on transparent background risks poor contrast in light mode.

Since the button uses bg-transparent, the effective background depends on the parent. On light page backgrounds (white/light gray), blue-300 will not meet 4.5:1. Use separate light/dark color tokens.

Proposed fix
-        className="flex items-center bg-transparent px-2 py-1 text-blue-300 hover:underline focus-visible:rounded focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+        className="flex items-center bg-transparent px-2 py-1 text-blue-700 hover:underline focus-visible:rounded focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 dark:text-blue-300"
frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx-72-72 (1)

72-72: ⚠️ Potential issue | 🟠 Major

text-blue-300 fails WCAG AA contrast on light backgrounds.

On the bg-gray-100 background used by SecondaryCard in light mode, text-blue-300 (#93c5fd) has a contrast ratio of only 1.64:1, failing the WCAG AA minimum of 4.5:1. In dark mode on bg-gray-800, the same color achieves 8.14:1, so it needs a mode-aware approach.

Use text-blue-600 for light mode and dark:text-blue-300 for dark mode:

Proposed fix
-        <Link href={repository.url} className="text-blue-300 hover:underline">
+        <Link href={repository.url} className="text-blue-600 hover:underline dark:text-blue-300">

This pattern is already used elsewhere in the codebase (e.g., MentorshipPullRequest.tsx, IssuesTable.tsx).

frontend/src/components/SingleModuleCard.tsx-92-92 (1)

92-92: ⚠️ Potential issue | 🟠 Major

text-blue-300 on light backgrounds (bg-gray-200, bg-gray-100) fails WCAG AA.

Same issue as in SnapshotCard.tsxblue-300 (#93c5fd) against bg-gray-200 (~1.8:1) and bg-gray-100 (~2.2:1) is well below the 4.5:1 minimum and is a regression from the previous blue-400.

Use a dark blue for light mode and a light blue for dark mode:

Proposed fix
-                  className="cursor-pointer overflow-hidden font-semibold text-ellipsis whitespace-nowrap text-blue-300 hover:underline"
+                  className="cursor-pointer overflow-hidden font-semibold text-ellipsis whitespace-nowrap text-blue-700 hover:underline dark:text-blue-300"
-            <h2 className="max-w-full text-2xl font-semibold break-words text-blue-300 transition-colors duration-300 hover:text-blue-600 sm:break-normal">
+            <h2 className="max-w-full text-2xl font-semibold break-words text-blue-700 transition-colors duration-300 hover:text-blue-600 dark:text-blue-300 dark:hover:text-blue-400 sm:break-normal">

Also applies to: 117-117

frontend/src/components/SnapshotCard.tsx-13-13 (1)

13-13: ⚠️ Potential issue | 🟠 Major

text-blue-300 on bg-white fails WCAG AA contrast requirement.

Tailwind's text-blue-300 (#93C5FD) on white yields a 1.80:1 contrast ratio, below the required 4.5:1 for normal text. Line 13 (hover state) and line 27 ("View Snapshot" button text) both use this combination on light backgrounds. Dark mode with text-blue-300 on dark:bg-gray-800 is adequate (8.14:1), but light mode requires a darker blue.

Apply separate light/dark classes on both affected lines:

  • Line 13: text-gray-900 group-hover:text-blue-700 dark:group-hover:text-blue-300
  • Line 27: text-blue-700 dark:text-blue-300

text-blue-700 (#1D4ED8) on white achieves 6.70:1, meeting WCAG AA.

frontend/src/components/UserCard.tsx-86-86 (1)

86-86: ⚠️ Potential issue | 🟠 Major

Same light-mode contrast regression: bare text-blue-300 on a light background.

As flagged in about/page.tsx, text-blue-300 (~#93C5FD) on the card's light background fails WCAG AA. The button label needs a dark blue for light mode.

Proposed fix
-      <div className="flex items-center justify-center text-sm font-medium text-blue-300">
+      <div className="flex items-center justify-center text-sm font-medium text-blue-700 dark:text-blue-300">
frontend/src/components/Card.tsx-57-57 (1)

57-57: ⚠️ Potential issue | 🟠 Major

text-blue-300 in the card title fails light-mode contrast — same issue flagged in other files.

The title link starts at text-blue-300 (~#93C5FD) on the card's bg-white (line 32), giving ~2.2:1 contrast — well below AA thresholds even for large text. The hover state (hover:text-blue-600) is fine, but the default state is the one most users see.

Proposed fix
-              className="max-w-full text-base font-semibold break-words text-blue-300 hover:text-blue-600 sm:text-lg sm:break-normal lg:text-2xl"
+              className="max-w-full text-base font-semibold break-words text-blue-700 hover:text-blue-600 sm:text-lg sm:break-normal lg:text-2xl dark:text-blue-300 dark:hover:text-blue-100"
frontend/src/components/ContributorsList.tsx-62-65 (1)

62-65: ⚠️ Potential issue | 🟠 Major

text-blue-300 on bg-gray-200 fails WCAG AA contrast in light mode (1.46:1, needs 4.5:1).

The change from text-blue-400 to text-blue-300 improves dark-mode contrast (5.72:1 on bg-gray-700) but creates a regression in light mode. text-blue-300 (#93C5FD) produces only 1.46:1 against bg-gray-200 (#E5E7EB)—below the AA threshold and worse than the previous blue-400 (2.05:1).

Use separate light/dark variants to pass AA in both modes:

Proposed fix
 <Link
-  className="cursor-pointer overflow-hidden font-semibold text-ellipsis whitespace-nowrap text-blue-300 hover:underline"
+  className="cursor-pointer overflow-hidden font-semibold text-ellipsis whitespace-nowrap text-blue-700 hover:underline dark:text-blue-300"
   href={getUrl(item?.login)}
   title={item?.name || item?.login}
 >

text-blue-700 (#1D4ED8) on bg-gray-200 gives 5.41:1 and dark:text-blue-300 on dark:bg-gray-700 gives 5.72:1—both pass AA.

frontend/src/app/page.tsx-382-382 (1)

382-382: ⚠️ Potential issue | 🟠 Major

Stat counter text-blue-300 also fails the large-text AA threshold (3:1).

Even though these are text-3xl font-bold (qualifying as large text under WCAG, requiring only 3:1), blue-300 on a light background yields ~1.8:1, which still fails. Consider using text-blue-600 dark:text-blue-300 here as well.

frontend/src/app/page.tsx-170-170 (1)

170-170: ⚠️ Potential issue | 🟠 Major

text-blue-300 on bg-gray-200 cards fails WCAG AA in light mode (~1.5:1).

All four link locations sit on bg-gray-200 in light mode. Same issue as flagged in ModuleCard.tsx and CardDetailsPage.tsx — blue-300 is too light for any light background. A dual-token approach is needed:

Proposed fix (apply to each occurrence)
-  className="... text-blue-300 hover:underline"
+  className="... text-blue-600 hover:underline dark:text-blue-300"

Also applies to: 231-231, 274-274, 357-357

frontend/src/components/CardDetailsPage.tsx-426-426 (1)

426-426: ⚠️ Potential issue | 🟠 Major

text-blue-300 fails WCAG AA in light mode on bg-gray-200 (~1.5:1).

These milestone links sit on bg-gray-200 in light mode. text-blue-300 (#93C5FD) on bg-gray-200 (#E5E7EB) produces a contrast ratio of roughly 1.5:1 — far below the 4.5:1 requirement. The dark-mode contrast (on dark:bg-gray-700) is fine at ~6.4:1.

Same root cause as flagged in ModuleCard.tsx — needs a dark-enough light-mode blue paired with dark:text-blue-300.

Proposed fix
-                              className="shrink-0 text-blue-300 hover:underline"
+                              className="shrink-0 text-blue-600 hover:underline dark:text-blue-300"
-                              className="text-blue-300 hover:underline"
+                              className="text-blue-600 hover:underline dark:text-blue-300"

Also applies to: 447-447

frontend/src/components/CardDetailsPage.tsx-526-526 (1)

526-526: ⚠️ Potential issue | 🟠 Major

text-blue-300 on the page's white background fails AA in light mode (~1.8:1).

Social icon links inherit the main container's bg-white in light mode. Same root cause as the milestone links above.

Proposed fix
-              className="text-blue-300 transition-colors hover:text-gray-800 dark:hover:text-gray-200"
+              className="text-blue-600 transition-colors hover:text-gray-800 dark:text-blue-300 dark:hover:text-gray-200"
frontend/src/components/ModuleCard.tsx-49-49 (1)

49-49: ⚠️ Potential issue | 🟠 Major

text-blue-300 fails WCAG AA in light mode (~1.8:1 on white).

Unlike IssuesTable.tsx (line 127), which correctly uses text-blue-600 dark:text-blue-300 to pass AA in both modes, this button uses a single text-blue-300 for both. Tailwind blue-300 (#93C5FD) against white or bg-gray-200 yields a contrast ratio well below 4.5:1.

Consider splitting the token so light mode uses a sufficiently dark blue:

Proposed fix
-            className="mt-4 flex items-center justify-center text-blue-300 hover:underline focus:rounded focus:outline-2 focus:outline-offset-2 focus:outline-blue-500"
+            className="mt-4 flex items-center justify-center text-blue-600 hover:underline focus:rounded focus:outline-2 focus:outline-offset-2 focus:outline-blue-500 dark:text-blue-300"
🟡 Minor comments (4)
frontend/src/components/BreadCrumbs.tsx-32-32 (1)

32-32: ⚠️ Potential issue | 🟡 Minor

Inconsistent dark-mode text shade: dark:text-gray-300 vs dark:text-gray-200.

The PR objectives and most other files in this changeset standardize on dark:text-gray-200 as the replacement for dark:text-gray-400. This line uses dark:text-gray-300 instead. While gray-300 still passes AA on typical dark backgrounds, it's inconsistent with the rest of the PR.

Suggested fix
-                    className="cursor-default font-semibold text-gray-800 dark:text-gray-300"
+                    className="cursor-default font-semibold text-gray-800 dark:text-gray-200"
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx-62-64 (1)

62-64: ⚠️ Potential issue | 🟡 Minor

Biome flags: arrow-function bodies in forEach callbacks implicitly return a value.

The arrow => without braces makes each callback an expression-body that returns the inner forEach result. While harmless at runtime (return value is ignored), this triggers the useIterableCallbackReturn lint rule and the leading ; on line 62 is an unusual ASI-guard that hurts readability.

Proposed fix — use block bodies to silence the lint and improve clarity
-      ; (moduleData?.issues || []).forEach((i) =>
-        (i.labels || []).forEach((l: string) => labels.add(l))
-      )
+      ;(moduleData?.issues || []).forEach((i) => {
+        (i.labels || []).forEach((l: string) => {
+          labels.add(l)
+        })
+      })
frontend/src/components/MentorshipPullRequest.tsx-47-47 (1)

47-47: ⚠️ Potential issue | 🟡 Minor

Dark-mode hover state is identical to the base state — no visual feedback.

dark:text-blue-300 and dark:hover:text-blue-300 resolve to the same color, so hovering the link provides zero visual feedback in dark mode. This likely happened when dark:text-blue-400 was bumped to dark:text-blue-300 without also adjusting the hover variant.

Proposed fix
-            className="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-300"
+            className="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-100"
frontend/src/components/MentorshipPullRequest.tsx-51-53 (1)

51-53: ⚠️ Potential issue | 🟡 Minor

text-gray-500 on bg-gray-200 falls short of WCAG AA contrast requirements.

text-gray-500 (#6B7280) on bg-gray-200 (#E5E7EB) produces a 4.10:1 contrast ratio, which is below the 4.5:1 AA threshold for normal text. Since this was introduced in a PR specifically targeting WCAG compliance, consider upgrading to text-gray-700 (4.92:1) or text-gray-800 (6.20:1) for consistency with the PR's accessibility goals.

🧹 Nitpick comments (6)
frontend/src/app/board/[year]/candidates/page.tsx (1)

341-341: No hover feedback for LinkedIn icon in dark mode.

dark:text-blue-300 dark:hover:text-blue-300 are identical, so the icon has no visual change on hover in dark mode. Consider using a slightly different shade (e.g., dark:hover:text-blue-200) for interactive affordance.

frontend/src/app/members/[memberKey]/page.tsx (1)

143-143: Dark mode fix looks correct; light-mode text-gray-500 should be verified.

The dark:text-gray-200 update is a good improvement. However, text-gray-500 (#6b7280) passes AA on pure white (~5.9:1) but may not on slightly tinted or gray backgrounds. If the parent card has a non-white light-mode background, consider bumping to text-gray-700 for more headroom.

frontend/src/components/forms/shared/FormTextInput.tsx (1)

5-12: LGTM!

Label contrast improved to text-gray-800 in light mode. The COMMON_INPUT_CLASS_NAMES object here is now identical to the one in FormDateInput.tsx — consider extracting it into a shared module to keep them in sync, but that's a pre-existing concern and not blocking.

frontend/src/components/forms/shared/FormTextarea.tsx (1)

43-44: Minor formatting nit: awkward line break inside template literal.

The closing } of the ternary sits alone on Line 44 with extra indentation. Consider reformatting to keep the template literal more readable.

🔧 Suggested formatting
-          className={`w-full min-w-0 rounded-lg border px-3 py-2 text-gray-800 placeholder:text-gray-400 focus:border-[`#1D7BD7`] focus:ring-1 focus:ring-[`#1D7BD7`] focus:outline-none dark:bg-gray-800 dark:text-gray-200 dark:focus:ring-[`#1D7BD7`] ${hasError ? 'border-red-500 dark:border-red-500' : 'border-gray-300 dark:border-gray-600'
-            }`}
+          className={`w-full min-w-0 rounded-lg border px-3 py-2 text-gray-800 placeholder:text-gray-400 focus:border-[`#1D7BD7`] focus:ring-1 focus:ring-[`#1D7BD7`] focus:outline-none dark:bg-gray-800 dark:text-gray-200 dark:focus:ring-[`#1D7BD7`] ${hasError ? 'border-red-500 dark:border-red-500' : 'border-gray-300 dark:border-gray-600'}`}
frontend/src/components/Modal.tsx (1)

43-43: Contrast improvements look good; minor dark-mode inconsistency between summary and hint sections.

Both sections now use text-gray-800 in light mode (good). In dark mode, the summary uses dark:text-gray-300 (Line 43) while the hint uses dark:text-gray-200 (Line 55). Both pass AA, but the inconsistency within the same modal may be unintentional — consider aligning them to the same shade.

🔧 Align dark-mode text color for both Markdown sections
          <Markdown
            content={summary}
-            className="text-base [overflow-wrap:anywhere] break-words text-gray-800 dark:text-gray-300"
+            className="text-base [overflow-wrap:anywhere] break-words text-gray-800 dark:text-gray-200"
          />

Also applies to: 55-55

frontend/src/components/SnapshotCard.tsx (1)

18-18: Minor inconsistency: dark:text-gray-300 instead of dark:text-gray-200.

All other files in this PR use dark:text-gray-200 for dark-mode text, but this line uses dark:text-gray-300. Both pass WCAG AA on dark:bg-gray-800, but if the intent is a consistent token across the codebase, consider aligning to dark:text-gray-200.

label: 'URL',
value: (
<Link href={chapter.url} className="text-blue-400 hover:underline">
<Link href={chapter.url} className="text-blue-300 hover:underline">
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and read the specific file to confirm line 64
find . -type f -name "page.tsx" -path "*chapters*" | head -5

Repository: OWASP/Nest

Length of output: 140


🏁 Script executed:

# Search for text-blue-300 instances in the codebase
rg -n 'text-blue-300' --type=tsx --type=ts --type=jsx -B 2 -A 2

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Search for text-blue-400 to see what was replaced
rg -n 'text-blue-400' --type=tsx --type=ts --type=jsx

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Check the specific file mentioned in the review
cat -n frontend/src/app/chapters/[chapterKey]/page.tsx | sed -n '55,75p'

Repository: OWASP/Nest

Length of output: 752


🏁 Script executed:

# Search for text-blue-300 without type restriction
rg 'text-blue-300' -n --glob '*.tsx' --glob '*.jsx' --glob '*.ts' --glob '*.js'

Repository: OWASP/Nest

Length of output: 9563


🏁 Script executed:

# Search for text-blue-400 to understand what was changed
rg 'text-blue-400' -n --glob '*.tsx' --glob '*.jsx' --glob '*.ts' --glob '*.js'

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Check git history for this specific file to see what changed
git diff HEAD~1 frontend/src/app/chapters/[chapterKey]/page.tsx 2>/dev/null || echo "Git diff not available"

Repository: OWASP/Nest

Length of output: 76


🏁 Script executed:

# Check the ActionButton.tsx file mentioned in learnings to understand design patterns
cat frontend/src/components/ActionButton.tsx | grep -n 'text-\|className' | head -20

Repository: OWASP/Nest

Length of output: 335


text-blue-300 on light backgrounds causes WCAG AA contrast failure — this is widespread across 54+ files.

Tailwind's blue-300 (#93c5fd) on white yields approximately 2.8:1 contrast ratio, well below the 4.5:1 AA requirement. The class is applied unconditionally (no dark: prefix) across ItemCardList, Release, RepositoryCard, UserCard, SingleModuleCard, ShowMoreButton, ModuleCard, chapters, organizations, committees, members pages and many others, meaning it affects light mode.

Use a dark variant for light mode and the lighter blue for dark mode:

-        <Link href={chapter.url} className="text-blue-300 hover:underline">
+        <Link href={chapter.url} className="text-blue-600 hover:underline dark:text-blue-300">

This pattern requires fixing across the entire PR — not just this one file.

📝 Committable suggestion

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

Suggested change
<Link href={chapter.url} className="text-blue-300 hover:underline">
<Link href={chapter.url} className="text-blue-600 hover:underline dark:text-blue-300">
🤖 Prompt for AI Agents
In `@frontend/src/app/chapters/`[chapterKey]/page.tsx at line 64, Replace the
low-contrast Tailwind class "text-blue-300" with a dark-mode-aware pair so
light-mode uses a darker blue and dark-mode keeps the lighter blue, e.g., change
usages like the Link component's className from "text-blue-300" to
"text-blue-700 dark:text-blue-300"; search for other occurrences (ItemCardList,
Release, RepositoryCard, UserCard, SingleModuleCard, ShowMoreButton, ModuleCard,
chapters, organizations, committees, members pages, etc.) and apply the same
replacement to each component or element using "text-blue-300".

>
<Link
className="shrink-0 text-blue-400 hover:underline"
className="shrink-0 text-blue-300 hover:underline"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the Release.tsx file around lines 53 and 72
head -100 frontend/src/components/Release.tsx | tail -60

Repository: OWASP/Nest

Length of output: 2710


🏁 Script executed:

# Check the git diff to see what was changed
git diff HEAD frontend/src/components/Release.tsx | head -100

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Find all text-blue-300 instances without dark: prefix
rg 'text-blue-300(?!.*dark:)' --type=tsx frontend/src/components/ -A 1 -B 1

Repository: OWASP/Nest

Length of output: 294


🏁 Script executed:

# Let's also verify the exact color values from Tailwind config
# and calculate contrast ratio properly
python3 << 'EOF'
from colorsys import rgb_to_hls

def hex_to_rgb(hex_color):
    hex_color = hex_color.lstrip('#')
    return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

def relative_luminance(r, g, b):
    """Calculate relative luminance per WCAG formula"""
    r, g, b = r/255.0, g/255.0, b/255.0
    r = r/12.92 if r <= 0.03928 else ((r+0.055)/1.055)**2.4
    g = g/12.92 if g <= 0.03928 else ((g+0.055)/1.055)**2.4
    b = b/12.92 if b <= 0.03928 else ((b+0.055)/1.055)**2.4
    return 0.2126*r + 0.7152*g + 0.0722*b

def contrast_ratio(color1, color2):
    """Calculate contrast ratio between two hex colors"""
    r1, g1, b1 = hex_to_rgb(color1)
    r2, g2, b2 = hex_to_rgb(color2)
    l1 = relative_luminance(r1, g1, b1)
    l2 = relative_luminance(r2, g2, b2)
    lighter = max(l1, l2)
    darker = min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)

# Tailwind colors
gray_200 = '#e5e7eb'  # bg-gray-200
gray_700 = '#374151'  # dark:bg-gray-700
blue_300 = '#93c5fd'  # text-blue-300
blue_400 = '#60a5fa'  # text-blue-400
blue_600 = '#2563eb'  # text-blue-600
blue_700 = '#1d4ed8'  # text-blue-700

# Test original claim
ratio_blue300_on_gray200 = contrast_ratio(blue_300, gray_200)
ratio_blue400_on_gray200 = contrast_ratio(blue_400, gray_200)
ratio_blue600_on_gray200 = contrast_ratio(blue_600, gray_200)
ratio_blue700_on_gray200 = contrast_ratio(blue_700, gray_200)

print(f"text-blue-300 (`#93c5fd`) on bg-gray-200 (`#e5e7eb`): {ratio_blue300_on_gray200:.2f}:1")
print(f"text-blue-400 (`#60a5fa`) on bg-gray-200 (`#e5e7eb`): {ratio_blue400_on_gray200:.2f}:1")
print(f"text-blue-600 (`#2563eb`) on bg-gray-200 (`#e5e7eb`): {ratio_blue600_on_gray200:.2f}:1")
print(f"text-blue-700 (`#1d4ed8`) on bg-gray-200 (`#e5e7eb`): {ratio_blue700_on_gray200:.2f}:1")
print()

# Dark mode
ratio_blue300_on_gray700 = contrast_ratio(blue_300, gray_700)
ratio_blue400_on_gray700 = contrast_ratio(blue_400, gray_700)

print(f"text-blue-300 (`#93c5fd`) on dark:bg-gray-700 (`#374151`): {ratio_blue300_on_gray700:.2f}:1")
print(f"text-blue-400 (`#60a5fa`) on dark:bg-gray-700 (`#374151`): {ratio_blue400_on_gray700:.2f}:1")
print()
print(f"WCAG AA requires 4.5:1 minimum for normal text")
print(f"WCAG AAA requires 7:1 minimum for normal text")
EOF

Repository: OWASP/Nest

Length of output: 500


🏁 Script executed:

# Check parent container - look at more context above line 40
head -60 frontend/src/components/Release.tsx | cat -n

Repository: OWASP/Nest

Length of output: 2497


🏁 Script executed:

# Find all text-blue-300 instances using PCRE2 flag for look-ahead
rg 'text-blue-300' --type=tsx frontend/src/ -B 2 -A 2

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Check if there's a parent container with bg-gray-200 in Release.tsx
rg 'bg-gray-200' frontend/src/components/Release.tsx -B 5 -A 5

Repository: OWASP/Nest

Length of output: 408


🏁 Script executed:

# Find all text-blue-300 instances in the frontend
rg 'text-blue-300' frontend/src/ -A 2 -B 2

Repository: OWASP/Nest

Length of output: 27495


🏁 Script executed:

# Also check git log to see if there's context about this change
git log --oneline -20 -- frontend/src/components/Release.tsx

Repository: OWASP/Nest

Length of output: 116


🏁 Script executed:

# Check if there are recent changes showing blue-400 → blue-300 pattern
git diff HEAD~5 HEAD frontend/src/components/ | grep -A 3 -B 3 'blue-[34]00' | head -60

Repository: OWASP/Nest

Length of output: 240


🏁 Script executed:

# Let's check if there's a parent container context that might affect the background
rg 'bg-gray-200|dark:bg-gray-700' frontend/src/components/Release.tsx -B 5 -A 10 | head -40

Repository: OWASP/Nest

Length of output: 604


🏁 Script executed:

# Let's look at what files with text-blue-300 have bg-gray-200 containers
# to understand the scope of the issue
rg 'text-blue-300' frontend/src/app/page.tsx -B 10 | grep -E '(bg-gray|text-blue-300)'

Repository: OWASP/Nest

Length of output: 864


🏁 Script executed:

# Verify if the proposed fix in the review would work by checking light/dark contrast
python3 << 'EOF'
def hex_to_rgb(hex_color):
    hex_color = hex_color.lstrip('#')
    return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

def relative_luminance(r, g, b):
    r, g, b = r/255.0, g/255.0, b/255.0
    r = r/12.92 if r <= 0.03928 else ((r+0.055)/1.055)**2.4
    g = g/12.92 if g <= 0.03928 else ((g+0.055)/1.055)**2.4
    b = b/12.92 if b <= 0.03928 else ((b+0.055)/1.055)**2.4
    return 0.2126*r + 0.7152*g + 0.0722*b

def contrast_ratio(color1, color2):
    r1, g1, b1 = hex_to_rgb(color1)
    r2, g2, b2 = hex_to_rgb(color2)
    l1 = relative_luminance(r1, g1, b1)
    l2 = relative_luminance(r2, g2, b2)
    lighter = max(l1, l2)
    darker = min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)

gray_200 = '#e5e7eb'
gray_700 = '#374151'
blue_700 = '#1d4ed8'

print("Proposed fix verification:")
print(f"text-blue-700 (`#1d4ed8`) on bg-gray-200 (`#e5e7eb`): {contrast_ratio(blue_700, gray_200):.2f}:1")
print(f"text-blue-700 (`#1d4ed8`) on dark:bg-gray-700 (`#374151`): {contrast_ratio(blue_700, gray_700):.2f}:1")
print()
print("Both exceed 4.5:1 WCAG AA minimum ✓" if contrast_ratio(blue_700, gray_200) >= 4.5 and contrast_ratio(blue_700, gray_700) >= 4.5 else "At least one fails")
EOF

Repository: OWASP/Nest

Length of output: 219


🏁 Script executed:

# The proposed fix doesn't work - blue-700 fails in dark mode.
# Let's find what blue shades work in both modes
python3 << 'EOF'
def hex_to_rgb(hex_color):
    hex_color = hex_color.lstrip('#')
    return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

def relative_luminance(r, g, b):
    r, g, b = r/255.0, g/255.0, b/255.0
    r = r/12.92 if r <= 0.03928 else ((r+0.055)/1.055)**2.4
    g = g/12.92 if g <= 0.03928 else ((g+0.055)/1.055)**2.4
    b = b/12.92 if b <= 0.03928 else ((b+0.055)/1.055)**2.4
    return 0.2126*r + 0.7152*g + 0.0722*b

def contrast_ratio(color1, color2):
    r1, g1, b1 = hex_to_rgb(color1)
    r2, g2, b2 = hex_to_rgb(color2)
    l1 = relative_luminance(r1, g1, b1)
    l2 = relative_luminance(r2, g2, b2)
    lighter = max(l1, l2)
    darker = min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)

gray_200 = '#e5e7eb'
gray_700 = '#374151'

blues = {
    'blue-500': '#3b82f6',
    'blue-600': '#2563eb',
    'blue-700': '#1d4ed8',
    'blue-400': '#60a5fa',
    'blue-300': '#93c5fd',
}

print("Finding blues that work in BOTH light and dark modes (≥4.5:1):\n")
print(f"{'Blue shade':<15} {'Light (gray-200)':<20} {'Dark (gray-700)':<20} {'Both OK?':<10}")
print("-" * 65)

for shade, color in blues.items():
    light_ratio = contrast_ratio(color, gray_200)
    dark_ratio = contrast_ratio(color, gray_700)
    both_ok = "✓" if light_ratio >= 4.5 and dark_ratio >= 4.5 else "✗"
    print(f"{shade:<15} {light_ratio:>6.2f}:1 {'✓' if light_ratio >= 4.5 else '✗':<12} {dark_ratio:>6.2f}:1 {'✓' if dark_ratio >= 4.5 else '✗':<12} {both_ok:<10}")
EOF

Repository: OWASP/Nest

Length of output: 607


🏁 Script executed:

# Check if there are any instances with proper dark: variants already in the codebase
# that show the correct pattern
rg 'text-blue.*dark:text-blue' frontend/src/ -A 1 -B 1 | head -30

Repository: OWASP/Nest

Length of output: 2675


text-blue-300 on bg-gray-200 fails WCAG AA in light mode — text is barely distinguishable.

Confirmed: text-blue-300 (#93c5fd) on bg-gray-200 (#e5e7eb) yields a contrast ratio of 1.46:1, well below the 4.5:1 minimum. This is a critical accessibility issue affecting actual text links, not just images.

However, there is a fundamental constraint: no single Tailwind blue shade achieves 4.5:1 contrast in both light mode (gray-200) and dark mode (gray-700). The design requires a split approach:

  • Light mode needs blue-600+ (4.17:1 minimum)
  • Dark mode needs blue-300+ (5.72:1 minimum)

The proposed text-blue-700 dark:text-blue-300 fix fails in dark mode (1.54:1), introducing a regression worse than the original blue-400 (4.05:1).

Proper solutions:

  1. Use a light/dark variant that prioritizes light mode: text-blue-600 dark:text-blue-400
  2. Change the container backgrounds to enable better contrast with a single blue
  3. Adopt a consistent theme strategy across light and dark modes

This issue spans multiple components (Release.tsx, ItemCardList.tsx, ContributorsList.tsx, ModuleCard.tsx, ShowMoreButton.tsx, SingleModuleCard.tsx, Card.tsx, RepositoryCard.tsx, UserCard.tsx, and app/page.tsx).

🤖 Prompt for AI Agents
In `@frontend/src/components/Release.tsx` at line 53, The link text color is too
low-contrast for light mode; update the Tailwind color classes to use a
light/dark split that meets WCAG AA (prefer the suggested combination): replace
occurrences like className="shrink-0 text-blue-300 hover:underline" with a
variant that uses text-blue-600 for light and dark:text-blue-400 for dark (e.g.,
text-blue-600 dark:text-blue-400 hover:underline) in Release.tsx and apply the
same pattern to the other affected components (ItemCardList, ContributorsList,
ModuleCard, ShowMoreButton, SingleModuleCard, Card, RepositoryCard, UserCard,
app/page) so links meet contrast requirements across modes while preserving
hover/underline behavior.

Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

Please follow our contributing workflow and use our standard PR template. https://github.com/OWASP/Nest/blob/main/CONTRIBUTING.md#contributing-workflow

@kasya
Copy link
Collaborator

kasya commented Feb 6, 2026

Looks like most of these are now failing WCAG AA rather than getting fixed 👀

@Srizdebnath
Copy link
Author

Thank you for the detailed review @kasya and @arkid15r.
I see the regression I introduced with the blue links in light mode. I mistakenly applied the dark-mode fix (blue-300) to the light mode classes as well.
Plan:
I will revert the light-mode links to a darker, accessible shade (text-blue-600 or text-blue-700) while keeping dark:text-blue-300 for dark mode.
I will resolve the merge conflicts.
I will update the PR description to match the official template as requested.
Working on this now.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/components/CardDetailsPage.tsx (1)

521-527: ⚠️ Potential issue | 🔴 Critical

text-blue-300 on bg-white fails WCAG AA in light mode — same regression as the avatar link.

The SocialLinks component inherits the bg-white main container background (line 136). Tailwind's blue-300 (#93c5fd) on white yields roughly 2.5:1 contrast — well below the 4.5:1 requirement. A dark-mode qualifier is needed here just like on line 447.

Also note the hover states: hover:text-gray-800 is fine for light mode, but dark:hover:text-gray-200 only styles the hover. The default state in dark mode is still blue-300, which needs to be verified against dark:bg-[#212529] (likely passes, ~6:1), so only the light-mode base color needs fixing.

Proposed fix
                          className="text-blue-300 transition-colors hover:text-gray-800 dark:hover:text-gray-200"
+                         className="text-blue-700 transition-colors hover:text-blue-900 dark:text-blue-300 dark:hover:text-gray-200"
frontend/__tests__/unit/pages/About.test.tsx (1)

1-9: ⚠️ Potential issue | 🔴 Critical

Add missing imports and define missing mock data.

The test uses GetProjectMetadataDocument, GetTopContributorsDocument, GetLeaderDataDocument, mockProjectData, mockUserData, and mockTopContributorsData without importing or defining them. This will cause ReferenceError and TypeError failures at runtime across multiple test cases (lines 203–221, 462–520, 585, 611, 697–711).

Additionally, mockUserData is called as a function mockUserData(key) on lines 215, 466, 520, and 709, but the exported mock is a plain object—this will fail with TypeError: mockUserData is not a function.

🔧 Required changes
  1. Add missing imports:
 import { useQuery } from '@apollo/client/react'
 import { addToast } from '@heroui/toast'
 import { mockAboutData } from '@mockData/mockAboutData'
+import { mockProjectData } from '@mockData/mockProjectData'
+import { mockUserData } from '@mockData/mockUserData'
 import { fireEvent, screen, waitFor, within } from '@testing-library/react'
 import { useRouter } from 'next/navigation'
 import React, { act } from 'react'
 import { render } from 'wrappers/testUtil'
 import About from 'app/about/page'
 import { GetAboutPageDataDocument } from 'types/__generated__/aboutQueries.generated'
+import { GetProjectMetadataDocument, GetTopContributorsDocument } from 'types/__generated__/projectQueries.generated'
+import { GetLeaderDataDocument } from 'types/__generated__/userQueries.generated'
  1. Create mockTopContributorsData (missing from codebase) or define it in the test file:
+import { mockAboutData } from '@mockData/mockAboutData'
+
+const mockTopContributorsData = mockAboutData.topContributors
  1. Fix mockUserData usage: either convert it to a factory function that returns user data by key, or use mockAboutData.users[key] directly.
🤖 Fix all issues with AI agents
In `@frontend/__tests__/unit/pages/About.test.tsx`:
- Line 177: The test uses a mock string with className "flex items-center
bg-transparent px-0 text-blue-300" which mirrors the component but uses a
too-light color; update the component's class usage and the test mock so the
light-mode text class is a darker shade (e.g., replace text-blue-300 with
text-blue-600 or text-blue-700) and keep the lighter color only as a dark:
variant (e.g., add dark:text-blue-300), then update the About.test.tsx mock to
match the new className string so the test reflects the accessible styling
change.
- Around line 635-695: Remove the duplicated Jest tests in the About.test.tsx
file by deleting the repeated test blocks with these names: "renders mission and
who its for sections correctly", "renders mission section", "renders 'Who It's
For' section", "renders key features section" (or "renders key features section
correctly"), "renders get involved section" (or "renders get involved section
correctly"), and "renders project history timeline section" (or "renders project
history timeline correctly"); keep only the original instances of these tests
(the first occurrences) so each assertion set (render(<About />) +
screen.getByText(...) expectations) appears exactly once.
- Around line 560-571: The mock implementation for useQuery inside the test
'renders ErrorDisplay when project is null' contains an unreachable final
"return { loading: true }" because the prior branches already return
unconditionally; remove that dead return (or restructure the mockImplementation
to use if/else if/else and only return once) so that useQuery mock only returns
the intended objects; reference the test name and the useQuery
mockImplementation to locate and fix the dead code.
- Around line 203-221: The beforeEach mock for useQuery references undefined
symbols from other pages; replace that mockImplementation to only handle
useQuery(GetAboutPageDataDocument) by reading variables.projectKey (and other
variables like excludedUsernames and leader usernames if present) and return the
imported mockAboutData, remove any branches for
GetProjectMetadataDocument/GetTopContributorsDocument/GetLeaderDataDocument and
references to mockProjectData/mockTopContributorsData/mockUserData, and fix the
malformed whitespace before the useRouter mock by ensuring mockRouter is created
(mockRouter = { push: jest.fn() }) and then calling (useRouter as
jest.Mock).mockReturnValue(mockRouter) without the stray leading semicolon.

In
`@frontend/src/app/organizations/`[organizationKey]/repositories/[repositoryKey]/page.tsx:
- Line 68: Replace the low-contrast Tailwind class on link elements so they meet
WCAG AA: in this file update the Link that renders repository.url (and all other
link components across chapters, committees, members, organizations, homepage)
to use "text-blue-600 dark:text-blue-300" instead of the bare "text-blue-300";
search for className="text-blue-300" (e.g., the Link in page.tsx) and change it
to include the dark: variant to preserve current dark-mode styling while
improving light-background contrast.

In `@frontend/src/components/CardDetailsPage.tsx`:
- Around line 425-427: The link showing the milestone author currently uses
text-blue-300 unconditionally which fails contrast in light mode; update the
Link rendering that targets `/members/${milestone?.author?.login}` (the Link
element around milestone?.author?.login) to use a dual-mode class like
"text-blue-700 dark:text-blue-300" (preserving existing classes such as
"shrink-0" and "hover:underline") so light mode uses an accessible darker blue
and dark mode keeps the lighter blue.
🧹 Nitpick comments (3)
frontend/__tests__/unit/pages/About.test.tsx (3)

462-469: Null-project test doesn't account for GetTopContributorsDocument or GetProjectMetadataDocument routing.

The mock on lines 462–468 only branches on options?.variables?.key === 'nest' without checking query, so it returns { data: { project: null }, ... } for both GetProjectMetadataDocument and GetTopContributorsDocument when key is 'nest'. If the component handles these independently, the test may pass for the wrong reason. Compare with the beforeEach mock that branches on query first.

♻️ Suggested improvement — branch on query type
     ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => {
-      if (options?.variables?.key === 'nest') {
+      if (query === GetProjectMetadataDocument && options?.variables?.key === 'nest') {
         return { data: { project: null }, loading: false, error: null }
+      } else if (query === GetTopContributorsDocument && options?.variables?.key === 'nest') {
+        return mockTopContributorsData
       } else if (['arkid15r', 'kasya', 'mamicidal'].includes(options?.variables?.key)) {
         return mockUserData(options?.variables?.key)
       }
       return { loading: true }
     })

583-607: Error tests for project and topContributors return dummy user data for all non-matching queries, hiding potential issues.

Both mocks fall through to returning { data: { user: { ... } }, ... } for every other query, including GetProjectMetadataDocument and GetTopContributorsDocument (whichever isn't being tested). This means the component receives a response shape (data.user) that doesn't match the expected schema for those queries, which may silently mask bugs. Consider returning realistic mock data for the non-error queries so the test better reflects real behavior.

Also applies to: 609-633


697-728: Leader error test: good coverage, but the duplicate GetLeaderDataDocument branch (lines 699 vs 708) could be fragile.

The mock correctly short-circuits for arkid15r with an error on line 699, then falls through to a second GetLeaderDataDocument check on line 708 for other leaders. This works because the first if matches before the second, but it relies on ordering and makes the intent unclear. A single branch with a conditional would be clearer.

♻️ Consolidate the leader branches
     ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => {
-      if (query === GetLeaderDataDocument && options?.variables?.key === 'arkid15r') {
-        return { loading: false, data: null, error: new Error('GraphQL error for leader') }
-      }
-      if (query === GetProjectMetadataDocument) {
+      if (query === GetProjectMetadataDocument) {
         return mockProjectData
       }
       if (query === GetTopContributorsDocument) {
         return mockTopContributorsData
       }
-      if (query === GetLeaderDataDocument) {
-        return mockUserData(options?.variables?.key)
+      if (query === GetLeaderDataDocument) {
+        if (options?.variables?.key === 'arkid15r') {
+          return { loading: false, data: null, error: new Error('GraphQL error for leader') }
+        }
+        return mockUserData(options?.variables?.key)
       }
       return { loading: true }
     })

Comment on lines 203 to 221
;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => {
const key = options?.variables?.key

if (query === GetProjectMetadataDocument) {
if (key === 'nest') {
return mockProjectData
}
} else if (query === GetTopContributorsDocument) {
if (key === 'nest') {
return mockTopContributorsData
}
} else if (query === GetLeaderDataDocument) {
return mockUserData(key)
}

return { loading: true }
})
mockRouter = { push: jest.fn() }
;(useRouter as jest.Mock).mockReturnValue(mockRouter)
; (useRouter as jest.Mock).mockReturnValue(mockRouter)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd "About" --full-path --glob '**/*.tsx' --type f | grep -E "(test|page)" | head -20

Repository: OWASP/Nest

Length of output: 149


🏁 Script executed:

fd "About" --type f | head -20

Repository: OWASP/Nest

Length of output: 288


🏁 Script executed:

# First, let's look at the imports and the mock section in About.test.tsx
cat -n frontend/__tests__/unit/pages/About.test.tsx | head -100

Repository: OWASP/Nest

Length of output: 4082


🏁 Script executed:

# Continue reading to find the mock setup around lines 203-221
cat -n frontend/__tests__/unit/pages/About.test.tsx | sed -n '100,230p'

Repository: OWASP/Nest

Length of output: 4225


🏁 Script executed:

# Check if there are more imports in the test file or if those constants are defined
cat -n frontend/__tests__/unit/pages/About.test.tsx | grep -E "(import|GetProjectMetadata|GetTopContributor|GetLeaderData|mockProjectData|mockTopContributorsData|mockUserData)"

Repository: OWASP/Nest

Length of output: 1883


🏁 Script executed:

# Check if these constants/functions are defined anywhere in the About.test.tsx file
rg -n "GetProjectMetadataDocument|GetTopContributorsDocument|GetLeaderDataDocument|mockProjectData|mockTopContributorsData|mockUserData" frontend/__tests__/unit/pages/About.test.tsx | grep -E "(const|import|function|export|=\s*)"

Repository: OWASP/Nest

Length of output: 847


🏁 Script executed:

# Look at the entire file to see if these are defined
wc -l frontend/__tests__/unit/pages/About.test.tsx

Repository: OWASP/Nest

Length of output: 102


🏁 Script executed:

# Read the entire file to find where these constants are defined
cat -n frontend/__tests__/unit/pages/About.test.tsx | tail -n +1 | head -n 400

Repository: OWASP/Nest

Length of output: 15954


🏁 Script executed:

# Let's check the About page component to see what queries it actually uses
cat -n frontend/src/app/about/page.tsx | head -50

Repository: OWASP/Nest

Length of output: 1999


🏁 Script executed:

# Continue reading to see how GetAboutPageDataDocument is used
cat -n frontend/src/app/about/page.tsx | sed -n '50,150p'

Repository: OWASP/Nest

Length of output: 3967


🏁 Script executed:

# Check what's actually in the aboutQueries.generated file
cat -n frontend/src/types/__generated__/aboutQueries.generated.ts | head -100

Repository: OWASP/Nest

Length of output: 8926


🏁 Script executed:

# Check if these constants are defined anywhere in the test file or imported
rg -n "const mockProjectData|const mockTopContributorsData|const mockUserData|const GetProjectMetadataDocument|const GetTopContributorsDocument|const GetLeaderDataDocument" frontend/__tests__/unit/pages/About.test.tsx

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Let's read more of the test file to see if there are any other beforeEach or mock setup blocks
cat -n frontend/__tests__/unit/pages/About.test.tsx | sed -n '400,600p'

Repository: OWASP/Nest

Length of output: 8418


🏁 Script executed:

# Search for where mockProjectData, mockTopContributorsData, and mockUserData are defined
rg -A 5 "mockProjectData\s*=" frontend/__tests__/unit/pages/About.test.tsx | head -30

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Search the entire test directory for where these mock data objects are defined
rg "mockProjectData|mockTopContributorsData|mockUserData" frontend/__tests__ --type=ts --type=tsx | head -30

Repository: OWASP/Nest

Length of output: 81


🏁 Script executed:

# Search for where these mock data objects are defined
rg "mockProjectData|mockTopContributorsData|mockUserData" frontend/__tests__ | head -30

Repository: OWASP/Nest

Length of output: 2687


Fix undefined mock references in beforeEach block — GetProjectMetadataDocument, GetTopContributorsDocument, GetLeaderDataDocument, mockProjectData, mockTopContributorsData, and mockUserData are not imported or defined.

Lines 206–215 reference constants and functions that don't exist in this file's scope, causing ReferenceError at runtime. These belong to other pages (Projects, Users). The About page imports and uses only GetAboutPageDataDocument (line 9), which is not handled by the mock. Replace the entire mock implementation to match the About page's actual query — it calls useQuery(GetAboutPageDataDocument) with variables including projectKey, excludedUsernames, and three leader usernames. Use mockAboutData (already imported on line 3) as the response. Additionally, line 221 has malformed whitespace (leading ; before (useRouter...)).

🤖 Prompt for AI Agents
In `@frontend/__tests__/unit/pages/About.test.tsx` around lines 203 - 221, The
beforeEach mock for useQuery references undefined symbols from other pages;
replace that mockImplementation to only handle
useQuery(GetAboutPageDataDocument) by reading variables.projectKey (and other
variables like excludedUsernames and leader usernames if present) and return the
imported mockAboutData, remove any branches for
GetProjectMetadataDocument/GetTopContributorsDocument/GetLeaderDataDocument and
references to mockProjectData/mockTopContributorsData/mockUserData, and fix the
malformed whitespace before the useRouter mock by ensuring mockRouter is created
(mockRouter = { push: jest.fn() }) and then calling (useRouter as
jest.Mock).mockReturnValue(mockRouter) without the stray leading semicolon.

Comment on lines 560 to 571
test('renders ErrorDisplay when project is null', async () => {
;(useQuery as unknown as jest.Mock).mockImplementation((query) => {
if (query === GetAboutPageDataDocument) {
return {
loading: false,
data: {
project: null,
topContributors: mockAboutData.topContributors,
leader1: mockAboutData.users['arkid15r'],
leader2: mockAboutData.users['kasya'],
leader3: mockAboutData.users['mamicidal'],
},
error: null,
}
;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => {
if (options?.variables?.key === 'nest') {
return { loading: false, data: { project: null }, error: null }
}
return {
loading: false,
data: { user: { avatarUrl: '', company: '', name: 'Dummy', location: '' } },
error: null,
}
return { loading: true }
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unreachable code on line 570.

The else branch (lines 565–569) unconditionally returns, so return { loading: true } on line 570 is dead code and will never execute. This looks like a leftover from a copy-paste merge.

🧹 Remove the dead return
       return { loading: false, data: { project: null }, error: null }
-      }
-      return {
+      } else {
+        return {
         loading: false,
         data: { user: { avatarUrl: '', company: '', name: 'Dummy', location: '' } },
         error: null,
       }
-      return { loading: true }
+      }
     })
📝 Committable suggestion

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

Suggested change
test('renders ErrorDisplay when project is null', async () => {
;(useQuery as unknown as jest.Mock).mockImplementation((query) => {
if (query === GetAboutPageDataDocument) {
return {
loading: false,
data: {
project: null,
topContributors: mockAboutData.topContributors,
leader1: mockAboutData.users['arkid15r'],
leader2: mockAboutData.users['kasya'],
leader3: mockAboutData.users['mamicidal'],
},
error: null,
}
;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => {
if (options?.variables?.key === 'nest') {
return { loading: false, data: { project: null }, error: null }
}
return {
loading: false,
data: { user: { avatarUrl: '', company: '', name: 'Dummy', location: '' } },
error: null,
}
return { loading: true }
})
test('renders ErrorDisplay when project is null', async () => {
;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => {
if (options?.variables?.key === 'nest') {
return { loading: false, data: { project: null }, error: null }
} else {
return {
loading: false,
data: { user: { avatarUrl: '', company: '', name: 'Dummy', location: '' } },
error: null,
}
}
})
🧰 Tools
🪛 Biome (2.3.13)

[error] 570-570: This code will never be reached ...

... because this statement will return from the function beforehand

(lint/correctness/noUnreachable)

🤖 Prompt for AI Agents
In `@frontend/__tests__/unit/pages/About.test.tsx` around lines 560 - 571, The
mock implementation for useQuery inside the test 'renders ErrorDisplay when
project is null' contains an unreachable final "return { loading: true }"
because the prior branches already return unconditionally; remove that dead
return (or restructure the mockImplementation to use if/else if/else and only
return once) so that useQuery mock only returns the intended objects; reference
the test name and the useQuery mockImplementation to locate and fix the dead
code.

Comment on lines +635 to +695
test('renders mission and who its for sections correctly', async () => {
await act(async () => {
render(<About />)
})

const missionSection = screen.getByText('Our Mission').closest('div')
expect(missionSection).toBeInTheDocument()
expect(screen.getByText('Test mission statement')).toBeInTheDocument()

const whoItsForSection = screen.getByText("Who It's For").closest('div')
expect(whoItsForSection).toBeInTheDocument()
expect(screen.getByText('Test target audience description')).toBeInTheDocument()
})

test('renders mission section', async () => {
await act(async () => {
render(<About />)
})
expect(screen.getByText('Our Mission')).toBeInTheDocument()
expect(screen.getByText('Test mission statement')).toBeInTheDocument()
})

test("renders 'Who It's For' section", async () => {
await act(async () => {
render(<About />)
})
expect(screen.getByText("Who It's For")).toBeInTheDocument()
expect(screen.getByText(/Test target audience description/)).toBeInTheDocument()
})

test('renders key features section', async () => {
await act(async () => {
render(<About />)
})
expect(screen.getByText('Key Features')).toBeInTheDocument()
expect(screen.getByText('Feature 1')).toBeInTheDocument()
expect(screen.getByText('Feature 2')).toBeInTheDocument()
expect(screen.getByText('Feature 1 description')).toBeInTheDocument()
expect(screen.getByText('Feature 2 description')).toBeInTheDocument()
})

test('renders get involved section', async () => {
await act(async () => {
render(<About />)
})
expect(screen.getByText('Get Involved')).toBeInTheDocument()
expect(screen.getByText('Get involved description')).toBeInTheDocument()
expect(screen.getByText('Way 1')).toBeInTheDocument()
expect(screen.getByText('Way 2')).toBeInTheDocument()
expect(screen.getByText('Test call to action')).toBeInTheDocument()
})

test('renders project history timeline section', async () => {
await act(async () => {
render(<About />)
})
expect(screen.getByText('Project Timeline')).toBeInTheDocument()
expect(screen.getByText('Timeline Event 7')).toBeInTheDocument()
expect(screen.getByText('Timeline Event 2')).toBeInTheDocument()
expect(screen.queryByText('Timeline Event 1')).not.toBeInTheDocument()
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Duplicate test cases — six tests are repeated verbatim.

The following tests at lines 635–695 duplicate earlier tests in the same describe block:

Duplicate (line) Original (line) Test name
635 228 renders mission and who its for sections correctly
649 242 renders mission section
657 250 renders 'Who It's For' section
665 258 renders key features section correctly / renders key features section
676 272 renders get involved section correctly / renders get involved section
687 286 renders project history timeline correctly / renders project history timeline section

Jest will run both copies (last one wins for name collisions), which wastes CI time, produces confusing output, and masks whether the "real" test is passing. Remove the duplicates.

🤖 Prompt for AI Agents
In `@frontend/__tests__/unit/pages/About.test.tsx` around lines 635 - 695, Remove
the duplicated Jest tests in the About.test.tsx file by deleting the repeated
test blocks with these names: "renders mission and who its for sections
correctly", "renders mission section", "renders 'Who It's For' section",
"renders key features section" (or "renders key features section correctly"),
"renders get involved section" (or "renders get involved section correctly"),
and "renders project history timeline section" (or "renders project history
timeline correctly"); keep only the original instances of these tests (the first
occurrences) so each assertion set (render(<About />) + screen.getByText(...)
expectations) appears exactly once.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 6, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@Srizdebnath Srizdebnath requested a review from arkid15r February 6, 2026 06:03
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/src/app/board/`[year]/candidates/page.tsx:
- Line 341: The dark-mode hover color for the LinkedIn link is identical to its
base dark color, so update the class on the LinkedIn anchor (the element with
className="text-blue-600 hover:text-blue-700 dark:text-blue-300
dark:hover:text-blue-300") to use a visibly different dark-mode hover shade (for
example change dark:hover:text-blue-300 to dark:hover:text-blue-200 or
dark:hover:text-blue-100) so the link shows a clear hover affordance in dark
theme.
🧹 Nitpick comments (1)
frontend/src/app/board/[year]/candidates/page.tsx (1)

424-453: Metric label color (text-gray-500) is borderline AA and inconsistent with sibling icon color.

The icons were changed to text-gray-800 (≈15:1 contrast on white — excellent), but the labels ("Commits", "PRs", "Issues", "Total") use text-gray-500 which yields only ≈4.85:1 on the white card background. While this technically passes AA's 4.5:1 threshold for normal text, it's the weakest contrast in the entire file and inconsistent with the intent of this PR. Using text-gray-700 or text-gray-800 would align with the other improvements and give comfortable headroom.

🎨 Proposed fix (example for one; apply to all four)
-                  <p className="text-xs text-gray-500 dark:text-gray-200">Commits</p>
+                  <p className="text-xs text-gray-700 dark:text-gray-200">Commits</p>

Apply the same change on Lines 435, 444, and 453.

)
}}
className="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
className="text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-300"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dark-mode hover state on LinkedIn link has no visual change.

dark:text-blue-300 and dark:hover:text-blue-300 resolve to the same color, so hovering provides no visual feedback in dark mode. This reduces the interactive affordance of the link. Use a lighter or different shade for the hover state.

🔧 Proposed fix
-                  className="text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-300"
+                  className="text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200"
📝 Committable suggestion

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

Suggested change
className="text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-300"
className="text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200"
🤖 Prompt for AI Agents
In `@frontend/src/app/board/`[year]/candidates/page.tsx at line 341, The dark-mode
hover color for the LinkedIn link is identical to its base dark color, so update
the class on the LinkedIn anchor (the element with className="text-blue-600
hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-300") to use a
visibly different dark-mode hover shade (for example change
dark:hover:text-blue-300 to dark:hover:text-blue-200 or
dark:hover:text-blue-100) so the link shows a clear hover affordance in dark
theme.

@arkid15r arkid15r marked this pull request as draft February 7, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(a11y): Correct color contrast for WCAG compliance in multiple components

3 participants