Summary
The Contributor Leaderboard tells a first-time visitor they have already filed 2 reports, had 1 verified, answered 55 quizzes and earned 125 points. None of that happened. The numbers are a hardcoded seed, and the panel ranks the visitor against five fictional people using them.
1. A new user's stats are fabricated
const [userStats, setUserStats] = useState(() => {
try {
const saved = localStorage.getItem(USER_STORAGE_KEY);
return saved
? JSON.parse(saved)
: { name: "You (Guest)", points: 125, reports: 2, verified: 1, quizzes: 55, avatar: "🌟" };
Open the app for the first time and the card reads "1 Verified Reports • 2 Submissions • 55 Quizzes Answered" above a rank computed from those figures. It is presented as a record of the user's own activity, in the first person ("Your Rank", "(You)"), with no indication anything is a placeholder.
The repo has consistently treated invented values shown as measurements as bugs rather than acceptable placeholders — #499 (fabricated AQI 85), #544 (invented 25 µg/m³), #546 (missing readings reported as Good). This is the same thing pointed at the user's own contribution history, and it is arguably worse, because a user cannot sanity-check it against anything.
2. It ignores the activity the app actually records
The app already tracks real contribution signals, and the leaderboard reads none of them:
| signal |
where it lives |
read by leaderboard |
| community reports |
pollution-hub-reports (CommunityHub) |
no |
| challenge points |
pollution_hub_total_points (ChallengesWidget) |
no |
| quiz completions |
QUIZ_COMPLETED on the event bus |
no |
| earned badges |
pollution-hub-achievements |
no |
Leaderboard invented a fourth storage key, pollution-hub-user-points, that nothing else in the codebase writes to. Submit a real report in the Community Hub and your leaderboard total does not move. Earn 10 challenge points and it does not move. The one thing that does move it is item 3.
3. A point simulator ships to production
{/* Developer Action Simulator for Testing */}
<button onClick={() => addPoints(10, "report")}>+10 New Report</button>
<button onClick={() => addPoints(50, "verified")}>+50 Verified Report</button>
<button onClick={() => addPoints(1, "quiz")}>+1 Quiz Answer</button>
These render unconditionally — no dev-mode guard, no import.meta.env.DEV check. They are visible to every visitor at the bottom of the panel, and clicking "+50 Verified Report" persists a verified report that was never submitted. Between the fabricated seed and these buttons, no number on this panel corresponds to anything.
4. Smaller defects in the same component
key={user.name} — names are not unique keys. A stored userStats.name matching a mock entry ("Priya Singh") produces duplicate React keys and one row silently wins.
currentUserRank is findIndex(...) + 1, and leaderboard is [] until the effect runs, so the first paint renders "Your Rank #0".
addPoints calls localStorage.setItem inside the setState updater — unguarded, so a quota error throws from inside a React state update, and the updater is not pure (it runs twice under StrictMode, writing twice).
- Mock contributors are labelled with no indication they are sample data, so the panel reads as a real community ranking.
Expected
A first-time visitor sees zeros, with the mock contributors clearly marked as sample data. Points derive from activity the app actually recorded. The simulator is not present in a production build.
Suggested fix
Extract the scoring into src/utils/leaderboardStats.js: read the existing report/quiz/challenge keys, apply the POINT_SYSTEM weights already declared at the top of the file, and return a stats object — so the score becomes a pure function of recorded activity and can be tested without rendering. Start a new user at zero, gate the simulator behind import.meta.env.DEV, key rows on a stable id, and don't render a rank before one exists.
Summary
The Contributor Leaderboard tells a first-time visitor they have already filed 2 reports, had 1 verified, answered 55 quizzes and earned 125 points. None of that happened. The numbers are a hardcoded seed, and the panel ranks the visitor against five fictional people using them.
1. A new user's stats are fabricated
Open the app for the first time and the card reads "1 Verified Reports • 2 Submissions • 55 Quizzes Answered" above a rank computed from those figures. It is presented as a record of the user's own activity, in the first person ("Your Rank", "(You)"), with no indication anything is a placeholder.
The repo has consistently treated invented values shown as measurements as bugs rather than acceptable placeholders — #499 (fabricated AQI 85), #544 (invented 25 µg/m³), #546 (missing readings reported as Good). This is the same thing pointed at the user's own contribution history, and it is arguably worse, because a user cannot sanity-check it against anything.
2. It ignores the activity the app actually records
The app already tracks real contribution signals, and the leaderboard reads none of them:
pollution-hub-reports(CommunityHub)pollution_hub_total_points(ChallengesWidget)QUIZ_COMPLETEDon the event buspollution-hub-achievementsLeaderboardinvented a fourth storage key,pollution-hub-user-points, that nothing else in the codebase writes to. Submit a real report in the Community Hub and your leaderboard total does not move. Earn 10 challenge points and it does not move. The one thing that does move it is item 3.3. A point simulator ships to production
These render unconditionally — no dev-mode guard, no
import.meta.env.DEVcheck. They are visible to every visitor at the bottom of the panel, and clicking "+50 Verified Report" persists a verified report that was never submitted. Between the fabricated seed and these buttons, no number on this panel corresponds to anything.4. Smaller defects in the same component
key={user.name}— names are not unique keys. A storeduserStats.namematching a mock entry ("Priya Singh") produces duplicate React keys and one row silently wins.currentUserRankisfindIndex(...) + 1, andleaderboardis[]until the effect runs, so the first paint renders "Your Rank #0".addPointscallslocalStorage.setIteminside thesetStateupdater — unguarded, so a quota error throws from inside a React state update, and the updater is not pure (it runs twice under StrictMode, writing twice).Expected
A first-time visitor sees zeros, with the mock contributors clearly marked as sample data. Points derive from activity the app actually recorded. The simulator is not present in a production build.
Suggested fix
Extract the scoring into
src/utils/leaderboardStats.js: read the existing report/quiz/challenge keys, apply thePOINT_SYSTEMweights already declared at the top of the file, and return a stats object — so the score becomes a pure function of recorded activity and can be tested without rendering. Start a new user at zero, gate the simulator behindimport.meta.env.DEV, key rows on a stable id, and don't render a rank before one exists.