Skip to content

fix: derive leaderboard stats from recorded activity instead of a seed (#671) - #676

Open
MOHITKOURAV01 wants to merge 2 commits into
Aditya8369:mainfrom
MOHITKOURAV01:fix/671-leaderboard-real-stats
Open

fix: derive leaderboard stats from recorded activity instead of a seed (#671)#676
MOHITKOURAV01 wants to merge 2 commits into
Aditya8369:mainfrom
MOHITKOURAV01:fix/671-leaderboard-real-stats

Conversation

@MOHITKOURAV01

@MOHITKOURAV01 MOHITKOURAV01 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #671

The problem

Open the app for the first time and the Contributor Leaderboard tells you:

1 Verified Reports • 2 Submissions • 55 Quizzes Answered — Your Rank #5 — 125 pts

None of it happened.

return saved
  ? JSON.parse(saved)
  : { name: "You (Guest)", points: 125, reports: 2, verified: 1, quizzes: 55, avatar: "🌟" };

It is presented in the first person — "Your Rank", "(You)", "Current User" — with nothing marking it as a placeholder, and ranked against five fictional people using those figures. This repo has consistently treated invented values shown as measurements as bugs rather than acceptable placeholders (#499, #544, #546, #496). This is the same thing pointed at the user's own contribution history, where they have nothing to check it against.

It ignored the activity the app actually records

Leaderboard invented a storage key of its own, pollution-hub-user-points, that nothing else in the codebase ever writes to:

signal where it already lives read before read now
community reports pollution-community-reports no yes
challenge points pollution_hub_total_points no yes
quiz completions QUIZ_COMPLETED on the bus no yes

File a report in the Community Hub — total does not move. Earn 10 challenge points — does not move. The only thing that moved it was the simulator below.

The fix

src/utils/contributionStats.js derives the stats from the keys the rest of the app maintains, weighted by the POINT_SYSTEM the component already declared at the top of the file. The weights now live in the scoring module and the table renders from them, so the "How to Earn Points" legend and the arithmetic cannot drift apart.

Derived, not accumulated. A stored running total can drift from the activity it is meant to summarise, and once it has there is no way to tell.

One thing genuinely was not recorded anywhere: how many quiz questions have been answered. QUIZ_COMPLETED carries score and total but nothing persisted a count — achievementsStore only keeps the set of perfect quiz ids. The module maintains that counter itself, and it counts total rather than score: a 10-question quiz scored 3/10 is still 10 answers.

It self-registers on import, matching achievementsStore, and App.jsx imports it for the side effect — a quiz can be completed without the leaderboard ever having been mounted.

The simulator

{/* Developer Action Simulator for Testing */}
<button onClick={() => addPoints(50, "verified")}>+50 Verified Report</button>

No import.meta.env.DEV guard, no dev-mode check — these rendered for every visitor, at the bottom of the panel. Clicking one persisted a verified report that was never submitted. Between these and the seeded starting figures, no number on this panel corresponded to anything.

What remains is dev-only and writes through the same recording path the app uses, rather than incrementing a display total directly.

Four smaller defects in the same component

  • key={user.name}. Names are not unique keys. A stored userStats.name matching a mock entry ("Priya Singh") produced duplicate React keys and one row silently won. Keyed on id.
  • "Your Rank #0" on first paint — findIndex(...) + 1 on the empty array the state starts as. Renders until there is a rank.
  • localStorage.setItem inside a setState updater. Unguarded against quota errors, so the throw came from inside a React state update — and the updater was impure, so StrictMode ran it twice and wrote twice.
  • The mock contributors were unlabelled, so the panel read as a real community ranking. They carry a sample chip now, and the panel says plainly that there is no shared backend behind the board yet ([Architecture]: Community Reports are localStorage-only — not actually shared between users; add backend + Hotspot Aggregation & Escalation #152).

Tests

src/utils/contributionStats.test.js (22) — each source counted and weighted, the combined total, Verified-prefixed statuses as CommunityHub actually writes them, corrupt/NaN/negative storage in every key, localStorage throwing, and the quiz listener counting questions rather than correct answers.

src/components/Leaderboard.test.jsx (13) — zeros for a new visitor, explicit assertions that the old seeded figures are gone, the ranking moving with a real total, sample labelling, a stable-key row count, and that the simulator is absent from a production build.

✓ src/utils/contributionStats.test.js (22 tests)
✓ src/components/Leaderboard.test.jsx  (13 tests)

Suite status

Re-verified against current main (575e33b), which now carries #672#675: merges cleanly, 548 passed, 0 failed across 54 files.

(The earlier note here referred to 6 Commute.test.jsx failures inherited from main. #672 has since merged and fixed them.)

Migration

pollution-hub-user-points is no longer read or written, and is not migrated — the values in it were either the fabricated seed or simulator clicks, and there is no honest way to convert either into a real record. Existing reports and challenge points are picked up immediately. The quiz counter starts at zero, since nothing was ever stored to recover.

A first-time visitor was told they had filed 2 reports, had 1 verified, answered
55 quizzes and earned 125 points. It was a hardcoded seed, presented in the first
person — "Your Rank", "(You)" — with nothing marking it as a placeholder, and
nothing a user could check it against.

The panel also invented a storage key of its own, pollution-hub-user-points, that
nothing else in the codebase writes to. Filing a real report in the Community Hub
did not move the total; nor did earning challenge points, nor answering quizzes.

src/utils/contributionStats.js derives the figures from the keys the rest of the
app already maintains — pollution-community-reports, pollution_hub_total_points —
plus a quiz-answer count it maintains itself from QUIZ_COMPLETED, since nothing
persisted one before. Derived rather than accumulated: a stored total can drift
from the activity it summarises, and there is no way to tell once it has.

The three "Simulate Action" buttons rendered unconditionally in production, so
any visitor could click +50 Verified Report and persist a verified report that
was never submitted. Between those and the seed, no number on this panel
corresponded to anything. What remains is dev-only and writes through the real
recording path.

Also:

  - rows keyed on user.name, so a stored name matching a mock entry produced
    duplicate React keys and one row silently won; keyed on id now
  - "Your Rank #0" on first paint, from findIndex(...) + 1 on an empty list
  - localStorage.setItem called inside a setState updater — unguarded against
    quota errors, and impure, so StrictMode wrote twice
  - mock contributors now carry a "sample" label, and the panel says plainly
    that there is no shared backend behind the board yet
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

@MOHITKOURAV01 is attempting to deploy a commit to the Aditya Mahajan's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the ECSoC26 Contributions considered under ECSoC'26 label Aug 10, 2026
@github-actions

Copy link
Copy Markdown

Thank You for Your Contribution! 🎉

Hi @MOHITKOURAV01,

Thank you for opening this Pull Request and contributing to our project. We truly appreciate your efforts.

Please make sure that:

  • Your code follows the project's guidelines.
  • You have linked the appropriate issue (if applicable).
  • Screenshots are added for UI/UX changes.
  • Your PR is ready for review.

The maintainer @Aditya8369 will review your PR shortly!

Happy Contributing! 🚀

@Aditya8369

Copy link
Copy Markdown
Owner

@MOHITKOURAV01 thanks for the contributions

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

Labels

ECSoC26 Contributions considered under ECSoC'26

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Leaderboard fabricates the visitor's contribution history, ignores real activity, and ships a point simulator to production

2 participants