Skip to content

feat (opensource): add AI- powered "For You" repo recommendations#1814

Closed
dhaanisi wants to merge 11 commits into
Sachinchaurasiya360:mainfrom
dhaanisi:feat/ai-powered-repo
Closed

feat (opensource): add AI- powered "For You" repo recommendations#1814
dhaanisi wants to merge 11 commits into
Sachinchaurasiya360:mainfrom
dhaanisi:feat/ai-powered-repo

Conversation

@dhaanisi

@dhaanisi dhaanisi commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Closes #949

Problem

The current repository discovery page presents a generic, unfiltered list of projects. Users with React skills see the same list as users with Rust skills, leading to a less engaging experience. Open source discovery lacks a "Recommendation for you" layer, which is a core feature for user retention and matching.

What has changed

This PR introduces a personalised, AI-powered "For you" section at the top of the Repo Discovery page, powered by Gemini.

Backend/API (/api/opensource/recommended)

  • Gemini-Powered Matching Logic: Implemented an AI-recommendation service that extracts the user's skills and tech stack from their profile and resume keywords, and cross-references them against repository languages, tags, and domains.
  • Match Reasoning: The API now returns a descriptive matchReason string (e.g., "Matches your React and TypeScript skills") for recommended repositories.
  • Smart Ranking Engine:
  1. Automatically boosts repositories that are marked as trending or have goodFirstIssuesCount > 0.
  2. De-ranks repositories the user has already viewed or bookmarked to keep discovery fresh.
  • Caching: Recommendations are cached per user to optimise API performance and Gemini token usage.

Frontend UI (RepoDiscoveryPage & RepoCard)

  • "Recommended For You" Row: Added a new horizontal scroll section specifically for AI-recommendation repos, positioned cleanly above the main grid.
  • Match Reason Chips: Updated RepoCard to display the AI match reasoning (e.g., "Matches you stack") visually.
  • Refresh Button: Included a "Refresh recommendations" button for users to pull a new batch of AI suggestions.
  • Good First Issue Indicators: Added UI indicators and filters for repositories containing "good first issues".

Impact

Priority: P1 This personalisation layer directly addresses discovery retention. By matching students with repositories aligned to their active tech stack and highlighting beginner-friendly issues, we significantly lower the barrier to open-source contributions.

Summary by CodeRabbit

  • New Features

    • AI-powered "Recommended for you" horizontal carousel with refresh control
    • Recently viewed repos list and a viewed-only filter
    • "Good first issues" filter and badge; show match reason pill on recommendations
  • Improvements

    • Results count and filters reflect viewed/saved and good-first modes
    • Pagination hidden when viewing saved or recently viewed lists
    • Visual tweaks to badges and bookmark button labeling

@github-actions github-actions Bot added level:advanced Complex implementation or logic gssoc:approved Approved for GSSoC scoring quality:clean Clean and well-structured contribution labels Jun 7, 2026
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

Hi @dhaanisi, thanks for contributing to InternHack! 🎉

I have automatically:

  • 👤 Assigned this PR to you.
  • 🏷️ Applied the gssoc:approved label.

Our workflows will now analyze your changes to classify:

  • 📈 PR Difficulty: level:*
  • 🧩 PR Type: type:*
  • 🌟 PR Quality: quality:*

Tip

Ensure your PR description references the issue it resolves (e.g. Closes #123). This allows the bot to inherit any additional labels from that issue!

Happy coding! 🚀

@github-actions github-actions Bot added scope:backend Changes to server-side / API code scope:frontend Changes to client-side / UI code type:feature New feature implementation labels Jun 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements Gemini-powered personalized repository recommendations alongside "good first issues" filtering and viewed-repo tracking. The client persists viewed IDs in localStorage, queries the new /opensource/recommended endpoint with refresh control, and displays recommendations as an inline horizontal list. The server caches recommendations per user with TTL, generates them via Gemini API using skills/projects/ATS data, and supports forward-compatible search and filtering.

Changes

Repository recommendations and advanced filtering

Layer / File(s) Summary
Type contracts and query infrastructure for recommendations
client/src/lib/types/opensource.types.ts, client/src/lib/query-keys.ts, server/src/module/opensource/opensource.validation.ts
RecommendedRepo extends OpenSourceRepo with matchReason: string. Query keys now include a recommended(refreshToken?) generator. List query schema validates optional goodFirstIssue enum.
RepoCard UI for recommended repos and good-first badges
client/src/module/student/opensource/RepoCard.tsx
RepoCard accepts RecommendedRepo and computes goodFirst status by matching repo issueTypes/tags against GOOD_FIRST_LABELS or difficulty="BEGINNER". Renders combined badges for trending/good-first and conditionally shows matchReason pill when populated.
RepoDiscoveryPage filtering, viewed history, and recommendations UI
client/src/module/student/opensource/RepoDiscoveryPage.tsx
Adds GOOD_FIRST_LABELS and isGoodFirstRepo predicate. Persists recently-viewed repo IDs in localStorage via rememberViewedRepo. Derives goodFirstOnly and viewedOnly flags from URL params. Wires React Query call to /opensource/recommended passing viewedIds and using recommendationRefreshToken for forceRefresh control. Renders inline "Recommended for you" section with Refresh button, adds filter-bar toggles for Viewed and Good first issues, hides pagination when filtering is active, shows good-first badge in modal, and fixes language select binding.
Server Gemini integration, search, and good-first filtering in listRepos
server/src/module/opensource/opensource.service.ts
Imports GoogleGenerativeAI and adds RECOMMENDATION_TTL_MS and GOOD_FIRST_LABELS constants. Creates in-memory recommendationCache with TTL tracking. Extends listRepos to filter by ids array, implement goodFirstIssue via AND-scoped OR matching issueTypes/tags or difficulty, and rework search using raw SQL unnest+ILIKE on tags/issueTypes combined with substring matching across name/owner/description/language. Replaces getRecommendedRepos with Gemini-driven flow: loads user skills/projects/ATS keywords, selects up to 40 candidate repos (excluding viewedIds), calls gemini-2.5-flash-lite with profile context, parses returned JSON to map id/matchReason onto repos, caches with TTL, and respects forceRefresh.
Backend controller query parsing and endpoint response
server/src/module/opensource/opensource.controller.ts
Parses optional forceRefresh and CSV viewedIds from query, converts to boolean and integer array respectively, calls service.getRecommendedRepos(userId, viewedIds, forceRefresh), returns response as { recommended }, and handles "User not found" errors with 404 status.
Database connection test utilities
server/test-db.js, server/test-db-real.js, server/test-db.d.ts, server/test-db-real.d.ts
Adds Prisma connectivity test scripts that verify database connection and log user query results with proper cleanup.
Miscellaneous client-side refactors and type improvements
client/src/components/OssContributionHeatmap.tsx, client/src/components/ui/ConfirmDialog.tsx, client/src/module/recruiter/applications/ApplicationDetail.tsx, client/src/module/recruiter/applications/CandidateImportPage.tsx, client/src/module/student/applications/MyApplicationsPage.tsx, client/src/module/student/applications/questions/interviewQuestions.ts, client/src/module/student/roadmap/RoadmapCanvasPage.tsx, client/src/module/student/dsa/components/AiHintPanel.tsx, client/src/module/student/profile/components/ProfilePageHeader.tsx, client/src/module/student/roadmap/*
Small fixes: ActivityCalendar prop names, type-only React import, ApplicationDetail fetch with AbortSignal, safer CSV casting, filteredExternal adjustment, typed status tabs, tips array shape, AiHintPanel/ProfilePageHeader ref initializations, roadmap icon/import fixes, and roadmap update invalidation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #949: Objectives to implement AI-powered repository recommendations driven by user skill profile and keyword analysis, render a "Recommended for you" section with match reasoning, cache recommendations per user, and refresh via UI button—this PR fully addresses those requirements using Gemini, localStorage-based viewed tracking, and inline UI integration.

Possibly related PRs

Suggested reviewers

  • Sachinchaurasiya360

Poem

🐰 I hopped through code and turned a key,
Recommendations bloom for you and me.
Good-first badges, reasons that gleam,
Viewed repos saved from every dream—
Refresh, explore — discover, free.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers the problem, solution, backend/frontend changes, and impact with good detail. However, the description mentions 'Screenshots / Video' are required for UI changes, but none are provided despite significant UI additions. Add screenshots or screen recordings demonstrating the new 'Recommended For You' section, match reason chips, and refresh button functionality to meet the template requirement for UI changes.
Out of Scope Changes check ⚠️ Warning Most changes directly support AI-powered recommendations. However, several unrelated changes appear: OssContributionHeatmap ActivityCalendar config swap, ConfirmDialog type import syntax, ApplicationDetail refactoring, CandidateImportPage casting, MyApplicationsPage filter logic, interviewQuestions tips type, RoadmapCanvasPage level expansion, AiHintPanel/ProfilePageHeader ref initialization, RoadmapDashboardPage Map icon alias, and RoadmapsLandingPage Sparkles import. Remove out-of-scope changes unrelated to the AI recommendations feature (OssContributionHeatmap, ConfirmDialog, ApplicationDetail, CandidateImportPage, MyApplicationsPage, interviewQuestions, RoadmapCanvasPage, AiHintPanel, ProfilePageHeader, RoadmapDashboardPage, RoadmapsLandingPage) to keep the PR focused.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: AI-powered 'For You' repo recommendations for the opensource module.
Linked Issues check ✅ Passed The PR implements all key objectives from issue #949: Gemini-powered matching [#949], AI recommendations with match reasoning [#949], smart ranking with trending/goodFirstIssues boost [#949], caching [#949], UI components (recommended row, match chips, refresh button) [#949], and good-first-issue filtering [#949].

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 6

🧹 Nitpick comments (4)
client/src/module/student/opensource/RepoCard.tsx (2)

137-140: ⚡ Quick win

Replace arbitrary Tailwind font-size token with a canonical scale class.

Line 139 uses text-[10px], which violates the Tailwind class rule for this repo. Use a scale class (text-xs / project-defined canonical token) instead.

As per coding guidelines, “Do not use arbitrary bracket sizes like text-[17px], use standard scale classes instead.”

🤖 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 `@client/src/module/student/opensource/RepoCard.tsx` around lines 137 - 140, In
RepoCard (component rendering the repo prop and conditional "matchReason" block)
replace the arbitrary Tailwind class text-[10px] with the canonical scale token
(e.g. text-xs or the project-defined token) on the span that contains the Wand2
icon and match reason; update the class list on that span to remove text-[10px]
and use text-xs (or the project's canonical font-size token) so it conforms to
the repo's Tailwind class rules.

Source: Coding guidelines


18-32: ⚡ Quick win

Consolidate good-first label detection into one shared helper.

Line 18-32 duplicates logic that also exists in client/src/module/student/opensource/RepoDiscoveryPage.tsx (Line 50-70). This can drift and produce inconsistent “good first” behavior across list cards vs detail/filter flows.

As per coding guidelines, “Apply DRY principle: no duplicate helpers, shared animation variants per file.”

🤖 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 `@client/src/module/student/opensource/RepoCard.tsx` around lines 18 - 32, The
GOOD_FIRST_LABELS constant and hasGoodFirstIssues logic are duplicated; extract
them into a single shared helper (e.g., export a constant GOOD_FIRST_LABELS and
a function isGoodFirstIssue(repo) that accepts the same repo union type) and
replace the local GOOD_FIRST_LABELS and hasGoodFirstIssues in RepoCard and the
duplicate in RepoDiscoveryPage with imports of the shared helper; ensure the
helper returns a boolean using the same case-insensitive includes logic and keep
the function name (isGoodFirstIssue or hasGoodFirstIssues) consistent across
both components so callers (RepoCard and RepoDiscoveryPage) use the common
implementation.

Source: Coding guidelines

client/src/module/student/opensource/RepoDiscoveryPage.tsx (2)

742-753: ⚡ Quick win

Use the shared Button component for newly added controls.

Lines 742-753, 770-781, and 971-979 introduce new native <button> elements. This diverges from the shared Button API already used in this file and makes variants/styles harder to keep consistent.

As per coding guidelines, “Use the reusable Button component from client/src/components/ui/button.tsx for all new buttons.”

Also applies to: 770-781, 971-979

🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 742
- 753, Replace the native <button> controls introduced around the viewed filter
with the shared Button component: use Button (from the project's UI button)
instead of the raw element for the controls that call updateFilter and rely on
viewedOnly/recentlyViewed, and do the same for the other two native buttons at
the other locations; map the current attributes (type="button", onClick={() =>
updateFilter("viewed", viewedOnly ? "" : "true")}) to Button props (onClick,
variant/size/className) so the visual conditional classes driven by viewedOnly
are applied via Button's styling API while preserving the label "Viewed" and the
count from recentlyViewed.length; ensure behavior and accessibility remain
identical and update any imports to include Button.

Source: Coding guidelines


985-985: ⚡ Quick win

Replace arbitrary width tokens with canonical Tailwind scale classes.

Line 985 uses min-w-[300px] w-[300px] sm:min-w-[350px] sm:w-[350px]. These bracket sizes violate the project’s Tailwind class policy.

As per coding guidelines, “Do not use arbitrary bracket sizes like text-[17px], use standard scale classes instead.”

🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` at line 985, The
div rendered for each repo (the element with key={repo.id} in
RepoDiscoveryPage.tsx) uses arbitrary bracketed widths (min-w-[300px] w-[300px]
sm:min-w-[350px] sm:w-[350px]); replace those with canonical Tailwind scale
classes instead (e.g., use min-w-80 w-80 and sm:min-w-96 sm:w-96 or another
appropriate w-*/min-w-* pair from the Tailwind scale) by editing the className
on that div.

Source: Coding guidelines

🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx`:
- Around line 226-233: The state initializer for viewedIds trusts JSON.parse to
return a number[] which can crash later when using viewedIds.join; update the
initializer for viewedIds (and any setter usage around the "viewed_repos" key)
to validate the parsed payload: parse localStorage.getItem("viewed_repos"),
confirm it's an array of numbers (e.g., Array.isArray(parsed) and every item is
typeof "number"), and only then return it; otherwise return [] and overwrite
localStorage with a safe [] when necessary so functions using viewedIds (and
code that calls setViewedIds or reads viewed_repos) always receive a number[].

In `@server/src/module/opensource/opensource.service.ts`:
- Around line 34-35: The global recommendationCache Map stores entries with
expiresAt but never removes expired keys, causing unbounded growth; add a
periodic cleanup and opportunistic pruning: implement a cleanup routine that
iterates recommendationCache and deletes entries with expiresAt <= Date.now()
(run on a setInterval, e.g., every minute) and also remove expired entries on
cache reads/writes where recommendationCache is accessed; reference the
recommendationCache variable and update the functions that read/write it (the
cache lookup/get and set paths in opensource.service.ts) to perform the
opportunistic prune, and apply the same pattern to the other cache Maps
mentioned in the review.
- Around line 559-567: The parsed AI payload assigned to matches must be
validated before mapping into recommended: ensure matches is an array and each
entry has a numeric id and a string matchReason; skip or normalize any entries
that don't match that shape. Update the code around the matches declaration and
the recommended mapping (the matches variable and the mapping that uses
candidateRepos.find) to first validate/filter/normalize items (e.g., typeof
match.id === 'number' and typeof match.matchReason === 'string' or coerce
toString) and only then map to candidate repos, so no non-string matchReason
values are forwarded to the client.
- Around line 554-557: Wrap the Gemini call in a timeout/abort guard so
generateContent cannot hang: create an AbortController with a short timeout
(setTimeout that calls controller.abort()), pass controller.signal into
model.generateContent(prompt, { signal }) if the API supports it, or otherwise
use Promise.race between model.generateContent(prompt) and a timeout promise
that rejects; catch the abort/timeout error around the call where
GoogleGenerativeAI, getGenerativeModel and model.generateContent are used, clear
the timeout, and on timeout/abort return an empty array ([]) instead of letting
the request hang or throw.

In `@server/test-db-real.js`:
- Around line 3-15: The DB-check catch block logs the error but still allows a
zero exit; update the catch in main() to set a non-zero exit status by assigning
process.exitCode = 1 (instead of calling process.exit directly) after logging
the error so prisma.$disconnect() in the finally block still runs, ensuring the
process exits with failure when the DB check fails.

In `@server/test-db.js`:
- Around line 3-15: The catch block in async function main() currently only logs
the error so the script can exit with success; update it to log the error,
ensure the DB client is disconnected, and then exit with a non-zero status so
health checks fail: in the catch for main() capture the error, call
console.error with the error, await prisma.$disconnect() (or ensure
prisma.$disconnect() runs before exiting), and then call process.exit(1); keep
the existing finally block behavior or remove duplicate disconnects to avoid
double-disconnect errors.

---

Nitpick comments:
In `@client/src/module/student/opensource/RepoCard.tsx`:
- Around line 137-140: In RepoCard (component rendering the repo prop and
conditional "matchReason" block) replace the arbitrary Tailwind class
text-[10px] with the canonical scale token (e.g. text-xs or the project-defined
token) on the span that contains the Wand2 icon and match reason; update the
class list on that span to remove text-[10px] and use text-xs (or the project's
canonical font-size token) so it conforms to the repo's Tailwind class rules.
- Around line 18-32: The GOOD_FIRST_LABELS constant and hasGoodFirstIssues logic
are duplicated; extract them into a single shared helper (e.g., export a
constant GOOD_FIRST_LABELS and a function isGoodFirstIssue(repo) that accepts
the same repo union type) and replace the local GOOD_FIRST_LABELS and
hasGoodFirstIssues in RepoCard and the duplicate in RepoDiscoveryPage with
imports of the shared helper; ensure the helper returns a boolean using the same
case-insensitive includes logic and keep the function name (isGoodFirstIssue or
hasGoodFirstIssues) consistent across both components so callers (RepoCard and
RepoDiscoveryPage) use the common implementation.

In `@client/src/module/student/opensource/RepoDiscoveryPage.tsx`:
- Around line 742-753: Replace the native <button> controls introduced around
the viewed filter with the shared Button component: use Button (from the
project's UI button) instead of the raw element for the controls that call
updateFilter and rely on viewedOnly/recentlyViewed, and do the same for the
other two native buttons at the other locations; map the current attributes
(type="button", onClick={() => updateFilter("viewed", viewedOnly ? "" :
"true")}) to Button props (onClick, variant/size/className) so the visual
conditional classes driven by viewedOnly are applied via Button's styling API
while preserving the label "Viewed" and the count from recentlyViewed.length;
ensure behavior and accessibility remain identical and update any imports to
include Button.
- Line 985: The div rendered for each repo (the element with key={repo.id} in
RepoDiscoveryPage.tsx) uses arbitrary bracketed widths (min-w-[300px] w-[300px]
sm:min-w-[350px] sm:w-[350px]); replace those with canonical Tailwind scale
classes instead (e.g., use min-w-80 w-80 and sm:min-w-96 sm:w-96 or another
appropriate w-*/min-w-* pair from the Tailwind scale) by editing the className
on that div.
🪄 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: 99ad5210-c06b-417e-b767-13a9a6127890

📥 Commits

Reviewing files that changed from the base of the PR and between 887bd15 and 1d46be2.

⛔ Files ignored due to path filters (4)
  • server/test-db-real.d.ts.map is excluded by !**/*.map
  • server/test-db-real.js.map is excluded by !**/*.map
  • server/test-db.d.ts.map is excluded by !**/*.map
  • server/test-db.js.map is excluded by !**/*.map
📒 Files selected for processing (14)
  • client/src/lib/query-keys.ts
  • client/src/lib/types/opensource.types.ts
  • client/src/module/recruiter/applications/ApplicationDetail.tsx
  • client/src/module/student/opensource/GSoCReposPage.tsx
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • server/src/module/opensource/opensource.controller.ts
  • server/src/module/opensource/opensource.routes.ts
  • server/src/module/opensource/opensource.service.ts
  • server/src/module/opensource/opensource.validation.ts
  • server/test-db-real.d.ts
  • server/test-db-real.js
  • server/test-db.d.ts
  • server/test-db.js
💤 Files with no reviewable changes (2)
  • client/src/module/student/opensource/GSoCReposPage.tsx
  • client/src/module/recruiter/applications/ApplicationDetail.tsx

Comment on lines +226 to 233
const [viewedIds, setViewedIds] = useState<number[]>(() => {
try {
const stored = localStorage.getItem("viewed_repos");
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate viewed_repos payload before storing it in state.

Line 226-233 trusts JSON.parse output as number[]. If storage contains an object/string, Line 358/363 (viewedIds.join) will throw and break page rendering.

🔧 Proposed fix
 const [viewedIds, setViewedIds] = useState<number[]>(() => {
   try {
     const stored = localStorage.getItem("viewed_repos");
-    return stored ? JSON.parse(stored) : [];
+    if (!stored) return [];
+    const parsed = JSON.parse(stored);
+    return Array.isArray(parsed) && parsed.every((id) => Number.isInteger(id) && id > 0)
+      ? parsed
+      : [];
   } catch {
     return [];
   }
 });

Also applies to: 358-364

🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 226
- 233, The state initializer for viewedIds trusts JSON.parse to return a
number[] which can crash later when using viewedIds.join; update the initializer
for viewedIds (and any setter usage around the "viewed_repos" key) to validate
the parsed payload: parse localStorage.getItem("viewed_repos"), confirm it's an
array of numbers (e.g., Array.isArray(parsed) and every item is typeof
"number"), and only then return it; otherwise return [] and overwrite
localStorage with a safe [] when necessary so functions using viewedIds (and
code that calls setViewedIds or reads viewed_repos) always receive a number[].

Comment on lines +34 to +35
const recommendationCache = new Map<number, { repos: any[]; expiresAt: number }>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prune expired recommendation cache entries to prevent unbounded memory growth.

Line 34 introduces a global Map cache, but expired entries are never removed unless the same user is requested again. Over time this can grow with user cardinality and retain stale objects indefinitely.

🔧 Proposed fix
 const recommendationCache = new Map<number, { repos: any[]; expiresAt: number }>();
+
+const pruneExpiredRecommendationCache = (now: number) => {
+  for (const [key, value] of recommendationCache.entries()) {
+    if (value.expiresAt <= now) recommendationCache.delete(key);
+  }
+};

 async getRecommendedRepos(userId: number, viewedIds: number[] = [], forceRefresh = false) {
   const now = Date.now();
+  pruneExpiredRecommendationCache(now);

   if (!forceRefresh) {
     const cached = recommendationCache.get(userId);

Also applies to: 474-479, 568-571

🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 34 - 35, The
global recommendationCache Map stores entries with expiresAt but never removes
expired keys, causing unbounded growth; add a periodic cleanup and opportunistic
pruning: implement a cleanup routine that iterates recommendationCache and
deletes entries with expiresAt <= Date.now() (run on a setInterval, e.g., every
minute) and also remove expired entries on cache reads/writes where
recommendationCache is accessed; reference the recommendationCache variable and
update the functions that read/write it (the cache lookup/get and set paths in
opensource.service.ts) to perform the opportunistic prune, and apply the same
pattern to the other cache Maps mentioned in the review.

Comment on lines +554 to +557
const genAI = new GoogleGenerativeAI(apiKey);
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash-lite" });
const result = await model.generateContent(prompt);
const text = result.response.text();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="server/src/module/opensource/opensource.service.ts"
echo "== lines around 520-590 =="
nl -ba "$FILE" | sed -n '520,590p'

echo
echo "== search for timeout/abort around Gemini calls =="
rg -n "GoogleGenerativeAI|getGenerativeModel|generateContent|AbortController|timeout|setTimeout" "$FILE"

Repository: Sachinchaurasiya360/InternHack

Length of output: 162


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="server/src/module/opensource/opensource.service.ts"
echo "== ensure file exists =="
ls -la "$FILE"

echo
echo "== lines around 520-590 (line-numbered via awk) =="
awk 'NR>=520 && NR<=590 {printf "%6d\t%s\n", NR, $0}' "$FILE"

echo
echo "== search for timeout/abort around Gemini calls =="
rg -n "GoogleGenerativeAI|getGenerativeModel|generateContent|AbortController|timeout|setTimeout|Promise\.race|withTimeout" "$FILE" || true

Repository: Sachinchaurasiya360/InternHack

Length of output: 3485


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="server/src/module/opensource/opensource.service.ts"

echo "== lines around Gemini call (670-730) =="
awk 'NR>=670 && NR<=730 {printf "%6d\t%s\n", NR, $0}' "$FILE"

echo
echo "== search for any other Gemini/timeout helpers in the file =="
rg -n "AbortController|setTimeout|withTimeout|Promise\.race|timeout|signal:" "$FILE" || true

echo
echo "== show full Gemini prompt/call block (best-effort) =="
awk 'NR>=660 && NR<=710 {print}' "$FILE"

Repository: Sachinchaurasiya360/InternHack

Length of output: 3749


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="server/src/module/opensource/opensource.service.ts"
echo "== count generateContent calls =="
rg -n "generateContent\(" "$FILE" || true
echo
echo "== count getGenerativeModel calls =="
rg -n "getGenerativeModel\(" "$FILE" || true
echo
echo "== search for any wrapper functions possibly named with timeout =="
rg -n "timeout|AbortController|Promise\.race|withTimeout|signal:" "$FILE" || true

Repository: Sachinchaurasiya360/InternHack

Length of output: 362


Add a timeout/abort guard around the Gemini recommendation call
model.generateContent(prompt) is awaited without any timeout or abort signal, so a hung upstream model call can tie up the endpoint (server/src/module/opensource/opensource.service.ts: ~692-695). Add a timeout/AbortController wrapper and fall back to [] on timeout.

🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 554 - 557,
Wrap the Gemini call in a timeout/abort guard so generateContent cannot hang:
create an AbortController with a short timeout (setTimeout that calls
controller.abort()), pass controller.signal into model.generateContent(prompt, {
signal }) if the API supports it, or otherwise use Promise.race between
model.generateContent(prompt) and a timeout promise that rejects; catch the
abort/timeout error around the call where GoogleGenerativeAI, getGenerativeModel
and model.generateContent are used, clear the timeout, and on timeout/abort
return an empty array ([]) instead of letting the request hang or throw.

Comment on lines +559 to +567
const matches = JSON.parse(cleanText) as { id: number; matchReason: string }[];

const recommended = matches
.map((match) => {
const repo = candidateRepos.find((candidate) => candidate.id === match.id);
return repo ? { ...repo, matchReason: match.matchReason } : null;
})
.filter((repo): repo is NonNullable<typeof repo> => Boolean(repo));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate parsed AI payload shape before building recommended.

Line 559 trusts JSON output structure from the model. If matchReason is non-string/object, that value is forwarded and can break the client render path when displayed.

🔧 Proposed fix
-const matches = JSON.parse(cleanText) as { id: number; matchReason: string }[];
+const parsed = JSON.parse(cleanText);
+const matches = Array.isArray(parsed)
+  ? parsed.filter(
+      (m): m is { id: number; matchReason: string } =>
+        Number.isInteger(m?.id) && typeof m?.matchReason === "string" && m.matchReason.trim().length > 0,
+    )
+  : [];
🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 559 - 567,
The parsed AI payload assigned to matches must be validated before mapping into
recommended: ensure matches is an array and each entry has a numeric id and a
string matchReason; skip or normalize any entries that don't match that shape.
Update the code around the matches declaration and the recommended mapping (the
matches variable and the mapping that uses candidateRepos.find) to first
validate/filter/normalize items (e.g., typeof match.id === 'number' and typeof
match.matchReason === 'string' or coerce toString) and only then map to
candidate repos, so no non-string matchReason values are forwarded to the
client.

Comment thread server/test-db-real.js
Comment on lines +3 to +15
try {
const users = await prisma.user.findMany({ take: 1 });
console.log("DB connection successful, users:", users.length);
}
catch (e) {
console.error("DB connection error:", e);
}
finally {
await prisma.$disconnect();
}
}
main();
//# sourceMappingURL=test-db-real.js.map No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Set a non-zero exit status on DB check failure.

At Line 8 the error is logged but not signaled as failure, so this script can report a broken DB connection while still exiting successfully.

Proposed fix
 async function main() {
     try {
         const users = await prisma.user.findMany({ take: 1 });
         console.log("DB connection successful, users:", users.length);
     }
     catch (e) {
         console.error("DB connection error:", e);
+        process.exitCode = 1;
     }
     finally {
         await prisma.$disconnect();
     }
 }
-main();
+main().catch((e) => {
+    console.error("Unexpected failure during DB check:", e);
+    process.exit(1);
+});
📝 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
try {
const users = await prisma.user.findMany({ take: 1 });
console.log("DB connection successful, users:", users.length);
}
catch (e) {
console.error("DB connection error:", e);
}
finally {
await prisma.$disconnect();
}
}
main();
//# sourceMappingURL=test-db-real.js.map
try {
const users = await prisma.user.findMany({ take: 1 });
console.log("DB connection successful, users:", users.length);
}
catch (e) {
console.error("DB connection error:", e);
process.exitCode = 1;
}
finally {
await prisma.$disconnect();
}
}
main().catch((e) => {
console.error("Unexpected failure during DB check:", e);
process.exit(1);
});
//# sourceMappingURL=test-db-real.js.map
🤖 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 `@server/test-db-real.js` around lines 3 - 15, The DB-check catch block logs
the error but still allows a zero exit; update the catch in main() to set a
non-zero exit status by assigning process.exitCode = 1 (instead of calling
process.exit directly) after logging the error so prisma.$disconnect() in the
finally block still runs, ensuring the process exits with failure when the DB
check fails.

Comment thread server/test-db.js
Comment on lines +3 to +15
async function main() {
try {
const users = await prisma.user.findMany({ take: 1 });
console.log("DB connection successful, users:", users.length);
}
catch (e) {
console.error("DB connection error:", e);
}
finally {
await prisma.$disconnect();
}
}
main();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fail the script explicitly when the DB check fails.

At Line 9 the script logs the error but can still exit as success, which makes health checks unreliable.

Proposed fix
 async function main() {
     try {
         const users = await prisma.user.findMany({ take: 1 });
         console.log("DB connection successful, users:", users.length);
     }
     catch (e) {
         console.error("DB connection error:", e);
+        process.exitCode = 1;
     }
     finally {
         await prisma.$disconnect();
     }
 }
-main();
+main().catch((e) => {
+    console.error("Unexpected failure during DB check:", e);
+    process.exit(1);
+});
📝 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
async function main() {
try {
const users = await prisma.user.findMany({ take: 1 });
console.log("DB connection successful, users:", users.length);
}
catch (e) {
console.error("DB connection error:", e);
}
finally {
await prisma.$disconnect();
}
}
main();
async function main() {
try {
const users = await prisma.user.findMany({ take: 1 });
console.log("DB connection successful, users:", users.length);
}
catch (e) {
console.error("DB connection error:", e);
process.exitCode = 1;
}
finally {
await prisma.$disconnect();
}
}
main().catch((e) => {
console.error("Unexpected failure during DB check:", e);
process.exit(1);
});
🤖 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 `@server/test-db.js` around lines 3 - 15, The catch block in async function
main() currently only logs the error so the script can exit with success; update
it to log the error, ensure the DB client is disconnected, and then exit with a
non-zero status so health checks fail: in the catch for main() capture the
error, call console.error with the error, await prisma.$disconnect() (or ensure
prisma.$disconnect() runs before exiting), and then call process.exit(1); keep
the existing finally block behavior or remove duplicate disconnects to avoid
double-disconnect errors.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
client/src/module/student/opensource/RepoDiscoveryPage.tsx (1)

365-371: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing hacktoberfestOnly in useMemo dependency array.

Line 365 uses hacktoberfestOnly but the dependency array at line 371 doesn't include it. This causes a stale closure: toggling the Hacktoberfest filter won't trigger a re-fetch because queryParams won't update.

-  }, [search, selectedDomain, selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey, trendingOnly, goodFirstOnly, page]);
+  }, [search, selectedDomain, selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey, trendingOnly, goodFirstOnly, hacktoberfestOnly, page]);
🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 365
- 371, The useMemo that builds queryParams references hacktoberfestOnly but the
dependency array on the useMemo does not include it, causing stale closures;
update the dependency array for the memo (the useMemo that returns params in
RepoDiscoveryPage, which currently lists search, selectedDomain,
selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey,
trendingOnly, goodFirstOnly, page) to also include hacktoberfestOnly so toggling
the Hacktoberfest filter will recompute queryParams and trigger the expected
update.
🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx`:
- Around line 1385-1441: Remove the dead RecommendedSection component entirely:
delete the function RecommendedSection and its JSX; ensure you don't leave any
unused imports related to it. If any references remain, replace them to use the
existing inline recommendations implementation (which uses
queryKeys.opensource.recommended()) rather than queryKeys.opensource.list({
recommended: "true" }). Also ensure no code expects the old response shape ({
repos }) or calls RepoCard without the required bookmarked and onToggleBookmark
props — confirm the inline implementation supplies the server's { recommended }
payload and passes bookmarked and onToggleBookmark to RepoCard.
- Around line 438-443: Remove the orphaned useMemo block that redeclares
displayedRepos; keep the single valid declaration (const displayedRepos =
showSaved ? (bookmarkedData ?? []) : (data?.repos ?? [])); delete the
useMemo(...) that references non-existent repos and recentlyViewed (and any
import/variable references only used by that removed block) so there is only one
displayedRepos identifier (and no duplicate const) relying on bookmarkedData,
viewedOnly/viewed flag, recentlyViewed, and data?.repos as intended.
- Line 390: Remove the stray dependency array "}, [search, selectedDomain,
selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey,
trendingOnly, hacktoberfestOnly, page]);" that is orphaned in
RepoDiscoveryPage.tsx; locate the stray closing brace/array and delete it so the
existing queryParams useMemo (the one that already has its correct dependency
array) remains intact and correctly closed, ensuring no extra closing tokens
remain that break the ESLint/parser.

---

Outside diff comments:
In `@client/src/module/student/opensource/RepoDiscoveryPage.tsx`:
- Around line 365-371: The useMemo that builds queryParams references
hacktoberfestOnly but the dependency array on the useMemo does not include it,
causing stale closures; update the dependency array for the memo (the useMemo
that returns params in RepoDiscoveryPage, which currently lists search,
selectedDomain, selectedDifficulty, selectedLanguage, languageMode,
inferredLanguages, sortKey, trendingOnly, goodFirstOnly, page) to also include
hacktoberfestOnly so toggling the Hacktoberfest filter will recompute
queryParams and trigger the expected update.
🪄 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: c52ff463-b0a1-4f2b-a243-2a64027f5077

📥 Commits

Reviewing files that changed from the base of the PR and between 1d46be2 and ba5aa74.

📒 Files selected for processing (7)
  • client/src/lib/query-keys.ts
  • client/src/lib/types/opensource.types.ts
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • server/src/module/opensource/opensource.controller.ts
  • server/src/module/opensource/opensource.service.ts
  • server/src/module/opensource/opensource.validation.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • server/src/module/opensource/opensource.validation.ts
  • client/src/lib/query-keys.ts
  • server/src/module/opensource/opensource.controller.ts
  • client/src/module/student/opensource/RepoCard.tsx
  • server/src/module/opensource/opensource.service.ts

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
client/src/module/student/opensource/RepoDiscoveryPage.tsx (1)

365-371: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing hacktoberfestOnly in useMemo dependency array.

Line 365 uses hacktoberfestOnly but the dependency array at line 371 doesn't include it. This causes a stale closure: toggling the Hacktoberfest filter won't trigger a re-fetch because queryParams won't update.

-  }, [search, selectedDomain, selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey, trendingOnly, goodFirstOnly, page]);
+  }, [search, selectedDomain, selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey, trendingOnly, goodFirstOnly, hacktoberfestOnly, page]);
🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 365
- 371, The useMemo that builds queryParams references hacktoberfestOnly but the
dependency array on the useMemo does not include it, causing stale closures;
update the dependency array for the memo (the useMemo that returns params in
RepoDiscoveryPage, which currently lists search, selectedDomain,
selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey,
trendingOnly, goodFirstOnly, page) to also include hacktoberfestOnly so toggling
the Hacktoberfest filter will recompute queryParams and trigger the expected
update.
🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx`:
- Around line 1385-1441: Remove the dead RecommendedSection component entirely:
delete the function RecommendedSection and its JSX; ensure you don't leave any
unused imports related to it. If any references remain, replace them to use the
existing inline recommendations implementation (which uses
queryKeys.opensource.recommended()) rather than queryKeys.opensource.list({
recommended: "true" }). Also ensure no code expects the old response shape ({
repos }) or calls RepoCard without the required bookmarked and onToggleBookmark
props — confirm the inline implementation supplies the server's { recommended }
payload and passes bookmarked and onToggleBookmark to RepoCard.
- Around line 438-443: Remove the orphaned useMemo block that redeclares
displayedRepos; keep the single valid declaration (const displayedRepos =
showSaved ? (bookmarkedData ?? []) : (data?.repos ?? [])); delete the
useMemo(...) that references non-existent repos and recentlyViewed (and any
import/variable references only used by that removed block) so there is only one
displayedRepos identifier (and no duplicate const) relying on bookmarkedData,
viewedOnly/viewed flag, recentlyViewed, and data?.repos as intended.
- Line 390: Remove the stray dependency array "}, [search, selectedDomain,
selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey,
trendingOnly, hacktoberfestOnly, page]);" that is orphaned in
RepoDiscoveryPage.tsx; locate the stray closing brace/array and delete it so the
existing queryParams useMemo (the one that already has its correct dependency
array) remains intact and correctly closed, ensuring no extra closing tokens
remain that break the ESLint/parser.

---

Outside diff comments:
In `@client/src/module/student/opensource/RepoDiscoveryPage.tsx`:
- Around line 365-371: The useMemo that builds queryParams references
hacktoberfestOnly but the dependency array on the useMemo does not include it,
causing stale closures; update the dependency array for the memo (the useMemo
that returns params in RepoDiscoveryPage, which currently lists search,
selectedDomain, selectedDifficulty, selectedLanguage, languageMode,
inferredLanguages, sortKey, trendingOnly, goodFirstOnly, page) to also include
hacktoberfestOnly so toggling the Hacktoberfest filter will recompute
queryParams and trigger the expected update.
🪄 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: c52ff463-b0a1-4f2b-a243-2a64027f5077

📥 Commits

Reviewing files that changed from the base of the PR and between 1d46be2 and ba5aa74.

📒 Files selected for processing (7)
  • client/src/lib/query-keys.ts
  • client/src/lib/types/opensource.types.ts
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • server/src/module/opensource/opensource.controller.ts
  • server/src/module/opensource/opensource.service.ts
  • server/src/module/opensource/opensource.validation.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • server/src/module/opensource/opensource.validation.ts
  • client/src/lib/query-keys.ts
  • server/src/module/opensource/opensource.controller.ts
  • client/src/module/student/opensource/RepoCard.tsx
  • server/src/module/opensource/opensource.service.ts
🛑 Comments failed to post (3)
client/src/module/student/opensource/RepoDiscoveryPage.tsx (3)

390-390: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Orphaned dependency array causes build failure.

Line 390 contains a stray dependency array }, [search, selectedDomain, ...]) that doesn't belong to any function or hook. This is causing the ESLint parsing error reported in CI. This line must be removed—the queryParams useMemo already has its dependency array at line 371.

-  }, [search, selectedDomain, selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey, trendingOnly, hacktoberfestOnly, page]);
📝 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.


🧰 Tools
🪛 GitHub Actions: CI / 3_Lint Client.txt

[error] 390-390: ESLint parsing error: Declaration or statement expected.

🪛 GitHub Actions: CI / Lint Client

[error] 390-390: ESLint Parsing error: Declaration or statement expected.

🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` at line 390,
Remove the stray dependency array "}, [search, selectedDomain,
selectedDifficulty, selectedLanguage, languageMode, inferredLanguages, sortKey,
trendingOnly, hacktoberfestOnly, page]);" that is orphaned in
RepoDiscoveryPage.tsx; locate the stray closing brace/array and delete it so the
existing queryParams useMemo (the one that already has its correct dependency
array) remains intact and correctly closed, ensuring no extra closing tokens
remain that break the ESLint/parser.

Source: Pipeline failures


438-443: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Conflicting displayedRepos definitions will cause a build error.

There are two definitions of displayedRepos:

  • Lines 438-442: A useMemo referencing undefined repos variable and recentlyViewed
  • Line 443: A direct assignment using data?.repos

The first definition references repos which doesn't exist in scope, and having duplicate const declarations for the same identifier is invalid. Remove the orphaned useMemo block (lines 438-442).

-  const displayedRepos = useMemo(() => {
-    if (showSaved) return bookmarkedData || [];
-    if (viewedOnly) return recentlyViewed;
-    return repos;
-  }, [repos, showSaved, viewedOnly, recentlyViewed, bookmarkedData]);
   const displayedRepos = showSaved ? (bookmarkedData ?? []) : (data?.repos ?? []);
📝 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.

  const displayedRepos = showSaved ? (bookmarkedData ?? []) : (data?.repos ?? []);
🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 438
- 443, Remove the orphaned useMemo block that redeclares displayedRepos; keep
the single valid declaration (const displayedRepos = showSaved ? (bookmarkedData
?? []) : (data?.repos ?? [])); delete the useMemo(...) that references
non-existent repos and recentlyViewed (and any import/variable references only
used by that removed block) so there is only one displayedRepos identifier (and
no duplicate const) relying on bookmarkedData, viewedOnly/viewed flag,
recentlyViewed, and data?.repos as intended.

1385-1441: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Orphaned RecommendedSection component should be removed.

This component is defined but never used—the inline recommendations UI at lines 997-1032 already handles this functionality. Additionally, this component has contract mismatches:

  1. Uses queryKeys.opensource.list({ recommended: "true" }) instead of queryKeys.opensource.recommended()
  2. Expects response shape { repos } but server returns { recommended } (per context snippet 3)
  3. Missing bookmarked and onToggleBookmark props on RepoCard

Since the inline implementation is correct and this component is dead code, remove it entirely.

🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 1385
- 1441, Remove the dead RecommendedSection component entirely: delete the
function RecommendedSection and its JSX; ensure you don't leave any unused
imports related to it. If any references remain, replace them to use the
existing inline recommendations implementation (which uses
queryKeys.opensource.recommended()) rather than queryKeys.opensource.list({
recommended: "true" }). Also ensure no code expects the old response shape ({
repos }) or calls RepoCard without the required bookmarked and onToggleBookmark
props — confirm the inline implementation supplies the server's { recommended }
payload and passes bookmarked and onToggleBookmark to RepoCard.

@github-actions github-actions Bot added enhancement New feature or request gssoc labels Jun 7, 2026
@dhaanisi dhaanisi force-pushed the feat/ai-powered-repo branch from ba5aa74 to 6bc07dd Compare June 7, 2026 20:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
client/src/module/recruiter/applications/ApplicationDetail.tsx (1)

159-179: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling for fetchDetail calls.

fetchDetail() is called without await or error handling at lines 162 and 174. Since fetchDetail can throw (line 121), these calls may produce unhandled promise rejections if the fetch fails.

🛡️ Recommended fix

Either await with error handling or add .catch():

 const handleAdvance = async () => {
   try {
     await api.patch(`/recruiter/applications/${applicationId}/advance`);
-    fetchDetail();
+    fetchDetail().catch(() => {
+      // Fetch failure after mutation is non-critical; mutation already succeeded
+    });
     toast.success("Application advanced");
   } catch {
     toast.error("Failed to advance");
   }
 };

Apply the same pattern to handleStatusChange at line 174.

🤖 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 `@client/src/module/recruiter/applications/ApplicationDetail.tsx` around lines
159 - 179, handleAdvance and handleStatusChange call fetchDetail() without
awaiting or handling errors; update both functions (handleAdvance and
handleStatusChange) to await fetchDetail() inside the existing try blocks (or
attach .catch) so any rejection is caught by the surrounding catch handler—i.e.,
replace the fire-and-forget fetchDetail() calls with await fetchDetail() (or
fetchDetail().catch(err => { throw err })) so failures from fetchDetail() don't
produce unhandled promise rejections.
🧹 Nitpick comments (3)
client/src/module/student/opensource/RepoDiscoveryPage.tsx (3)

357-369: ⚡ Quick win

Consider adding staleTime to avoid redundant recommendation fetches.

The main repo list query uses staleTime: 5 * 60 * 1000, but recommendedData has no staleTime. Since recommendations are cached server-side for 6 hours (per PR objectives), adding a client-side staleTime would reduce unnecessary network requests and improve UX.

♻️ Suggested change
   const { data: recommendedData, isFetching: isFetchingRec } = useQuery({
     queryKey: [...queryKeys.opensource.recommended(recommendationRefreshToken), viewedIds.join(",")],
     queryFn: async () => {
       const res = await api.get<{ recommended: RecommendedRepo[] }>("/opensource/recommended", {
         params: {
           forceRefresh: recommendationRefreshToken > 0 ? "true" : undefined,
           viewedIds: viewedIds.join(","),
         },
       });
       return res.data;
     },
     enabled: !!user,
+    staleTime: 5 * 60 * 1000,
   });
🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 357
- 369, Add a client-side staleTime to the useQuery call that fetches
recommendedData to avoid redundant network requests; update the useQuery for
recommendedData (the call using queryKey
[...queryKeys.opensource.recommended(recommendationRefreshToken),
viewedIds.join(",")]) to include a staleTime matching the server-side cache
(e.g., 6 hours) and optionally adjust refetch behavior
(refetchOnWindowFocus/refetchOnReconnect) so fetches only occur when
recommendationRefreshToken changes or after the stale window.

61-62: ⚡ Quick win

Consider using the Button component instead of raw class strings.

Per coding guidelines, all buttons should use the reusable Button component from client/src/components/ui/button.tsx. The ghostBtnCls constant is applied to the Refresh button at line 974. Consider using <Button variant="ghost" size="sm"> instead.

♻️ Suggested refactor
-const ghostBtnCls =
-  "inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-bold border border-stone-200 dark:border-white/10 text-stone-600 dark:text-stone-300 hover:border-stone-400 dark:hover:border-white/25 bg-white dark:bg-stone-900 disabled:opacity-60 disabled:cursor-not-allowed transition-colors";

Then at line 970-978:

-              <button
-                type="button"
-                onClick={handleRefreshRecs}
-                disabled={isFetchingRec}
-                className={ghostBtnCls}
-              >
+              <Button
+                variant="ghost"
+                size="sm"
+                onClick={handleRefreshRecs}
+                disabled={isFetchingRec}
+              >
                 <RefreshCw className={`w-3.5 h-3.5 ${isFetchingRec ? "animate-spin" : ""}`} />
                 Refresh
-              </button>
+              </Button>
🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 61 -
62, The file defines a raw class string ghostBtnCls and applies it to the
Refresh button in RepoDiscoveryPage.tsx; replace that usage with the shared
Button component from client/src/components/ui/button.tsx (e.g. <Button
variant="ghost" size="sm">) keeping the same props (disabled, onClick) and
children (icon + label), and remove the ghostBtnCls constant if it’s only used
for this button so styling comes from the Button variant instead.

Source: Coding guidelines


50-59: Confirm GOOD_FIRST_LABELS are identical across client and server; dedupe for maintainability.

GOOD_FIRST_LABELS matches exactly (same 8 strings) in:

  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • client/src/module/student/opensource/RepoCard.tsx
  • server/src/module/opensource/opensource.service.ts

Since the constant is duplicated (including twice in client), extract to a shared source to avoid future drift.

🤖 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 `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 50 -
59, The GOOD_FIRST_LABELS array is duplicated; extract it to a single exported
constant (e.g., export const GOOD_FIRST_LABELS = [...]) in a shared module and
update all usages to import that constant instead of defining their own arrays:
replace the local GOOD_FIRST_LABELS in RepoDiscoveryPage and RepoCard and the
server opensource.service with an import from the new shared module so client
and server reference the same source of truth and avoid future drift.
🤖 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 `@client/src/module/student/roadmap/RoadmapCanvasPage.tsx`:
- Line 499: The level state in RoadmapCanvasPage (useState variable level /
setter setLevel) includes "ALL_LEVELS" but the <select> options and its onChange
handler only handle BEGINNER, INTERMEDIATE, ADVANCED, causing "ALL_LEVELS" from
the backend (initial value set during initialization) to not be selectable or
displayed; either add an "ALL_LEVELS" option to the dropdown and update the
onChange casting to include "ALL_LEVELS", or remove "ALL_LEVELS" from the
useState type and any initialization logic that sets it (so level is only
"BEGINNER" | "INTERMEDIATE" | "ADVANCED"); update the code paths that
read/assign level (including the initialization where level is set and the
select onChange handler) to use the chosen approach consistently.

---

Outside diff comments:
In `@client/src/module/recruiter/applications/ApplicationDetail.tsx`:
- Around line 159-179: handleAdvance and handleStatusChange call fetchDetail()
without awaiting or handling errors; update both functions (handleAdvance and
handleStatusChange) to await fetchDetail() inside the existing try blocks (or
attach .catch) so any rejection is caught by the surrounding catch handler—i.e.,
replace the fire-and-forget fetchDetail() calls with await fetchDetail() (or
fetchDetail().catch(err => { throw err })) so failures from fetchDetail() don't
produce unhandled promise rejections.

---

Nitpick comments:
In `@client/src/module/student/opensource/RepoDiscoveryPage.tsx`:
- Around line 357-369: Add a client-side staleTime to the useQuery call that
fetches recommendedData to avoid redundant network requests; update the useQuery
for recommendedData (the call using queryKey
[...queryKeys.opensource.recommended(recommendationRefreshToken),
viewedIds.join(",")]) to include a staleTime matching the server-side cache
(e.g., 6 hours) and optionally adjust refetch behavior
(refetchOnWindowFocus/refetchOnReconnect) so fetches only occur when
recommendationRefreshToken changes or after the stale window.
- Around line 61-62: The file defines a raw class string ghostBtnCls and applies
it to the Refresh button in RepoDiscoveryPage.tsx; replace that usage with the
shared Button component from client/src/components/ui/button.tsx (e.g. <Button
variant="ghost" size="sm">) keeping the same props (disabled, onClick) and
children (icon + label), and remove the ghostBtnCls constant if it’s only used
for this button so styling comes from the Button variant instead.
- Around line 50-59: The GOOD_FIRST_LABELS array is duplicated; extract it to a
single exported constant (e.g., export const GOOD_FIRST_LABELS = [...]) in a
shared module and update all usages to import that constant instead of defining
their own arrays: replace the local GOOD_FIRST_LABELS in RepoDiscoveryPage and
RepoCard and the server opensource.service with an import from the new shared
module so client and server reference the same source of truth and avoid future
drift.
🪄 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: 08457736-bfea-46d9-9290-cf9c664442c3

📥 Commits

Reviewing files that changed from the base of the PR and between ba5aa74 and 6bc07dd.

📒 Files selected for processing (10)
  • client/src/components/OssContributionHeatmap.tsx
  • client/src/components/ui/ConfirmDialog.tsx
  • client/src/module/recruiter/applications/ApplicationDetail.tsx
  • client/src/module/recruiter/applications/CandidateImportPage.tsx
  • client/src/module/student/applications/MyApplicationsPage.tsx
  • client/src/module/student/applications/questions/interviewQuestions.ts
  • client/src/module/student/companies/CompanyDetailPage.tsx
  • client/src/module/student/opensource/GSoCReposPage.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • client/src/module/student/roadmap/RoadmapCanvasPage.tsx
✅ Files skipped from review due to trivial changes (3)
  • client/src/components/ui/ConfirmDialog.tsx
  • client/src/module/recruiter/applications/CandidateImportPage.tsx
  • client/src/module/student/applications/MyApplicationsPage.tsx

const [title, setTitle] = useState("");
const [shortDescription, setShortDescription] = useState("");
const [level, setLevel] = useState<"BEGINNER" | "INTERMEDIATE" | "ADVANCED">(
const [level, setLevel] = useState<"BEGINNER" | "INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS">(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Level type includes "ALL_LEVELS" but UI select does not.

The level state type at line 499 includes "ALL_LEVELS", but the select dropdown at lines 1610-1620 only offers BEGINNER, INTERMEDIATE, and ADVANCED. Additionally, the onChange handler at lines 1603-1606 casts to only those three values.

If the backend returns level: "ALL_LEVELS" (initialized at line 589), the select will not display it as a selected option, and the user cannot select it.

Either:

  1. Add an ALL_LEVELS option to the select if it's a valid user choice, or
  2. Remove "ALL_LEVELS" from the type union if it's not user-selectable via this UI
💡 Possible fix if ALL_LEVELS should be selectable
             <select
               value={level}
-              onChange={(e) =>
-                setLevel(
-                  e.target.value as "BEGINNER" | "INTERMEDIATE" | "ADVANCED",
-                )
-              }
+              onChange={(e) =>
+                setLevel(
+                  e.target.value as "BEGINNER" | "INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS",
+                )
+              }
               className="w-full rounded-xl border border-stone-700 bg-stone-900 px-4 py-3 text-stone-50 outline-none focus:border-lime-500"
             >
               <option value="BEGINNER">
                 Beginner
               </option>

               <option value="INTERMEDIATE">
                 Intermediate
               </option>

               <option value="ADVANCED">
                 Advanced
               </option>
+
+              <option value="ALL_LEVELS">
+                All Levels
+              </option>
             </select>
📝 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
const [level, setLevel] = useState<"BEGINNER" | "INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS">(
<select
value={level}
onChange={(e) =>
setLevel(
e.target.value as "BEGINNER" | "INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS",
)
}
className="w-full rounded-xl border border-stone-700 bg-stone-900 px-4 py-3 text-stone-50 outline-none focus:border-lime-500"
>
<option value="BEGINNER">
Beginner
</option>
<option value="INTERMEDIATE">
Intermediate
</option>
<option value="ADVANCED">
Advanced
</option>
<option value="ALL_LEVELS">
All Levels
</option>
</select>
🤖 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 `@client/src/module/student/roadmap/RoadmapCanvasPage.tsx` at line 499, The
level state in RoadmapCanvasPage (useState variable level / setter setLevel)
includes "ALL_LEVELS" but the <select> options and its onChange handler only
handle BEGINNER, INTERMEDIATE, ADVANCED, causing "ALL_LEVELS" from the backend
(initial value set during initialization) to not be selectable or displayed;
either add an "ALL_LEVELS" option to the dropdown and update the onChange
casting to include "ALL_LEVELS", or remove "ALL_LEVELS" from the useState type
and any initialization logic that sets it (so level is only "BEGINNER" |
"INTERMEDIATE" | "ADVANCED"); update the code paths that read/assign level
(including the initialization where level is set and the select onChange
handler) to use the chosen approach consistently.

@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

Caution

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

⚠️ Outside diff range comments (2)
server/src/module/opensource/opensource.service.ts (2)

672-680: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass tags into Gemini if recommendations are supposed to match on tags.

The candidate payload only includes language, techStack, domain, trending, and issueTypes. The PR objective explicitly says matching should consider repository tags, so the model currently has no way to use that signal.

🔧 Minimal fix
 ${JSON.stringify(
   candidateRepos.map((repo) => ({
     id: repo.id,
     name: repo.name,
     language: repo.language,
     techStack: repo.techStack,
+    tags: repo.tags,
     domain: repo.domain,
     trending: repo.trending,
     issueTypes: repo.issueTypes,
   })),
 )}
🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 672 - 680,
The candidate mapping in opensource.service.ts (the candidateRepos.map block)
omits repository tags, so include tags in the payload passed to Gemini by adding
tags: repo.tags (or repo?.tags) to the object returned by candidateRepos.map;
also verify the repo type/interface used by the function (e.g., the repo model
or DTO referenced where candidateRepos is created) exposes a tags field and
update that type if necessary so Gemini receives the tag signal for matching.

647-651: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don’t cache a viewedIds-dependent result under userId alone.

The miss path excludes viewedIds before ranking, but the cached value is keyed only by userId. A request from one device/session can therefore shrink another session’s recommendation pool for the full 6-hour TTL. It also hard-excludes viewed repos instead of de-ranking them as described in the PR objective.

Also applies to: 706-709

🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 647 - 651,
The current prisma.opensourceRepo.findMany call excludes repos by viewedIds but
the cached result is keyed only by userId, causing one session's viewedIds to
affect others; fix by removing the where: viewedIds exclusion in
prisma.opensourceRepo.findMany so the DB returns the full candidate list (update
the candidateRepos query), then de-rank viewed repos in-memory by using
viewedIds to push those items to the end of the list (or reduce their score)
before returning; and change caching so you either (A) cache only the base
candidate list under userId and apply viewedIds-based de-ranking per-request
without writing a viewed-dependent value to that same key, or (B) include a
short deterministic signature/hash of viewedIds in the cache key (instead of
caching solely by userId) to avoid cross-session pollution (referencing
candidateRepos, viewedIds, userId, and prisma.opensourceRepo.findMany).
♻️ Duplicate comments (1)
client/src/module/student/roadmap/RoadmapCanvasPage.tsx (1)

501-501: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Level type includes "ALL_LEVELS" but UI select does not.

This is the same issue flagged in the previous review. The level state type includes "ALL_LEVELS", but the select dropdown (lines 1610-1622) only offers BEGINNER, INTERMEDIATE, and ADVANCED, and the onChange handler (lines 1605-1609) only casts to those three values.

If the backend returns level: "ALL_LEVELS" (set at line 591), the select will not display it as a selected option, and the user cannot select it.

Either add an "ALL_LEVELS" option to the select and update the onChange cast, or remove "ALL_LEVELS" from the type union if it's not user-selectable via this UI.

🤖 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 `@client/src/module/student/roadmap/RoadmapCanvasPage.tsx` at line 501, The
union type for the React state variable level (useState<"BEGINNER" |
"INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS">) is out of sync with the select
dropdown and its onChange handler (which only handles BEGINNER, INTERMEDIATE,
ADVANCED) and with the backend possibly returning "ALL_LEVELS"; either add an
"ALL_LEVELS" option to the select options list and include it in the onChange
cast so setLevel(...) can accept it, or remove "ALL_LEVELS" from the useState
union (and any backend mapping code that sets level to "ALL_LEVELS") so the UI
and state type match; locate and update the level state declaration, the select
element/options, and the onChange handler that calls setLevel to keep the values
consistent.
🤖 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 `@server/src/module/opensource/opensource.service.ts`:
- Around line 456-457: The octStart/novStart values are hard-coded to 2026;
update the calculation to derive the Hacktoberfest window from the current year
instead. Replace the fixed Date.UTC(2026, ...) uses for octStart and novStart
with a year variable computed from new Date().getUTCFullYear() (or similar
UTC-aware current-year logic) so octStart = new Date(Date.UTC(year, 9, 1)) and
novStart = new Date(Date.UTC(year, 10, 1)); modify the occurrences of octStart
and novStart in the opensource.service.ts logic accordingly.
- Around line 129-131: The file contains a duplicate declaration of the variable
trimmedSearch (const trimmedSearch = search?.trim();) in the same scope inside
the OpenSourceService logic; remove the redundant declaration so only one const
trimmedSearch exists and ensure any subsequent code (calls, checks, or uses)
reference that single variable (trimmedSearch) in the same function/method
(e.g., the method where trimmedSearch is declared in opensource.service.ts).

---

Outside diff comments:
In `@server/src/module/opensource/opensource.service.ts`:
- Around line 672-680: The candidate mapping in opensource.service.ts (the
candidateRepos.map block) omits repository tags, so include tags in the payload
passed to Gemini by adding tags: repo.tags (or repo?.tags) to the object
returned by candidateRepos.map; also verify the repo type/interface used by the
function (e.g., the repo model or DTO referenced where candidateRepos is
created) exposes a tags field and update that type if necessary so Gemini
receives the tag signal for matching.
- Around line 647-651: The current prisma.opensourceRepo.findMany call excludes
repos by viewedIds but the cached result is keyed only by userId, causing one
session's viewedIds to affect others; fix by removing the where: viewedIds
exclusion in prisma.opensourceRepo.findMany so the DB returns the full candidate
list (update the candidateRepos query), then de-rank viewed repos in-memory by
using viewedIds to push those items to the end of the list (or reduce their
score) before returning; and change caching so you either (A) cache only the
base candidate list under userId and apply viewedIds-based de-ranking
per-request without writing a viewed-dependent value to that same key, or (B)
include a short deterministic signature/hash of viewedIds in the cache key
(instead of caching solely by userId) to avoid cross-session pollution
(referencing candidateRepos, viewedIds, userId, and
prisma.opensourceRepo.findMany).

---

Duplicate comments:
In `@client/src/module/student/roadmap/RoadmapCanvasPage.tsx`:
- Line 501: The union type for the React state variable level
(useState<"BEGINNER" | "INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS">) is out of
sync with the select dropdown and its onChange handler (which only handles
BEGINNER, INTERMEDIATE, ADVANCED) and with the backend possibly returning
"ALL_LEVELS"; either add an "ALL_LEVELS" option to the select options list and
include it in the onChange cast so setLevel(...) can accept it, or remove
"ALL_LEVELS" from the useState union (and any backend mapping code that sets
level to "ALL_LEVELS") so the UI and state type match; locate and update the
level state declaration, the select element/options, and the onChange handler
that calls setLevel to keep the values consistent.
🪄 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: c96541e9-44aa-42be-8f39-fb89a7921334

📥 Commits

Reviewing files that changed from the base of the PR and between 6bc07dd and 00bfa64.

📒 Files selected for processing (10)
  • client/src/lib/query-keys.ts
  • client/src/lib/types/opensource.types.ts
  • client/src/module/recruiter/applications/ApplicationDetail.tsx
  • client/src/module/student/applications/MyApplicationsPage.tsx
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • client/src/module/student/roadmap/RoadmapCanvasPage.tsx
  • server/src/module/opensource/opensource.controller.ts
  • server/src/module/opensource/opensource.service.ts
  • server/src/module/opensource/opensource.validation.ts
✅ Files skipped from review due to trivial changes (2)
  • server/src/module/opensource/opensource.validation.ts
  • client/src/module/student/applications/MyApplicationsPage.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • server/src/module/opensource/opensource.controller.ts
  • client/src/lib/types/opensource.types.ts
  • client/src/lib/query-keys.ts
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • client/src/module/recruiter/applications/ApplicationDetail.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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
server/src/module/opensource/opensource.service.ts (2)

672-680: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass tags into Gemini if recommendations are supposed to match on tags.

The candidate payload only includes language, techStack, domain, trending, and issueTypes. The PR objective explicitly says matching should consider repository tags, so the model currently has no way to use that signal.

🔧 Minimal fix
 ${JSON.stringify(
   candidateRepos.map((repo) => ({
     id: repo.id,
     name: repo.name,
     language: repo.language,
     techStack: repo.techStack,
+    tags: repo.tags,
     domain: repo.domain,
     trending: repo.trending,
     issueTypes: repo.issueTypes,
   })),
 )}
🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 672 - 680,
The candidate mapping in opensource.service.ts (the candidateRepos.map block)
omits repository tags, so include tags in the payload passed to Gemini by adding
tags: repo.tags (or repo?.tags) to the object returned by candidateRepos.map;
also verify the repo type/interface used by the function (e.g., the repo model
or DTO referenced where candidateRepos is created) exposes a tags field and
update that type if necessary so Gemini receives the tag signal for matching.

647-651: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don’t cache a viewedIds-dependent result under userId alone.

The miss path excludes viewedIds before ranking, but the cached value is keyed only by userId. A request from one device/session can therefore shrink another session’s recommendation pool for the full 6-hour TTL. It also hard-excludes viewed repos instead of de-ranking them as described in the PR objective.

Also applies to: 706-709

🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 647 - 651,
The current prisma.opensourceRepo.findMany call excludes repos by viewedIds but
the cached result is keyed only by userId, causing one session's viewedIds to
affect others; fix by removing the where: viewedIds exclusion in
prisma.opensourceRepo.findMany so the DB returns the full candidate list (update
the candidateRepos query), then de-rank viewed repos in-memory by using
viewedIds to push those items to the end of the list (or reduce their score)
before returning; and change caching so you either (A) cache only the base
candidate list under userId and apply viewedIds-based de-ranking per-request
without writing a viewed-dependent value to that same key, or (B) include a
short deterministic signature/hash of viewedIds in the cache key (instead of
caching solely by userId) to avoid cross-session pollution (referencing
candidateRepos, viewedIds, userId, and prisma.opensourceRepo.findMany).
♻️ Duplicate comments (1)
client/src/module/student/roadmap/RoadmapCanvasPage.tsx (1)

501-501: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Level type includes "ALL_LEVELS" but UI select does not.

This is the same issue flagged in the previous review. The level state type includes "ALL_LEVELS", but the select dropdown (lines 1610-1622) only offers BEGINNER, INTERMEDIATE, and ADVANCED, and the onChange handler (lines 1605-1609) only casts to those three values.

If the backend returns level: "ALL_LEVELS" (set at line 591), the select will not display it as a selected option, and the user cannot select it.

Either add an "ALL_LEVELS" option to the select and update the onChange cast, or remove "ALL_LEVELS" from the type union if it's not user-selectable via this UI.

🤖 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 `@client/src/module/student/roadmap/RoadmapCanvasPage.tsx` at line 501, The
union type for the React state variable level (useState<"BEGINNER" |
"INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS">) is out of sync with the select
dropdown and its onChange handler (which only handles BEGINNER, INTERMEDIATE,
ADVANCED) and with the backend possibly returning "ALL_LEVELS"; either add an
"ALL_LEVELS" option to the select options list and include it in the onChange
cast so setLevel(...) can accept it, or remove "ALL_LEVELS" from the useState
union (and any backend mapping code that sets level to "ALL_LEVELS") so the UI
and state type match; locate and update the level state declaration, the select
element/options, and the onChange handler that calls setLevel to keep the values
consistent.
🤖 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 `@server/src/module/opensource/opensource.service.ts`:
- Around line 456-457: The octStart/novStart values are hard-coded to 2026;
update the calculation to derive the Hacktoberfest window from the current year
instead. Replace the fixed Date.UTC(2026, ...) uses for octStart and novStart
with a year variable computed from new Date().getUTCFullYear() (or similar
UTC-aware current-year logic) so octStart = new Date(Date.UTC(year, 9, 1)) and
novStart = new Date(Date.UTC(year, 10, 1)); modify the occurrences of octStart
and novStart in the opensource.service.ts logic accordingly.
- Around line 129-131: The file contains a duplicate declaration of the variable
trimmedSearch (const trimmedSearch = search?.trim();) in the same scope inside
the OpenSourceService logic; remove the redundant declaration so only one const
trimmedSearch exists and ensure any subsequent code (calls, checks, or uses)
reference that single variable (trimmedSearch) in the same function/method
(e.g., the method where trimmedSearch is declared in opensource.service.ts).

---

Outside diff comments:
In `@server/src/module/opensource/opensource.service.ts`:
- Around line 672-680: The candidate mapping in opensource.service.ts (the
candidateRepos.map block) omits repository tags, so include tags in the payload
passed to Gemini by adding tags: repo.tags (or repo?.tags) to the object
returned by candidateRepos.map; also verify the repo type/interface used by the
function (e.g., the repo model or DTO referenced where candidateRepos is
created) exposes a tags field and update that type if necessary so Gemini
receives the tag signal for matching.
- Around line 647-651: The current prisma.opensourceRepo.findMany call excludes
repos by viewedIds but the cached result is keyed only by userId, causing one
session's viewedIds to affect others; fix by removing the where: viewedIds
exclusion in prisma.opensourceRepo.findMany so the DB returns the full candidate
list (update the candidateRepos query), then de-rank viewed repos in-memory by
using viewedIds to push those items to the end of the list (or reduce their
score) before returning; and change caching so you either (A) cache only the
base candidate list under userId and apply viewedIds-based de-ranking
per-request without writing a viewed-dependent value to that same key, or (B)
include a short deterministic signature/hash of viewedIds in the cache key
(instead of caching solely by userId) to avoid cross-session pollution
(referencing candidateRepos, viewedIds, userId, and
prisma.opensourceRepo.findMany).

---

Duplicate comments:
In `@client/src/module/student/roadmap/RoadmapCanvasPage.tsx`:
- Line 501: The union type for the React state variable level
(useState<"BEGINNER" | "INTERMEDIATE" | "ADVANCED" | "ALL_LEVELS">) is out of
sync with the select dropdown and its onChange handler (which only handles
BEGINNER, INTERMEDIATE, ADVANCED) and with the backend possibly returning
"ALL_LEVELS"; either add an "ALL_LEVELS" option to the select options list and
include it in the onChange cast so setLevel(...) can accept it, or remove
"ALL_LEVELS" from the useState union (and any backend mapping code that sets
level to "ALL_LEVELS") so the UI and state type match; locate and update the
level state declaration, the select element/options, and the onChange handler
that calls setLevel to keep the values consistent.
🪄 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: c96541e9-44aa-42be-8f39-fb89a7921334

📥 Commits

Reviewing files that changed from the base of the PR and between 6bc07dd and 00bfa64.

📒 Files selected for processing (10)
  • client/src/lib/query-keys.ts
  • client/src/lib/types/opensource.types.ts
  • client/src/module/recruiter/applications/ApplicationDetail.tsx
  • client/src/module/student/applications/MyApplicationsPage.tsx
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • client/src/module/student/roadmap/RoadmapCanvasPage.tsx
  • server/src/module/opensource/opensource.controller.ts
  • server/src/module/opensource/opensource.service.ts
  • server/src/module/opensource/opensource.validation.ts
✅ Files skipped from review due to trivial changes (2)
  • server/src/module/opensource/opensource.validation.ts
  • client/src/module/student/applications/MyApplicationsPage.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • server/src/module/opensource/opensource.controller.ts
  • client/src/lib/types/opensource.types.ts
  • client/src/lib/query-keys.ts
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • client/src/module/recruiter/applications/ApplicationDetail.tsx
🛑 Comments failed to post (2)
server/src/module/opensource/opensource.service.ts (2)

129-131: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="server/src/module/opensource/opensource.service.ts"

echo "== duplicate declaration site =="
awk 'NR>=122 && NR<=134 {printf "%6d\t%s\n", NR, $0}' "$FILE"

echo
echo "== all matching declarations in file =="
rg -n 'const trimmedSearch = search\?\.trim\(\);' "$FILE"

Repository: Sachinchaurasiya360/InternHack

Length of output: 778


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="server/src/module/opensource/opensource.service.ts"

echo "== context around line 129 =="
nl -ba "$FILE" | sed -n '105,160p'

Repository: Sachinchaurasiya360/InternHack

Length of output: 165


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="server/src/module/opensource/opensource.service.ts"

echo "== context around line 129 (120-145) =="
awk -v start=115 -v end=150 '{if(NR>=start && NR<=end){printf "%6d\t%s\n", NR, $0}}' "$FILE"

echo
echo "== other occurrence around line 260 (245-275) =="
awk -v start=235 -v end=280 '{if(NR>=start && NR<=end){printf "%6d\t%s\n", NR, $0}}' "$FILE"

Repository: Sachinchaurasiya360/InternHack

Length of output: 3273


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="server/src/module/opensource/opensource.service.ts"

echo "== Cache key / viewedIds dependencies (search) =="
rg -n "viewedIds|cache|recommend|getRecommendedRepos" "$FILE"

echo
echo "== Hard-coded Hacktoberfest date bounds =="
rg -n "Date\.UTC\(" "$FILE"

echo
echo "== Gemini prompt candidate payload construction =="
rg -n "getRecommendedRepos|GoogleGenerativeAI|gemini|prompt|candidates|candidate|techStack|issueTypes|domain|trending|tags" "$FILE"

Repository: Sachinchaurasiya360/InternHack

Length of output: 3196


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="server/src/module/opensource/opensource.service.ts"

echo "== getHacktoberfestProgress context (420-560) =="
awk 'NR>=410 && NR<=560 {printf "%6d\t%s\n", NR, $0}' "$FILE"

echo
echo "== getRecommendedRepos context (600-740) =="
awk 'NR>=590 && NR<=740 {printf "%6d\t%s\n", NR, $0}' "$FILE"

echo
echo "== getRecommendedRepos: occurrences of 'tags' in nearby area =="
rg -n "getRecommendedRepos|tags" "$FILE" | sed -n '580,740p'

Repository: Sachinchaurasiya360/InternHack

Length of output: 10934


Remove the duplicate trimmedSearch declaration in server/src/module/opensource/opensource.service.ts (lines 129–131).

const trimmedSearch = search?.trim(); is declared twice in the same scope, which will fail TypeScript compilation.

🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 129 - 131,
The file contains a duplicate declaration of the variable trimmedSearch (const
trimmedSearch = search?.trim();) in the same scope inside the OpenSourceService
logic; remove the redundant declaration so only one const trimmedSearch exists
and ensure any subsequent code (calls, checks, or uses) reference that single
variable (trimmedSearch) in the same function/method (e.g., the method where
trimmedSearch is declared in opensource.service.ts).

456-457: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Derive the Hacktoberfest window instead of hard-coding 2026.

Date.UTC(2026, 9, 1) and Date.UTC(2026, 10, 1) lock this endpoint to October 2026. Starting January 2027, it will still report against October 1, 2026 through November 1, 2026.

🔧 Suggested direction
-    const octStart = new Date(Date.UTC(2026, 9, 1));
-    const novStart = new Date(Date.UTC(2026, 10, 1));
+    const year = new Date().getUTCFullYear();
+    const octStart = new Date(Date.UTC(year, 9, 1));
+    const novStart = new Date(Date.UTC(year, 10, 1));
🤖 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 `@server/src/module/opensource/opensource.service.ts` around lines 456 - 457,
The octStart/novStart values are hard-coded to 2026; update the calculation to
derive the Hacktoberfest window from the current year instead. Replace the fixed
Date.UTC(2026, ...) uses for octStart and novStart with a year variable computed
from new Date().getUTCFullYear() (or similar UTC-aware current-year logic) so
octStart = new Date(Date.UTC(year, 9, 1)) and novStart = new Date(Date.UTC(year,
10, 1)); modify the occurrences of octStart and novStart in the
opensource.service.ts logic accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
client/src/module/recruiter/applications/ApplicationDetail.tsx (1)

105-118: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove duplicated fetchDetail/catch blocks (compile blocker).

Line 106 and Line 116 introduce nested duplicate declarations, which makes this file fail parsing/build.

Proposed fix
-  const fetchDetail = useCallback(async (signal?: AbortSignal) => {
-const fetchDetail = useCallback(async (signal?: AbortSignal) => {
+  const fetchDetail = useCallback(async (signal?: AbortSignal) => {
     try {
       const res = await api.get(
         `/recruiter/applications/${applicationId}`,
         { signal }
       );
       setApplication(res.data.application);
       return res.data.application;
-    } catch (err: any) {
-      if (err.name !== "CanceledError" && err.name !== "AbortError") {
-    } catch (err) {
-      if (err instanceof Error && err.name !== "CanceledError" && err.name !== "AbortError") {
+    } catch (err) {
+      if (
+        err instanceof Error &&
+        err.name !== "CanceledError" &&
+        err.name !== "AbortError"
+      ) {
         console.error(err);
       }
       throw err;
     }
   }, [applicationId]);
🤖 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 `@client/src/module/recruiter/applications/ApplicationDetail.tsx` around lines
105 - 118, There are duplicate nested declarations of the fetchDetail function
and duplicate catch blocks causing a parse error; locate the fetchDetail
useCallback (function name fetchDetail) and remove the inner duplicated
declaration and its extra catch so only one async function remains that calls
api.get(`/recruiter/applications/${applicationId}`, { signal }), calls
setApplication(res.data.application), and has a single catch that checks err
instanceof Error (or err.name) and logs non-abort errors (e.g.,
console.error(err)); ensure the function signature remains useCallback(async
(signal?: AbortSignal) => { ... }, [applicationId]) and no duplicated blocks
remain.

Source: Linters/SAST tools

🤖 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 `@client/src/module/student/applications/MyApplicationsPage.tsx`:
- Around line 300-315: The filteredExternal useMemo is missing status filtering
so external tabs behave inconsistently; update the computation in
filteredExternal (and its dependency array) to apply the same statusFilter logic
used for internal applications: first filter externalApplications by
statusFilter (e.g., compare a.status to statusFilter when statusFilter !==
"all"), then apply the debouncedSearch filter and the sortOption handling in the
useMemo, and add statusFilter to the dependencies so counts and tab behavior
stay consistent.

---

Outside diff comments:
In `@client/src/module/recruiter/applications/ApplicationDetail.tsx`:
- Around line 105-118: There are duplicate nested declarations of the
fetchDetail function and duplicate catch blocks causing a parse error; locate
the fetchDetail useCallback (function name fetchDetail) and remove the inner
duplicated declaration and its extra catch so only one async function remains
that calls api.get(`/recruiter/applications/${applicationId}`, { signal }),
calls setApplication(res.data.application), and has a single catch that checks
err instanceof Error (or err.name) and logs non-abort errors (e.g.,
console.error(err)); ensure the function signature remains useCallback(async
(signal?: AbortSignal) => { ... }, [applicationId]) and no duplicated blocks
remain.
🪄 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: 784847c2-4cec-4e44-b376-eccba0110045

📥 Commits

Reviewing files that changed from the base of the PR and between 00bfa64 and 1002419.

📒 Files selected for processing (9)
  • client/src/module/recruiter/applications/ApplicationDetail.tsx
  • client/src/module/student/applications/MyApplicationsPage.tsx
  • client/src/module/student/dsa/components/AiHintPanel.tsx
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx
  • client/src/module/student/profile/components/ProfilePageHeader.tsx
  • client/src/module/student/roadmap/RoadmapDashboardPage.tsx
  • client/src/module/student/roadmap/RoadmapsLandingPage.tsx
  • server/src/module/opensource/opensource.service.ts
💤 Files with no reviewable changes (1)
  • server/src/module/opensource/opensource.service.ts
✅ Files skipped from review due to trivial changes (2)
  • client/src/module/student/profile/components/ProfilePageHeader.tsx
  • client/src/module/student/dsa/components/AiHintPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/src/module/student/opensource/RepoCard.tsx
  • client/src/module/student/opensource/RepoDiscoveryPage.tsx

Comment on lines 300 to +315
const filteredExternal = useMemo(() => {
let base = !debouncedSearch.trim()
const base = !debouncedSearch.trim()
? externalApplications
: externalApplications.filter(
(a) =>
a.adminJob.role?.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
a.adminJob.company?.toLowerCase().includes(debouncedSearch.toLowerCase())
);

if (statusFilter !== "ALL") {
base = base.filter((a) => a.status === statusFilter);
}

return [...base].sort((a, b) => {
if (sortOption === "newest") return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
if (sortOption === "oldest") return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
if (sortOption === "company") return (a.adminJob.company ?? "").localeCompare(b.adminJob.company ?? "");
return 0;
});
}, [externalApplications, debouncedSearch, sortOption, statusFilter]);
}, [externalApplications, debouncedSearch, sortOption]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reapply status filtering for external applications to keep tab behavior consistent.

Line 301–315 dropped statusFilter from filteredExternal, so status tabs filter internal cards but not external cards. This causes inconsistent results and counts.

Proposed fix
   const filteredExternal = useMemo(() => {
-    const base = !debouncedSearch.trim()
+    let base = !debouncedSearch.trim()
       ? externalApplications
       : externalApplications.filter(
         (a) =>
           a.adminJob.role?.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
           a.adminJob.company?.toLowerCase().includes(debouncedSearch.toLowerCase())
       );
+
+    if (statusFilter !== "ALL") {
+      base = base.filter((a) => a.status === statusFilter);
+    }

     return [...base].sort((a, b) => {
       if (sortOption === "newest") return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
       if (sortOption === "oldest") return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
       if (sortOption === "company") return (a.adminJob.company ?? "").localeCompare(b.adminJob.company ?? "");
       return 0;
     });
-  }, [externalApplications, debouncedSearch, sortOption]);
+  }, [externalApplications, debouncedSearch, sortOption, statusFilter]);
📝 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
const filteredExternal = useMemo(() => {
let base = !debouncedSearch.trim()
const base = !debouncedSearch.trim()
? externalApplications
: externalApplications.filter(
(a) =>
a.adminJob.role?.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
a.adminJob.company?.toLowerCase().includes(debouncedSearch.toLowerCase())
);
if (statusFilter !== "ALL") {
base = base.filter((a) => a.status === statusFilter);
}
return [...base].sort((a, b) => {
if (sortOption === "newest") return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
if (sortOption === "oldest") return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
if (sortOption === "company") return (a.adminJob.company ?? "").localeCompare(b.adminJob.company ?? "");
return 0;
});
}, [externalApplications, debouncedSearch, sortOption, statusFilter]);
}, [externalApplications, debouncedSearch, sortOption]);
const filteredExternal = useMemo(() => {
let base = !debouncedSearch.trim()
? externalApplications
: externalApplications.filter(
(a) =>
a.adminJob.role?.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
a.adminJob.company?.toLowerCase().includes(debouncedSearch.toLowerCase())
);
if (statusFilter !== "ALL") {
base = base.filter((a) => a.status === statusFilter);
}
return [...base].sort((a, b) => {
if (sortOption === "newest") return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
if (sortOption === "oldest") return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
if (sortOption === "company") return (a.adminJob.company ?? "").localeCompare(b.adminJob.company ?? "");
return 0;
});
}, [externalApplications, debouncedSearch, sortOption, statusFilter]);
🤖 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 `@client/src/module/student/applications/MyApplicationsPage.tsx` around lines
300 - 315, The filteredExternal useMemo is missing status filtering so external
tabs behave inconsistently; update the computation in filteredExternal (and its
dependency array) to apply the same statusFilter logic used for internal
applications: first filter externalApplications by statusFilter (e.g., compare
a.status to statusFilter when statusFilter !== "all"), then apply the
debouncedSearch filter and the sortOption handling in the useMemo, and add
statusFilter to the dependencies so counts and tab behavior stay consistent.

@dhaanisi

dhaanisi commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Hey @Sachinchaurasiya360! Can I get an update on this PR? I have solved the merge conflicts and build errors.

@Sachinchaurasiya360

Copy link
Copy Markdown
Owner

Review

Status: Changes requested

Critical: Committed build artifacts

The PR includes compiled output files that must not be tracked in git:

  • server/test-db.js
  • server/test-db.js.map
  • server/test-db.d.ts
  • server/test-db.d.ts.map

Remove these from the commit (git rm --cached server/test-db.*) and add them to .gitignore.

Regression: Status filter broken for external applications

MyApplicationsPage.tsx removes the status filter from filteredExternal:

// Removed — this breaks filtering external applications by status
-    if (statusFilter !== "ALL") {
-      base = base.filter((a) => a.status === statusFilter);
-    }

When a user selects e.g. "REJECTED" from the status chips, external applications no longer filter. Revert this change — it is unrelated to the recommendation feature and introduces a bug.

Screenshots required

This adds a "Recommended for you" carousel and match-reason chips to the repo discovery page. Please attach screenshots of both light and dark mode.

UI style violation

Sparkles icon is used in the repo discovery page — this icon is banned in this project. Replace with Wand2 (already imported) or BrainCircuit.

Unrelated changes

This PR mixes the recommendations feature with unrelated fixes across ApplicationDetail.tsx, CandidateImportPage.tsx, AiHintPanel.tsx, ConfirmDialog.tsx, interviewQuestions.ts, OssContributionHeatmap.tsx, and several roadmap files. Please either split these into a separate PR or confirm each change is intentional and explain it in the description.

No CI build/lint/test ran

Only label automation checks passed — the full CI suite did not run. Fix the above issues to get a clean CI run before re-requesting review.

@dhaanisi dhaanisi closed this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request gssoc:approved Approved for GSSoC scoring gssoc level:advanced Complex implementation or logic quality:clean Clean and well-structured contribution scope:backend Changes to server-side / API code scope:frontend Changes to client-side / UI code type:feature New feature implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants