Skip to content

Feature/custom colors and leaderboard#3950

Open
100Snakes wants to merge 4 commits into
openfrontio:mainfrom
100Snakes:feature/custom-colors-leaderboard
Open

Feature/custom colors and leaderboard#3950
100Snakes wants to merge 4 commits into
openfrontio:mainfrom
100Snakes:feature/custom-colors-leaderboard

Conversation

@100Snakes
Copy link
Copy Markdown

Description:

Adds two new features to improve the gameplay experience:

1. Custom Territory Colors

  • A new color button on the main menu (next to the Skin button) lets players choose their territory color
  • Cycles through 3 modes: 🎲 Random (default), 🎨 Custom (pick your own color), 🤝 Team (use team color)
  • Custom color is ignored in team games — team colors always take priority

2. Alliance Stats on Leaderboard

  • A "Show Alliances" button on the leaderboard swaps Gold/Troops columns for:
    • Betrayals — how many times a player broke an alliance
    • Alliances — how many active alliances they currently have
  • Both columns are sortable

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 confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Discord: 100_snankes_93529

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Holden seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Review Change Stack

Walkthrough

This PR adds a three-mode color customization system (random/custom/team) via UserSettings, creates a ColorInput UI component for mode cycling and custom color picking, integrates it into PlayPage, applies the selected colors to PlayerView territory and border rendering, and enhances Leaderboard to display player betrayal counts and alliance membership in a separate stats view.

Changes

Color Customization and Leaderboard Stats

Layer / File(s) Summary
UserSettings color mode and custom color storage
src/core/game/UserSettings.ts
Adds persistent colorMode (random/custom/team) with getter/setter, custom color enable/disable toggle, and methods to get/set primary and secondary colors with hex defaults.
ColorInput custom element and mode cycling
src/client/ColorInput.ts
Creates color-input web component with mode cycling button, custom color picker input in custom mode, and color-mode-changed event dispatch on mode/color changes.
PlayPage ColorInput integration
src/client/components/PlayPage.ts
Adds color-input elements to mobile left-column and desktop right-column layouts alongside username and pattern inputs.
SettingColor reusable color picker component
src/client/components/baseComponents/setting/SettingColor.ts
Provides labeled color picker component with reactive label/description/value properties and change event emission for settings UI.
PlayerView territory and border color rendering
src/core/game/GameView.ts
Applies color mode logic: custom mode uses custom primary/secondary when available, team mode forces team colors, random mode falls back to pattern/cosmetics; adds betrayals() accessor method.
Leaderboard betrayals and alliances view
src/client/graphics/layers/Leaderboard.ts
Adds stats view toggle showing betrayals and alliances columns instead of gold/maxTroops, with conditional header/row rendering, sortable columns, and view-state-aware sort key reset.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🎨 Colors cycle through the modes so bright,
Custom hues now paint the maps just right,
Betrayals count and alliances align,
Territory shines with player design! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/custom colors and leaderboard' clearly summarizes the two main features added in the changeset.
Description check ✅ Passed The description is directly related to the changeset, detailing both new features (custom territory colors and alliance stats) with clear explanations of functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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
Copy Markdown
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: 8

🧹 Nitpick comments (1)
src/core/game/UserSettings.ts (1)

256-258: 💤 Low value

Toggle behavior may be confusing with three-way state.

When colorMode() is "team", calling toggleCustomColorsEnabled() will switch to "custom" (not back to "random"). This asymmetric toggle behavior might surprise callers expecting a simple on/off switch.

Since the comment describes this as a "legacy helper", this may be acceptable for backward compatibility. Consider documenting the exact behavior or deprecating in favor of direct setColorMode() calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/game/UserSettings.ts` around lines 256 - 258, The helper
toggleCustomColorsEnabled currently flips colorMode() between "custom" and
"random" which yields surprising results when colorMode() is "team"; update
toggleCustomColorsEnabled (or mark it deprecated) so its behavior is explicit:
either (A) implement a true toggle that switches back to the previous
non-"custom" mode (store previousMode when entering "custom" and restore it when
leaving), or (B) change it to a clear on/off API (e.g.,
setCustomColorsEnabled(boolean) that sets "custom" or a chosen fallback like
"random"), and add a short doc/deprecation comment on toggleCustomColorsEnabled
explaining the chosen behavior and recommending callers use setColorMode()
directly; reference the methods toggleCustomColorsEnabled, colorMode,
setColorMode when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/ColorInput.ts`:
- Around line 51-53: Replace hardcoded user-facing strings in ColorInput.ts with
calls to translateText(): locate the branch that returns
"Custom"/"Team"/"Random" (the getter or method referencing this.mode) and change
each literal to translateText('colorMode.custom') /
translateText('colorMode.team') / translateText('colorMode.random') (or similar
keys), then add corresponding entries in resources/lang/en.json; apply the same
treatment for the other occurrences noted (lines around 68-69, 78, 83, 87, 99)
so every visible label uses translateText() rather than raw strings.
- Around line 91-102: The transparent color <input id="color-input-picker"> is
currently stretched over the whole control (class "w-full h-1" + style
"height:100%") and blocks clicks to the mode-cycle button; restrict its hit area
to only the color swatch (make it a small, absolutely positioned element over
the swatch rather than full width, or adjust CSS to remove full-width/100%
height) and ensure it uses the existing onColorChange handler. Also refactor the
modeLabel() function to call translateText() for user-facing strings and add
corresponding keys (e.g. "colorMode.custom", "colorMode.team",
"colorMode.random", "colorMode.pickTooltip", "colorMode.label") to
resources/lang/en.json so "Custom", "Team", "Random", "Pick your territory
color", and "Color Mode..." all come from translations.

In `@src/client/components/baseComponents/setting/SettingColor.ts`:
- Line 6: The default visible label on the SettingColor component is hardcoded
as "Color"; change the `@property`() label initializer in SettingColor (the label
property) to call translateText(...) instead (e.g.,
translateText('setting.color')) and add the matching key/value ("setting.color":
"Color") to resources/lang/en.json so all user-visible text is localized; use
the translateText function name and ensure the key follows existing naming
conventions.

In `@src/client/graphics/layers/Leaderboard.ts`:
- Line 329: The hardcoded UI strings in Leaderboard.ts must be replaced with
localized keys: change the ternary that renders ${this._viewState === "default"
? "Show Alliances" : "Show Stats"} to use
translateText("leaderboard.showAlliances") and
translateText("leaderboard.showStats") respectively (ensure translateText is
imported/available in the Leaderboard class), and add matching entries
"leaderboard.showAlliances" and "leaderboard.showStats" to
resources/lang/en.json so the translations are present.
- Line 238: The string "Betrayals" in Leaderboard.ts is hardcoded and must be
localized; replace the literal with translateText("leaderboard.betrayals") where
it appears (search for the "Betrayals" token in the Leaderboard component/render
method) and add a matching "leaderboard.betrayals": "Betrayals" entry to
resources/lang/en.json so the translateText lookup resolves.
- Line 245: The hardcoded "Alliances" string in Leaderboard.ts must be replaced
with translateText("leaderboard.alliances") and the corresponding key added to
resources/lang/en.json; update any JSX or template code that currently renders
"Alliances" to call translateText and ensure translateText is imported in
Leaderboard.ts (add import if missing), then add "leaderboard.alliances":
"Alliances" to resources/lang/en.json.

In `@src/core/game/GameView.ts`:
- Around line 268-304: Add unit tests for GameView's color selection branches:
cover when GameView.game.myClientID() === data.clientID vs not to exercise
userSettings.colorMode() === "custom" with team() === null (verify
_territoryColor uses userSettings.customPrimaryColor() and _borderColor uses
customSecondaryColor()) and team() !== null (verify team games use
defaultTerritoryColor and border uses maybeFocusedBorderColor), colorMode ===
"team" (verify _territoryColor forced to defaultTerritoryColor), and "random"
mode (verify fallback chain: cosmetics.color?.color ->
pattern?.colorPalette?.primaryColor -> defaultTerritoryColor, and secondaryColor
fallback for _borderColor). Instantiate GameView with controlled mocks/stubs for
game, userSettings, theme, pattern, cosmetics and assert _territoryColor,
_structureColors (via theme.structureColors), and _borderColor for each case.

In `@src/core/game/UserSettings.ts`:
- Around line 240-275: Add unit tests for the new color-mode helpers: cover
colorMode() validation by asserting unknown stored values fall back to "random"
and setColorMode()/colorMode() round-trip; verify default customPrimaryColor()
and customSecondaryColor() return "`#2196f3`" and "`#1565c0`" when nothing is
stored; test toggleCustomColorsEnabled() and customColorsEnabled() produce the
correct transitions between "custom" and "random"; and assert persistence by
simulating/localStorage (or the storage layer used by UserSettings) to ensure
values written by setColorMode, setCustomPrimaryColor, and
setCustomSecondaryColor are actually persisted and reloaded. Target tests at the
UserSettings class and the methods colorMode, setColorMode, customColorsEnabled,
toggleCustomColorsEnabled, customPrimaryColor, setCustomPrimaryColor,
customSecondaryColor, and setCustomSecondaryColor.

---

Nitpick comments:
In `@src/core/game/UserSettings.ts`:
- Around line 256-258: The helper toggleCustomColorsEnabled currently flips
colorMode() between "custom" and "random" which yields surprising results when
colorMode() is "team"; update toggleCustomColorsEnabled (or mark it deprecated)
so its behavior is explicit: either (A) implement a true toggle that switches
back to the previous non-"custom" mode (store previousMode when entering
"custom" and restore it when leaving), or (B) change it to a clear on/off API
(e.g., setCustomColorsEnabled(boolean) that sets "custom" or a chosen fallback
like "random"), and add a short doc/deprecation comment on
toggleCustomColorsEnabled explaining the chosen behavior and recommending
callers use setColorMode() directly; reference the methods
toggleCustomColorsEnabled, colorMode, setColorMode when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 36d07086-38ee-4b0a-9b58-f7875824a39e

📥 Commits

Reviewing files that changed from the base of the PR and between 2e17fb5 and 67b4fcb.

📒 Files selected for processing (6)
  • src/client/ColorInput.ts
  • src/client/components/PlayPage.ts
  • src/client/components/baseComponents/setting/SettingColor.ts
  • src/client/graphics/layers/Leaderboard.ts
  • src/core/game/GameView.ts
  • src/core/game/UserSettings.ts

Comment thread src/client/ColorInput.ts
Comment on lines +51 to +53
if (this.mode === "custom") return "Custom";
if (this.mode === "team") return "Team";
return "Random";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Localize new UI strings via translateText().

These user-facing labels/titles are hardcoded and bypass localization.

As per coding guidelines: src/client/**/*.{ts,tsx}: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.

Also applies to: 68-69, 78-78, 83-83, 87-87, 99-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/ColorInput.ts` around lines 51 - 53, Replace hardcoded user-facing
strings in ColorInput.ts with calls to translateText(): locate the branch that
returns "Custom"/"Team"/"Random" (the getter or method referencing this.mode)
and change each literal to translateText('colorMode.custom') /
translateText('colorMode.team') / translateText('colorMode.random') (or similar
keys), then add corresponding entries in resources/lang/en.json; apply the same
treatment for the other occurrences noted (lines around 68-69, 78, 83, 87, 99)
so every visible label uses translateText() rather than raw strings.

Comment thread src/client/ColorInput.ts
Comment on lines +91 to +102
<!-- Hidden native color picker — only shown in custom mode -->
${this.mode === "custom"
? html`
<input
type="color"
id="color-input-picker"
.value=${this.primaryColor}
@input=${this.onColorChange}
title="Pick your territory color"
class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer"
style="height:100%;top:0;"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's read the ColorInput.ts file to see the full context
head -n 120 src/client/ColorInput.ts | tail -n +70

Repository: openfrontio/OpenFrontIO

Length of output: 1459


🏁 Script executed:

# Check if the file uses translateText() function
rg "translateText" src/client/ColorInput.ts -A 2 -B 2

Repository: openfrontio/OpenFrontIO

Length of output: 49


🏁 Script executed:

# Look for all user-visible strings in the file
rg -A 3 -B 3 'title=|placeholder=|textContent' src/client/ColorInput.ts

Repository: openfrontio/OpenFrontIO

Length of output: 938


🏁 Script executed:

# Check the resources/lang/en.json for existing translations
head -n 50 resources/lang/en.json

Repository: openfrontio/OpenFrontIO

Length of output: 1452


🏁 Script executed:

# Look for how mode cycling button is implemented and attached
rg -A 10 "onModeChange|modeButton|mode-cycle" src/client/ColorInput.ts

Repository: openfrontio/OpenFrontIO

Length of output: 49


🏁 Script executed:

# Check the modeLabel() method to see if it returns localized text
rg -A 5 "modeLabel" src/client/ColorInput.ts

Repository: openfrontio/OpenFrontIO

Length of output: 715


🏁 Script executed:

# Check how the file is structured - parent container for button and input
rg -B 15 "color-input-picker" src/client/ColorInput.ts | head -n 30

Repository: openfrontio/OpenFrontIO

Length of output: 694


🏁 Script executed:

# Verify the exact parent container structure
ast-grep --pattern 'render() {
  $$$
  <button
    id="color-input-btn"
    $$$
  >
  $$$
  </button>
  $$$
}'

Repository: openfrontio/OpenFrontIO

Length of output: 49


Color picker overlay blocks mode-cycle button and text lacks translations.

In custom mode, the transparent color input (w-full h-1 + height:100%) covers the button underneath, preventing clicks to cycle modes. Additionally, user-visible text ("Custom", "Team", "Random", "Pick your territory color", "Color Mode...") must use translateText() with entries in resources/lang/en.json per guidelines.

Suggested fix for overlay
-                class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer"
-                style="height:100%;top:0;"
+                class="absolute inset-x-0 top-1 mx-auto w-7 h-7 opacity-0 cursor-pointer"

Also refactor modeLabel() to use translateText() and add translation keys to language file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- Hidden native color picker — only shown in custom mode -->
${this.mode === "custom"
? html`
<input
type="color"
id="color-input-picker"
.value=${this.primaryColor}
@input=${this.onColorChange}
title="Pick your territory color"
class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer"
style="height:100%;top:0;"
/>
${this.mode === "custom"
? html`
<input
type="color"
id="color-input-picker"
.value=${this.primaryColor}
`@input`=${this.onColorChange}
title="Pick your territory color"
class="absolute inset-x-0 top-1 mx-auto w-7 h-7 opacity-0 cursor-pointer"
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/ColorInput.ts` around lines 91 - 102, The transparent color <input
id="color-input-picker"> is currently stretched over the whole control (class
"w-full h-1" + style "height:100%") and blocks clicks to the mode-cycle button;
restrict its hit area to only the color swatch (make it a small, absolutely
positioned element over the swatch rather than full width, or adjust CSS to
remove full-width/100% height) and ensure it uses the existing onColorChange
handler. Also refactor the modeLabel() function to call translateText() for
user-facing strings and add corresponding keys (e.g. "colorMode.custom",
"colorMode.team", "colorMode.random", "colorMode.pickTooltip",
"colorMode.label") to resources/lang/en.json so "Custom", "Team", "Random",
"Pick your territory color", and "Color Mode..." all come from translations.


@customElement("setting-color")
export class SettingColor extends LitElement {
@property() label = "Color";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Default label should be localized.

The default visible label "Color" is hardcoded.

As per coding guidelines: src/client/**/*.{ts,tsx}: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/components/baseComponents/setting/SettingColor.ts` at line 6, The
default visible label on the SettingColor component is hardcoded as "Color";
change the `@property`() label initializer in SettingColor (the label property) to
call translateText(...) instead (e.g., translateText('setting.color')) and add
the matching key/value ("setting.color": "Color") to resources/lang/en.json so
all user-visible text is localized; use the translateText function name and
ensure the key follows existing naming conventions.

class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap truncate"
@click=${() => this.setSort("betrayals")}
>
Betrayals
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Hardcoded English text violates localization guidelines.

Replace with translateText("leaderboard.betrayals") and add the corresponding entry to resources/lang/en.json.

As per coding guidelines: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/graphics/layers/Leaderboard.ts` at line 238, The string
"Betrayals" in Leaderboard.ts is hardcoded and must be localized; replace the
literal with translateText("leaderboard.betrayals") where it appears (search for
the "Betrayals" token in the Leaderboard component/render method) and add a
matching "leaderboard.betrayals": "Betrayals" entry to resources/lang/en.json so
the translateText lookup resolves.

class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap truncate"
@click=${() => this.setSort("alliances")}
>
Alliances
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Hardcoded English text violates localization guidelines.

Replace with translateText("leaderboard.alliances") and add the corresponding entry to resources/lang/en.json.

As per coding guidelines: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/graphics/layers/Leaderboard.ts` at line 245, The hardcoded
"Alliances" string in Leaderboard.ts must be replaced with
translateText("leaderboard.alliances") and the corresponding key added to
resources/lang/en.json; update any JSX or template code that currently renders
"Alliances" to call translateText and ensure translateText is imported in
Leaderboard.ts (add import if missing), then add "leaderboard.alliances":
"Alliances" to resources/lang/en.json.

this.updateLeaderboard();
}}
>
${this._viewState === "default" ? "Show Alliances" : "Show Stats"}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Hardcoded English text violates localization guidelines.

Replace with translateText("leaderboard.showAlliances") and translateText("leaderboard.showStats"), and add the corresponding entries to resources/lang/en.json.

As per coding guidelines: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/graphics/layers/Leaderboard.ts` at line 329, The hardcoded UI
strings in Leaderboard.ts must be replaced with localized keys: change the
ternary that renders ${this._viewState === "default" ? "Show Alliances" : "Show
Stats"} to use translateText("leaderboard.showAlliances") and
translateText("leaderboard.showStats") respectively (ensure translateText is
imported/available in the Leaderboard class), and add matching entries
"leaderboard.showAlliances" and "leaderboard.showStats" to
resources/lang/en.json so the translations are present.

Comment thread src/core/game/GameView.ts
Comment on lines +268 to +304
const isMyPlayer = this.game.myClientID() === this.data.clientID;
const colorMode = isMyPlayer ? userSettings.colorMode() : "random";

if (colorMode === "custom" && this.team() === null) {
// Custom color only in solo/FFA — team games always use assigned team color
this._territoryColor = colord(userSettings.customPrimaryColor());
} else if (colorMode === "team" || this.team() !== null) {
// Force team color (default game behavior)
this._territoryColor = defaultTerritoryColor;
} else {
// "random" — normal game logic
if (this.team() === null) {
this._territoryColor = colord(
this.cosmetics.color?.color ??
pattern?.colorPalette?.primaryColor ??
defaultTerritoryColor.toHex(),
);
} else {
this._territoryColor = defaultTerritoryColor;
}
}

this._structureColors = theme.structureColors(this._territoryColor);

const maybeFocusedBorderColor =
this.game.myClientID() === this.data.clientID
const maybeFocusedBorderColor = isMyPlayer
? theme.focusedBorderColor()
: defaultBorderColor;

this._borderColor = new Colord(
pattern?.colorPalette?.secondaryColor ??
this.cosmetics.color?.color ??
maybeFocusedBorderColor.toHex(),
);
if (colorMode === "custom" && this.team() === null) {
this._borderColor = new Colord(userSettings.customSecondaryColor());
} else {
this._borderColor = new Colord(
pattern?.colorPalette?.secondaryColor ??
this.cosmetics.color?.color ??
maybeFocusedBorderColor.toHex(),
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing tests for color mode rendering logic.

The color selection logic has several branches that need test coverage:

  1. Custom mode behavior when team() === null vs team() !== null
  2. Team mode forcing default color
  3. Random mode fallback chain
  4. Border color selection for each mode

As per coding guidelines: All changes to src/core/ must include tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/game/GameView.ts` around lines 268 - 304, Add unit tests for
GameView's color selection branches: cover when GameView.game.myClientID() ===
data.clientID vs not to exercise userSettings.colorMode() === "custom" with
team() === null (verify _territoryColor uses userSettings.customPrimaryColor()
and _borderColor uses customSecondaryColor()) and team() !== null (verify team
games use defaultTerritoryColor and border uses maybeFocusedBorderColor),
colorMode === "team" (verify _territoryColor forced to defaultTerritoryColor),
and "random" mode (verify fallback chain: cosmetics.color?.color ->
pattern?.colorPalette?.primaryColor -> defaultTerritoryColor, and secondaryColor
fallback for _borderColor). Instantiate GameView with controlled mocks/stubs for
game, userSettings, theme, pattern, cosmetics and assert _territoryColor,
_structureColors (via theme.structureColors), and _borderColor for each case.

Comment on lines +240 to +275
// Color mode: "random" = game default, "custom" = user picks, "team" = always use team color
colorMode(): "random" | "custom" | "team" {
const val = this.getString("settings.colorMode", "random");
if (val === "custom" || val === "team") return val;
return "random";
}

setColorMode(mode: "random" | "custom" | "team"): void {
this.setString("settings.colorMode", mode);
}

// Keep legacy helpers so GameView still compiles
customColorsEnabled(): boolean {
return this.colorMode() === "custom";
}

toggleCustomColorsEnabled() {
this.setColorMode(this.colorMode() === "custom" ? "random" : "custom");
}

customPrimaryColor(): string {
return this.getString("settings.customPrimaryColor", "#2196f3");
}

setCustomPrimaryColor(color: string): void {
this.setString("settings.customPrimaryColor", color);
}

customSecondaryColor(): string {
return this.getString("settings.customSecondaryColor", "#1565c0");
}

setCustomSecondaryColor(color: string): void {
this.setString("settings.customSecondaryColor", color);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing tests for core game logic changes.

All changes to src/core/ must include tests. These new color mode methods need test coverage to verify:

  • Color mode validation logic (line 243-244)
  • Default color values (lines 261, 269)
  • Toggle behavior (lines 256-258)
  • Persistence via localStorage

As per coding guidelines: All changes to src/core/ must include tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/game/UserSettings.ts` around lines 240 - 275, Add unit tests for the
new color-mode helpers: cover colorMode() validation by asserting unknown stored
values fall back to "random" and setColorMode()/colorMode() round-trip; verify
default customPrimaryColor() and customSecondaryColor() return "`#2196f3`" and
"`#1565c0`" when nothing is stored; test toggleCustomColorsEnabled() and
customColorsEnabled() produce the correct transitions between "custom" and
"random"; and assert persistence by simulating/localStorage (or the storage
layer used by UserSettings) to ensure values written by setColorMode,
setCustomPrimaryColor, and setCustomSecondaryColor are actually persisted and
reloaded. Target tests at the UserSettings class and the methods colorMode,
setColorMode, customColorsEnabled, toggleCustomColorsEnabled,
customPrimaryColor, setCustomPrimaryColor, customSecondaryColor, and
setCustomSecondaryColor.

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management May 17, 2026
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.

2 participants