From b7e4d09b321dfac41f15cae97601833f584c31e0 Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Thu, 30 Apr 2026 13:50:58 +0200 Subject: [PATCH] fix: honor disableKillCount in playback UI (#87) The disableKillCount customize setting was exposed via the API but never consumed by the SolidJS frontend. Hide per-player kill counts in the units list, unit detail card, and stats leaderboard while keeping the per-side totals, matching v2.0 behavior. Also honor disableKillCount even when customize.enabled is false, since it is a privacy toggle rather than a branding option. --- ui/src/hooks/__tests__/useCustomize.test.tsx | 22 ++++++++++ ui/src/hooks/useCustomize.tsx | 9 +++- .../components/StatsTab.tsx | 5 ++- .../components/UnitsTab.tsx | 41 +++++++++++-------- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/ui/src/hooks/__tests__/useCustomize.test.tsx b/ui/src/hooks/__tests__/useCustomize.test.tsx index a40ca360e..eb2028812 100644 --- a/ui/src/hooks/__tests__/useCustomize.test.tsx +++ b/ui/src/hooks/__tests__/useCustomize.test.tsx @@ -127,6 +127,28 @@ describe("useCustomize", () => { expect(document.documentElement.style.getPropertyValue("--accent-primary")).toBe(""); }); + it("honors disableKillCount even when customize is not enabled", async () => { + mockGetCustomize.mockResolvedValue({ + enabled: false, + disableKillCount: true, + websiteURL: "https://should-not-appear.com", + }); + + const { getByTestId } = render(() => ( + + {}} /> + + )); + + await vi.waitFor(() => { + const parsed = JSON.parse(getByTestId("config").textContent || "{}") as CustomizeConfig; + expect(parsed.disableKillCount).toBe(true); + }); + // Branding fields must NOT leak through when customize is disabled. + const parsed = JSON.parse(getByTestId("config").textContent || "{}") as CustomizeConfig; + expect(parsed.websiteURL).toBeUndefined(); + }); + it("cleans up applied properties on unmount", async () => { mockGetCustomize.mockResolvedValue({ enabled: true, diff --git a/ui/src/hooks/useCustomize.tsx b/ui/src/hooks/useCustomize.tsx index d1b33d8a2..5e3a06b8c 100644 --- a/ui/src/hooks/useCustomize.tsx +++ b/ui/src/hooks/useCustomize.tsx @@ -19,7 +19,14 @@ export function CustomizeProvider(props: { try { const api = new ApiClient(); const data = await api.getCustomize(); - if (!data.enabled) return; + if (!data.enabled) { + // disableKillCount is a privacy toggle, not a branding option, so + // honor it even when customize itself is not enabled. + if (data.disableKillCount) { + setConfig({ disableKillCount: true }); + } + return; + } setConfig(data); // Apply CSS variable overrides to :root diff --git a/ui/src/pages/recording-playback/components/StatsTab.tsx b/ui/src/pages/recording-playback/components/StatsTab.tsx index 1e891d51c..cc750bece 100644 --- a/ui/src/pages/recording-playback/components/StatsTab.tsx +++ b/ui/src/pages/recording-playback/components/StatsTab.tsx @@ -3,6 +3,7 @@ import type { JSX } from "solid-js"; import type { Side } from "../../../data/types"; import { SIDE_COLORS_UI, SIDE_BG_COLORS } from "../../../config/sideColors"; import { useEngine } from "../../../hooks/useEngine"; +import { useCustomize } from "../../../hooks/useCustomize"; import { useI18n } from "../../../hooks/useLocale"; import styles from "./SidePanel.module.css"; @@ -33,7 +34,9 @@ interface LeaderboardEntry { export function StatsTab(): JSX.Element { const engine = useEngine(); + const customize = useCustomize(); const { t } = useI18n(); + const showPlayerKillCount = (): boolean => !customize().disableKillCount; // Frame-aware kill/death counts const killDeathCounts = createMemo(() => @@ -150,7 +153,7 @@ export function StatsTab(): JSX.Element { {/* Leaderboard */} - 0}> + 0}>
{t("leaderboard")}
diff --git a/ui/src/pages/recording-playback/components/UnitsTab.tsx b/ui/src/pages/recording-playback/components/UnitsTab.tsx index 583fc2f2c..19aa7d8ce 100644 --- a/ui/src/pages/recording-playback/components/UnitsTab.tsx +++ b/ui/src/pages/recording-playback/components/UnitsTab.tsx @@ -4,6 +4,7 @@ import type { Side } from "../../../data/types"; import type { Unit } from "../../../playback/entities/unit"; import { SIDE_COLORS_UI, SIDE_BG_COLORS } from "../../../config/sideColors"; import { useEngine } from "../../../hooks/useEngine"; +import { useCustomize } from "../../../hooks/useCustomize"; import { useI18n } from "../../../hooks/useLocale"; import { activeSide, setActiveSide } from "../shortcuts"; import { CrosshairIcon, ChevronRightIcon, EyeOffIcon, EyeIcon, NavigationIcon } from "../../../components/Icons"; @@ -32,7 +33,9 @@ export interface UnitsTabProps { export function UnitsTab(props: UnitsTabProps): JSX.Element { const engine = useEngine(); + const customize = useCustomize(); const { t } = useI18n(); + const showKillCount = (): boolean => !customize().disableKillCount; const [expandedGroups, setExpandedGroups] = createSignal>(new Set()); const [selectedUnit, setSelectedUnit] = createSignal(null); @@ -221,7 +224,7 @@ export function UnitsTab(props: UnitsTabProps): JSX.Element { {unit.role} - 0}> + 0}> {killDeathCounts().kills.get(unit.id)} @@ -240,6 +243,7 @@ export function UnitsTab(props: UnitsTabProps): JSX.Element { onToggleFollow={toggleFollow} onToggleBlacklist={props.onToggleBlacklist} side={activeSide()} + showKillCount={showKillCount()} /> @@ -267,6 +271,7 @@ interface UnitDetailCardProps { onToggleFollow: (unitId: number) => void; onToggleBlacklist?: (playerEntityId: number) => void; side: Side; + showKillCount: boolean; } function UnitDetailCard(props: UnitDetailCardProps): JSX.Element { @@ -277,24 +282,26 @@ function UnitDetailCard(props: UnitDetailCardProps): JSX.Element { > {/* Stats row */}
-
-
0 ? "var(--accent-danger)" : "var(--text-dimmest)" }} - > - {props.kills} + +
+
0 ? "var(--accent-danger)" : "var(--text-dimmest)" }} + > + {props.kills} +
+
KILLS
-
KILLS
-
-
-
0 ? "var(--accent-warning)" : "var(--text-dimmest)" }} - > - {props.deaths} +
+
0 ? "var(--accent-warning)" : "var(--text-dimmest)" }} + > + {props.deaths} +
+
DEATHS
-
DEATHS
-
+
{(() => { const visible = props.isBlacklisted ? 0 : props.markerCount;