Skip to content

Feature/improve team leaderboard#3340

Open
Lako-Lako wants to merge 6 commits intoopenfrontio:mainfrom
Lako-Lako:feature/improve_team_leaderboard
Open

Feature/improve team leaderboard#3340
Lako-Lako wants to merge 6 commits intoopenfrontio:mainfrom
Lako-Lako:feature/improve_team_leaderboard

Conversation

@Lako-Lako
Copy link

@Lako-Lako Lako-Lako commented Mar 3, 2026

If this PR fixes an issue, link it below. If not, delete these two lines.
Resolves #3330

Description:

Describe the PR.
This PR adds the sortability of the player leaderboard to the team leaderboard. This allows sorting for tiles, gold, troops and the units i.e. SAMS etc. Additionally clicking on a team in the leaderboard now focuses on the largest player of that team.
This is my first pr overall so any feedback would be appreciated.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced
Screenshot from 2026-03-03 22-06-49 Screenshot from 2026-03-03 22-07-14

Please put your Discord username so you can be contacted if a bug or regression is found:

lakoeris

@CLAassistant
Copy link

CLAassistant commented Mar 3, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Walkthrough

TeamStats now supports clickable and keyboard-accessible sortable headers, stores raw numeric totals for correct sorting, and emits a GoToPlayerEvent (via eventBus) targeting the strongest alive player when a team row is activated. Reactive @state fields track sort key and order.

Changes

Cohort / File(s) Summary
Team Leaderboard Interactivity & Sorting
src/client/graphics/layers/TeamStats.ts
Added @state fields _sortKey and _sortOrder. Extended TeamEntry with raw numeric totals (totalGoldRaw, totalMaxTroopsRaw, totalSAMsRaw, totalLaunchersRaw, totalWarShipsRaw, totalCitiesRaw) used for numeric sorting while keeping formatted strings for display. Added keyboard handler _handleKeyDown, setSort() for toggling sort key/order, and handleTeamClick() which finds the strongest alive player in the team and emits GoToPlayerEvent. Updated updateTeamStats() to populate raw fields and updated rendering to provide clickable/keyboard-accessible headers, rows, sort indicators, dynamic grid sizing, and highlighted current user team. Key review areas: sorting comparator changes (bigint-aware gold sort), keyboard accessibility handlers, and event emission target selection.

Sequence Diagram(s)

sequenceDiagram
    participant User as "User (click/keyboard)"
    participant UI as "TeamStats UI"
    participant BUS as "eventBus"
    participant Game as "Game / Players"

    User->>UI: activate team row (click / Enter / Space)
    UI->>Game: compute strongest alive player for team (use raw totals)
    Game-->>UI: returns playerId
    UI->>BUS: emit GoToPlayerEvent(playerId)
    BUS->>Game: route event -> focus / goto player
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

🎯 Headers hum, columns change their tune,
Numbers sorted true beneath the moon.
Press Enter, click — the strongest found,
An event flies out, the view goes round. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/improve team leaderboard' accurately describes the main change—adding interactive features and sorting to the team leaderboard as detailed in the changeset.
Description check ✅ Passed The description clearly relates to the changeset, explaining that the PR adds sortability (tiles, gold, troops, units) and team-click functionality to focus on the largest player, matching the implemented changes.
Linked Issues check ✅ Passed The changes fully address issue #3330 requirements: team leaderboard is now interactive with click functionality, sorting is implemented for all specified metrics (tiles, gold, troops, units), and clicking focuses on the team's strongest player.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to the team leaderboard feature; modifications to TeamStats.ts implement only the interactive sorting and team-click features specified in issue #3330.

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


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
Contributor

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

🧹 Nitpick comments (1)
src/client/graphics/layers/TeamStats.ts (1)

46-47: Remove duplicate @state() on _sortKey.

@state() is applied twice to the same field. Keep only one to avoid duplicate reactive metadata registration.

Proposed fix
-  `@state`()
   `@state`()
   private _sortKey:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/TeamStats.ts` around lines 46 - 47, The field
decorator `@state`() is applied twice to the same private field _sortKey in
TeamStats; remove the duplicate so _sortKey has only one `@state`() decorator to
avoid duplicate reactive metadata registration — locate the _sortKey declaration
in the TeamStats class and delete the redundant `@state`(), leaving a single
`@state`() above _sortKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/StructureIconsLayer.ts`:
- Around line 192-198: When unitUpdates reference units that no longer exist,
the loop in StructureIconsLayer skips them but leaves stale entries in
rendersByUnitId; update the loop handling unitUpdates so that when
this.game.unit(update.id) returns undefined you remove the corresponding entry
from rendersByUnitId and also clean up any visual resources for that unit (e.g.,
call the existing render disposal/remove method or destroy the sprite/container
stored in rendersByUnitId[update.id]). Ensure you reference the same keys
(update.id) and use the existing render cleanup code/path so orphaned renders
are removed when a unit is deleted.

In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 119-123: The current mapping in TeamStats.ts builds a translation
key via `team_colors.${rawTeam.toLowerCase()}` and calls `translateText(key)`
for every `rawTeam`; avoid doing this for generic team IDs like "Team 1"/"Team
N" to prevent repeated missing-key fallbacks. Update the logic around
`rawTeam`/`translateText` (the block that computes `key`, `translated`, and
`teamName`) to first detect generic labels (e.g., match `/^team\s*\d+$/i` or a
"Team <number>" pattern) and, when matched, set `teamName = rawTeam` (skip
creating `key` and calling `translateText`); otherwise continue to build `key`
and use `translateText` as before. This change should be localized to the
mapping in TeamStats.ts that references `rawTeam`, `key`, and `translateText`.
- Around line 169-176: totalGoldRaw is a bigint and casting to Number loses
precision; update the sorting logic in TeamStats.ts to compare bigints directly
instead of Number(a.totalGoldRaw). Create a bigint comparator (e.g.,
compareBigInt) that accepts two bigint values and returns -1/0/1 based on
this._sortOrder (use v1 < v2 / v1 > v2 checks, and invert for "desc"), and use
that comparator in the "gold" branch of the switch on this._sortKey so the sort
uses the bigint comparison without converting to Number.
- Around line 242-313: Header divs that call setSort are not
keyboard-accessible; add role="button", tabindex="0", and an `@keydown` handler
that invokes this.setSort(...) when Enter or Space is pressed (mirror the
existing `@click` behavior in the same elements that call setSort). For team rows
(the elements currently using class="contents" with an `@click` handler to select
a team), stop using display:contents for that interactive element — either
change the container to a semantic interactive element (button or anchor) or
move the clickable element inside so it can be focused; then add tabindex="0"
(if not a native control) plus role/aria attributes and an `@keydown` handler that
triggers the same team-selection handler used by the `@click`. Ensure the keyboard
handlers check for Enter/Space and call the same selection methods so both mouse
and keyboard invoke identical logic.

---

Nitpick comments:
In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 46-47: The field decorator `@state`() is applied twice to the same
private field _sortKey in TeamStats; remove the duplicate so _sortKey has only
one `@state`() decorator to avoid duplicate reactive metadata registration —
locate the _sortKey declaration in the TeamStats class and delete the redundant
`@state`(), leaving a single `@state`() above _sortKey.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28bbd93 and 7354b99.

📒 Files selected for processing (19)
  • resources/lang/en.json
  • src/client/FlagInput.ts
  • src/client/FlagInputModal.ts
  • src/client/GameModeSelector.ts
  • src/client/LangSelector.ts
  • src/client/PatternInput.ts
  • src/client/Utils.ts
  • src/client/components/Footer.ts
  • src/client/components/LobbyPlayerView.ts
  • src/client/components/MainLayout.ts
  • src/client/components/PatternButton.ts
  • src/client/components/PlayPage.ts
  • src/client/graphics/layers/GameLeftSidebar.ts
  • src/client/graphics/layers/PlayerInfoOverlay.ts
  • src/client/graphics/layers/PlayerPanel.ts
  • src/client/graphics/layers/StructureIconsLayer.ts
  • src/client/graphics/layers/TeamStats.ts
  • src/client/graphics/layers/UnitLayer.ts
  • tests/perf/StructureIconsLayerLookupPerf.ts

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Mar 3, 2026
@Lako-Lako Lako-Lako marked this pull request as draft March 4, 2026 09:07
@Lako-Lako Lako-Lako force-pushed the feature/improve_team_leaderboard branch from 7354b99 to 02a2400 Compare March 4, 2026 10:25
Copy link
Contributor

@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

♻️ Duplicate comments (2)
src/client/graphics/layers/TeamStats.ts (2)

169-172: ⚠️ Potential issue | 🟠 Major

Fix gold sorting to compare bigint directly.

totalGoldRaw is bigint; converting to Number(...) can lose precision and produce wrong order for large totals.

Suggested fix
       .sort((a, b) => {
         const compare = (v1: number, v2: number) =>
           this._sortOrder === "asc" ? v1 - v2 : v2 - v1;
+        const compareBigInt = (v1: bigint, v2: bigint) => {
+          if (v1 === v2) return 0;
+          const less = v1 < v2;
+          return this._sortOrder === "asc" ? (less ? -1 : 1) : less ? 1 : -1;
+        };

         switch (this._sortKey) {
           case "gold":
-            return compare(Number(a.totalGoldRaw), Number(b.totalGoldRaw));
+            return compareBigInt(a.totalGoldRaw, b.totalGoldRaw);
#!/bin/bash
# Verify bigint field + lossy numeric cast in sort branch
rg -nP 'totalGoldRaw:\s*bigint|Number\(a\.totalGoldRaw\)|Number\(b\.totalGoldRaw\)' src/client/graphics/layers/TeamStats.ts -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/TeamStats.ts` around lines 169 - 172, The "gold"
sort branch in TeamStats is casting totalGoldRaw (a bigint) to Number which can
lose precision; replace the Number(...) casts in the case "gold" branch so you
compare the bigint values directly (e.g., use a BigInt-aware comparison or call
compare with a.totalGoldRaw and b.totalGoldRaw rather than
Number(a.totalGoldRaw)/Number(b.totalGoldRaw)), keeping the branch inside the
method that switches on this._sortKey.

239-240: ⚠️ Potential issue | 🟠 Major

Add keyboard-accessible interaction semantics for sortable headers and team-row actions.

These handlers are click-only, and the row target uses class="contents" (not a robust focus/semantic target). Keyboard users currently cannot perform sorting/team focus actions.

Also applies to: 250-251, 261-262, 272-273, 285-286, 296-297, 307-308, 324-329, 348-353

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/TeamStats.ts` around lines 239 - 240, The sortable
header cells in TeamStats.ts are only click-driven and some team rows use
class="contents", making them inaccessible to keyboard users; update each
clickable header (where `@click` calls this.setSort("...")) to include
role="button", tabindex="0" and a keydown handler that triggers
this.setSort(...) on Enter or Space, and similarly add role="button",
tabindex="0" and a keydown handler for the team-row actions (the elements with
class="contents") to invoke the existing team-selection/focus action (the same
callback used by the click handler) on Enter/Space so keyboard users can
activate sorting and team focus.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 46-47: The property _sortKey has two consecutive `@state`()
decorators; remove the duplicate so only a single `@state`() decorates the
_sortKey field (locate the declaration of _sortKey in the TeamStats class and
delete the extra `@state`() line immediately above it).

---

Duplicate comments:
In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 169-172: The "gold" sort branch in TeamStats is casting
totalGoldRaw (a bigint) to Number which can lose precision; replace the
Number(...) casts in the case "gold" branch so you compare the bigint values
directly (e.g., use a BigInt-aware comparison or call compare with
a.totalGoldRaw and b.totalGoldRaw rather than
Number(a.totalGoldRaw)/Number(b.totalGoldRaw)), keeping the branch inside the
method that switches on this._sortKey.
- Around line 239-240: The sortable header cells in TeamStats.ts are only
click-driven and some team rows use class="contents", making them inaccessible
to keyboard users; update each clickable header (where `@click` calls
this.setSort("...")) to include role="button", tabindex="0" and a keydown
handler that triggers this.setSort(...) on Enter or Space, and similarly add
role="button", tabindex="0" and a keydown handler for the team-row actions (the
elements with class="contents") to invoke the existing team-selection/focus
action (the same callback used by the click handler) on Enter/Space so keyboard
users can activate sorting and team focus.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 626f261b-6396-4ac7-ad4d-c66b7e035862

📥 Commits

Reviewing files that changed from the base of the PR and between 7354b99 and 02a2400.

📒 Files selected for processing (1)
  • src/client/graphics/layers/TeamStats.ts

Copy link
Contributor

@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

♻️ Duplicate comments (1)
src/client/graphics/layers/TeamStats.ts (1)

354-360: ⚠️ Potential issue | 🟠 Major

Interactive row wrapper still uses display: contents, which breaks keyboard/AT behavior.

Line 354 and Line 379 keep class="contents" on the focusable interactive element (role + tabindex + key handler). This pattern is unreliable for screen readers and keyboard focus. Please move interactivity to a real focusable element (e.g., button/a) that is not display: contents, or make per-cell controls focusable.

#!/bin/bash
# Verify focusable/interactive semantics on display: contents wrappers.
rg -nP 'class="contents"[\s\S]{0,220}(role="(link|button)"|tabindex="0"|@keydown=)' src/client/graphics/layers/TeamStats.ts -C2

Also applies to: 379-385

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/TeamStats.ts` around lines 354 - 360, The
interactive row wrapper uses class="contents" (CSS display: contents) with
role="link", tabindex="0", `@click` and `@keydown` handlers (see the element
wrapping rows in TeamStats.ts that calls handleTeamClick(team) and
_handleKeyDown), which breaks keyboard/AT support; fix by replacing the
display:contents wrapper with a real focusable element (e.g., a <button> or <a>)
or move the interactive handlers onto per-cell focusable controls: create a
semantic element (button or anchor) that is not display:contents, attach `@click`
to handleTeamClick(team) and `@keydown` to _handleKeyDown there, remove
role/tabindex from the contents class, and ensure each interactive cell has its
own focusable control if row-level semantics are not appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 354-355: Prettier/style CI failed for the TeamStats component due
to formatting inconsistencies (e.g. the <div class="contents"> block and nearby
lines around the render/JSX), so run Prettier on
src/client/graphics/layers/TeamStats.ts and commit the formatting-only changes;
specifically reformat the JSX/HTML blocks (including the occurrences around the
"contents" div and the lines flagged at roughly the later JSX section) to match
the project's Prettier config and push the resulting diff.

---

Duplicate comments:
In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 354-360: The interactive row wrapper uses class="contents" (CSS
display: contents) with role="link", tabindex="0", `@click` and `@keydown` handlers
(see the element wrapping rows in TeamStats.ts that calls handleTeamClick(team)
and _handleKeyDown), which breaks keyboard/AT support; fix by replacing the
display:contents wrapper with a real focusable element (e.g., a <button> or <a>)
or move the interactive handlers onto per-cell focusable controls: create a
semantic element (button or anchor) that is not display:contents, attach `@click`
to handleTeamClick(team) and `@keydown` to _handleKeyDown there, remove
role/tabindex from the contents class, and ensure each interactive cell has its
own focusable control if row-level semantics are not appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 228f2117-8874-483c-a5e1-4cf22e200dc6

📥 Commits

Reviewing files that changed from the base of the PR and between 02a2400 and d823f68.

📒 Files selected for processing (1)
  • src/client/graphics/layers/TeamStats.ts

Copy link
Contributor

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

♻️ Duplicate comments (1)
src/client/graphics/layers/TeamStats.ts (1)

362-368: ⚠️ Potential issue | 🟠 Major

Interactive row wrapper still uses display: contents, which is unreliable for accessibility.

class="contents" on a focusable/interactive container (role + tabindex + key/click handlers) can break semantics and keyboard/screen-reader behavior. Move interaction to a real interactive element (button/a) that is not display: contents.

Proposed direction
- <div
-   class="contents"
-   role="link"
-   tabindex="0"
-   `@click`=${() => this.handleTeamClick(team)}
-   `@keydown`=${(e: KeyboardEvent) =>
-     this._handleKeyDown(e, () => this.handleTeamClick(team))}
- >
-   <div class="py-1.5 ... cursor-pointer ${team.isMyTeam ? "font-bold" : ""}">
-     ${team.teamName}
-   </div>
+ <div class="contents">
+   <button
+     type="button"
+     class="py-1.5 border-b border-slate-500 text-center hover:bg-slate-600/60 ${team.isMyTeam ? "font-bold" : ""}"
+     `@click`=${() => this.handleTeamClick(team)}
+     `@keydown`=${(e: KeyboardEvent) =>
+       this._handleKeyDown(e, () => this.handleTeamClick(team))}
+   >
+     ${team.teamName}
+   </button>

Apply the same pattern to the non-units branch as well.

#!/bin/bash
# Verify there are no focusable/interactive wrappers still using class="contents"
rg -nP 'class="contents"[\s\S]{0,200}(role=|tabindex=|@click=|@keydown=)' src/client/graphics/layers/TeamStats.ts -C2

Expected result after fix: no matches where class="contents" is also interactive.

Also applies to: 400-406

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/TeamStats.ts` around lines 362 - 368, The
interactive wrapper uses class="contents" with role/tabindex and handlers which
is inaccessible; replace the focusable/display:contents container with a real
interactive element (e.g., a <button> or <a>) and attach the click and key
handlers directly to it instead of using display:contents. Update the instances
in TeamStats (the element that calls this.handleTeamClick(team) and
this._handleKeyDown) and the similar non-units branch (the block around the
400-406 region) so the wrapper is a semantic interactive element (no
class="contents") and preserve existing handler calls and ARIA role as needed.
🧹 Nitpick comments (1)
src/client/graphics/layers/TeamStats.ts (1)

82-106: Please add targeted regression tests for new interaction paths.

This introduces non-trivial behavior (setSort, keyboard activation, and GoToPlayerEvent emission). Add tests for sort toggling, keyboard-triggered sorting, and team click choosing the strongest alive player.

Also applies to: 171-205, 210-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/TeamStats.ts` around lines 82 - 106, Add targeted
unit/integration tests covering the new interaction paths: (1) test setSort
toggles sort order when called twice for the same key and sets default "desc"
when switching keys by exercising the setSort method and asserting
_sortKey/_sortOrder and that updateTeamStats is invoked; (2) test keyboard
activation by simulating KeyboardEvent inputs to _handleKeyDown (Enter and
Space) to ensure it prevents default and invokes the provided callback; and (3)
test team-click behavior that chooses the strongest alive player and emits a
GoToPlayerEvent by simulating the team click UI path (or invoking the handler
used by the click) on a team with mixed alive/dead players and asserting the
emitted GoToPlayerEvent payload targets the strongest alive player. Include
assertions for event emission and state changes and mock/stub updateTeamStats
and event emitter to isolate behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 362-368: The interactive wrapper uses class="contents" with
role/tabindex and handlers which is inaccessible; replace the
focusable/display:contents container with a real interactive element (e.g., a
<button> or <a>) and attach the click and key handlers directly to it instead of
using display:contents. Update the instances in TeamStats (the element that
calls this.handleTeamClick(team) and this._handleKeyDown) and the similar
non-units branch (the block around the 400-406 region) so the wrapper is a
semantic interactive element (no class="contents") and preserve existing handler
calls and ARIA role as needed.

---

Nitpick comments:
In `@src/client/graphics/layers/TeamStats.ts`:
- Around line 82-106: Add targeted unit/integration tests covering the new
interaction paths: (1) test setSort toggles sort order when called twice for the
same key and sets default "desc" when switching keys by exercising the setSort
method and asserting _sortKey/_sortOrder and that updateTeamStats is invoked;
(2) test keyboard activation by simulating KeyboardEvent inputs to
_handleKeyDown (Enter and Space) to ensure it prevents default and invokes the
provided callback; and (3) test team-click behavior that chooses the strongest
alive player and emits a GoToPlayerEvent by simulating the team click UI path
(or invoking the handler used by the click) on a team with mixed alive/dead
players and asserting the emitted GoToPlayerEvent payload targets the strongest
alive player. Include assertions for event emission and state changes and
mock/stub updateTeamStats and event emitter to isolate behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dd0f486b-03a9-4730-a80e-48e0292b36d3

📥 Commits

Reviewing files that changed from the base of the PR and between d823f68 and 5aaa16b.

📒 Files selected for processing (1)
  • src/client/graphics/layers/TeamStats.ts

@Lako-Lako Lako-Lako marked this pull request as ready for review March 4, 2026 18:10
@Lako-Lako
Copy link
Author

Except for tests this should be done, I am unsure how to make tests so it'll take me a bit to add them if needed.

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

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

Improve Team leaderboard

2 participants