Skip to content

fix: Handle missing leaderboard JSON gracefully instead of crashing#263

Open
notlevi911 wants to merge 3 commits intoCircuitVerse:mainfrom
notlevi911:main
Open

fix: Handle missing leaderboard JSON gracefully instead of crashing#263
notlevi911 wants to merge 3 commits intoCircuitVerse:mainfrom
notlevi911:main

Conversation

@notlevi911
Copy link
Copy Markdown

@notlevi911 notlevi911 commented Feb 16, 2026

Description

Replaces hard error throwing with graceful fallbacks when leaderboard JSON files are missing or corrupted. Instead of crashing with a 500 error, the page now displays an empty state with the existing "No contributors with points in this period" message.

Related Issue

Fixes #252

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation

Checklist

  • Code follows project style
  • Tested locally
  • No unnecessary files added
  • PR title is clear and descriptive

Screenshots (if applicable)

Not applicable - this is an error handling improvement that prevents crashes rather than adding visual changes.

Summary by CodeRabbit

  • Bug Fixes

    • Improved leaderboard reliability: app now falls back to a safe, empty leaderboard and shows a loading state instead of crashing when data is missing, corrupted, or invalid. Structured error handling and validation prevent user-facing failures.
  • Refactor

    • Simplified rendering flow to consistently reuse the leaderboard view and Suspense-style loading.

@netlify
Copy link
Copy Markdown

netlify Bot commented Feb 16, 2026

Deploy Preview for cv-community-dashboard ready!

Name Link
🔨 Latest commit 755dd05
🔍 Latest deploy log https://app.netlify.com/projects/cv-community-dashboard/deploys/699352f4fe31ce0008149d96
😎 Deploy Preview https://deploy-preview-263--cv-community-dashboard.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 16, 2026

Warning

Rate limit exceeded

@notlevi911 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

The leaderboard page now validates the period param and loads leaderboard JSON with guarded parsing. Missing or invalid period files are handled without throwing: parsed data is validated via a runtime type guard, JSON parse/load errors are caught and logged, and a minimal fallback LeaderboardJSON is returned and rendered inside a Suspense-wrapped LeaderboardView. The change also updates next-env.d.ts to import route types from ./.next/dev/types/routes.d.ts.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The next-env.d.ts import path change appears unrelated to graceful error handling objectives in issue #252 and may be out of scope. Clarify why the next-env.d.ts import path was changed from './.next/types/routes.d.ts' to './.next/dev/types/routes.d.ts' as it seems unrelated to leaderboard error handling.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: handling missing leaderboard JSON gracefully instead of crashing, which is the primary objective of this PR.
Description check ✅ Passed The description is complete with all required sections: clear description of changes, related issue (#252), type of change marked (bug fix), and all checklist items addressed.
Linked Issues check ✅ Passed The PR successfully addresses issue #252 by implementing graceful fallback handling for missing/corrupted leaderboard JSON, adding validation functions, and replacing hard errors with empty state display.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
app/leaderboard/[period]/page.tsx (3)

64-120: Extract duplicated fallback data and rendering into a helper.

The fallbackData object (lines 66-74 and 98-106) and the Suspense+LeaderboardView JSX block are repeated three times. Extract both to reduce duplication.

♻️ Proposed refactor

Add a helper function near the top of the file (after the type/constant declarations):

function renderLeaderboard(
  data: LeaderboardJSON,
  isGridView: boolean
) {
  return (
    <Suspense fallback={<LeaderboardSkeleton count={10} variant={isGridView ? "grid" : "list"} />}>
      <LeaderboardView
        entries={data.entries}
        period={data.period}
        startDate={new Date(data.startDate)}
        endDate={new Date(data.endDate)}
        topByActivity={data.topByActivity}
        hiddenRoles={data.hiddenRoles}
      />
    </Suspense>
  );
}

function createFallbackData(period: "week" | "month" | "year"): LeaderboardJSON {
  return {
    period,
    updatedAt: Date.now(),
    startDate: new Date().toISOString(),
    endDate: new Date().toISOString(),
    entries: [],
    topByActivity: {},
    hiddenRoles: [],
  };
}

Then the page function simplifies to:

   if (!fs.existsSync(filePath)) {
-    const fallbackData: LeaderboardJSON = {
-      period,
-      updatedAt: Date.now(),
-      startDate: new Date().toISOString(),
-      endDate: new Date().toISOString(),
-      entries: [],
-      topByActivity: {},
-      hiddenRoles: []
-    };
-
-    return (
-      <Suspense fallback={<LeaderboardSkeleton count={10} variant={isGridView ? "grid" : "list"} />}>
-        <LeaderboardView
-          entries={fallbackData.entries}
-          period={period}
-          startDate={new Date(fallbackData.startDate)}
-          endDate={new Date(fallbackData.endDate)}
-          topByActivity={fallbackData.topByActivity}
-          hiddenRoles={fallbackData.hiddenRoles}
-        />
-      </Suspense>
-    );
+    return renderLeaderboard(createFallbackData(period), isGridView);
   }
 
   let data: LeaderboardJSON;
   try {
     const file = fs.readFileSync(filePath, "utf-8");
     data = JSON.parse(file);
   } catch (error) {
     console.error(`Failed to parse leaderboard data for ${period}:`, error);
-    
-    // fallback for corrupted JSON files
-    const fallbackData: LeaderboardJSON = {
-      period,
-      updatedAt: Date.now(),
-      startDate: new Date().toISOString(),
-      endDate: new Date().toISOString(),
-      entries: [],
-      topByActivity: {},
-      hiddenRoles: []
-    };
-
-    return (
-      <Suspense fallback={<LeaderboardSkeleton count={10} variant={isGridView ? "grid" : "list"} />}>
-        <LeaderboardView
-          entries={fallbackData.entries}
-          period={period}
-          startDate={new Date(fallbackData.startDate)}
-          endDate={new Date(fallbackData.endDate)}
-          topByActivity={fallbackData.topByActivity}
-          hiddenRoles={fallbackData.hiddenRoles}
-        />
-      </Suspense>
-    );
+    return renderLeaderboard(createFallbackData(period), isGridView);
   }
 
-  return (
-    <Suspense fallback={<LeaderboardSkeleton count={10} variant={isGridView ? "grid" : "list"} />}>
-      <LeaderboardView
-        entries={data.entries}
-        period={period}
-        startDate={new Date(data.startDate)}
-        endDate={new Date(data.endDate)}
-        topByActivity={data.topByActivity}
-        hiddenRoles={data.hiddenRoles}
-      />
-    </Suspense>
-  );
+  return renderLeaderboard(data, isGridView);

94-95: Use a structured logger instead of console.error.

As per coding guidelines, use a structured logger with debug, info, warn, and error levels rather than console.error.


90-93: Parsed JSON is not validated against the expected shape.

JSON.parse succeeds on any valid JSON. If the file contains valid JSON but with an unexpected structure (e.g., missing entries or topByActivity), the cast to LeaderboardJSON won't catch it and the component will receive malformed props, potentially causing a runtime error. Consider adding a lightweight schema check (e.g., verify Array.isArray(data.entries)) and falling back if it fails.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/leaderboard/`[period]/page.tsx:
- Line 93: Change the type guard signature for isValidLeaderboardData from
accepting any to accepting unknown, and update the function body to properly
narrow and validate the unknown value (using typeof checks, Array.isArray, and
property existence checks against the expected LeaderboardJSON shape) so the
type guard compiles and still returns data is LeaderboardJSON; reference the
isValidLeaderboardData function and the LeaderboardJSON type when making the
change.
🧹 Nitpick comments (2)
app/leaderboard/[period]/page.tsx (2)

151-166: Use a structured logger instead of console.error.

The coding guidelines call for a structured logger with debug/info/warn/error levels rather than raw console.error. If the project has a shared logger utility, use it here; otherwise, consider introducing one for server-side code to enable consistent log formatting and level control.

As per coding guidelines, "Use structured logger from context with debug, info, warn, and error levels".


52-62: Minor: createFallbackData produces potentially misleading dates.

startDate and endDate are both set to new Date().toISOString() (i.e., the same timestamp). If any downstream code computes a date range or displays it, this could look odd. Consider using more meaningful defaults (e.g., empty strings or the actual period boundaries) if the dates are surfaced in the UI.

Comment thread app/leaderboard/[period]/page.tsx Outdated
@notlevi911
Copy link
Copy Markdown
Author

Actionable comments posted: 1

🤖 Fix all issues with AI agents

In `@app/leaderboard/`[period]/page.tsx:
- Line 93: Change the type guard signature for isValidLeaderboardData from
accepting any to accepting unknown, and update the function body to properly
narrow and validate the unknown value (using typeof checks, Array.isArray, and
property existence checks against the expected LeaderboardJSON shape) so the
type guard compiles and still returns data is LeaderboardJSON; reference the
isValidLeaderboardData function and the LeaderboardJSON type when making the
change.

🧹 Nitpick comments (2)

All issues have been addressed:

Line 93 - Type Guard Fixed: Changed isValidLeaderboardData(data: any) to isValidLeaderboardData(data: unknown) with proper type narrowing and comprehensive validation of the LeaderboardJSON structure.

Structured Logger Implemented: Created lib/logger.ts with debug/info/warn/error levels and replaced console.error with logger.error() for structured logging with context.

Meaningful Fallback Dates: Fixed createFallbackData() to generate period-appropriate date ranges instead of identical timestamps (week: last 7 days, month: last month, year: last year).

All changes have been tested and TypeScript compilation passes successfully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Handle missing leaderboard JSON gracefully instead of crashing

1 participant