Skip to content

feat: Lifetime leaderboard tab — unique postboxes claimed#85

Merged
code418 merged 4 commits intomasterfrom
feat/lifetime-leaderboard
Apr 12, 2026
Merged

feat: Lifetime leaderboard tab — unique postboxes claimed#85
code418 merged 4 commits intomasterfrom
feat/lifetime-leaderboard

Conversation

@code418
Copy link
Copy Markdown
Owner

@code418 code418 commented Apr 12, 2026

Summary

  • Backend: updateLifetimeLeaderboard() in _leaderboardUtils.ts maintains leaderboards/lifetime document (sorted by uniquePostboxesClaimed desc, top 100)
  • startScoring: after each claim session, queries prior claims to detect new unique boxes, increments uniquePostboxesClaimed + lifetimePoints on users/{uid} via FieldValue.increment, then pushes to lifetime leaderboard
  • updateDisplayName: propagates display name changes to leaderboards/lifetime as it already does for the periodic tabs
  • Flutter: 4th "Lifetime" tab in LeaderboardScreen; trailing shows "N box(es) · M pts"; Postman James slides up with an explanation when the tab is selected

Data model

users/{uid} gains two server-write-only fields:

  • uniquePostboxesClaimed: number — deduplicated; same box on a different day doesn't increment
  • lifetimePoints: number — cumulative points from all claims since feature launch

leaderboards/lifetime document mirrors the same structure as other periods but with periodKey: "lifetime" (no rollover) and entries[].uniquePostboxesClaimed / entries[].totalPoints instead of entries[].points.

Test plan

  • cd functions && npx tsc --noEmit — clean
  • flutter test — 58/58 pass
  • Deploy functions; claim a postbox → leaderboards/lifetime document created with correct entry
  • Claim same postbox next day → uniquePostboxesClaimed stays, lifetimePoints increases
  • Claim new postbox → uniquePostboxesClaimed increments
  • Change display name → entry in leaderboards/lifetime updates
  • UI: Lifetime tab visible in Scores; trailing reads "1 box · N pts" (singular) / "3 boxes · M pts" (plural)
  • Switching to Lifetime tab triggers James strip message

🤖 Generated with Claude Code

code418 and others added 4 commits April 12, 2026 22:25
Tapping the James strip immediately slides it out rather than waiting
up to 8 s for the auto-dismiss timer. Prevents the strip from blocking
the Claim button on the Claim screen while James is speaking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace `as Map<String, dynamic>?` casts on Firestore array entries
with `is Map` checks so malformed backend data doesn't throw a
CastError. Entries are always maps in practice; this is purely
defensive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the raw UID title/subtitle with the user's display name.
While the name is loading a spinner shows in the avatar and the
title reads "Loading...". Once resolved the display name is shown
with two-letter initials; if the profile has no displayName the
title falls back to "Unknown player".

The raw Firebase UID is no longer shown to the user anywhere in
the friends list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: updateLifetimeLeaderboard() in _leaderboardUtils.ts tracks
  uniquePostboxesClaimed + totalPoints in leaderboards/lifetime
- startScoring: uniqueness check via prior-claim query; FieldValue.increment
  on users/{uid}.uniquePostboxesClaimed + lifetimePoints
- updateDisplayName: propagates name change to leaderboards/lifetime
- Flutter: Lifetime tab in LeaderboardScreen (4th tab); displays
  "N box(es) · M pts" format; James strip explains the list on tab switch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@code418 code418 merged commit be6e240 into master Apr 12, 2026
2 checks passed
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.

1 participant