Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions internal/ranking/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,19 @@ type LeaderboardEntry struct {
Metrics map[LeaderboardMetric]int64 `json:"metrics,omitempty"`
}

type ScoreExplanation struct {
Version int `json:"version"`
Texts map[string]string `json:"texts"`
}

type Leaderboard struct {
Period LeaderboardPeriod `json:"period"`
PeriodKey string `json:"period_key"`
Metric LeaderboardMetric `json:"metric"`
GeneratedAt time.Time `json:"generated_at"`
Stale bool `json:"stale"`
Entries []LeaderboardEntry `json:"entries"`
Period LeaderboardPeriod `json:"period"`
PeriodKey string `json:"period_key"`
Metric LeaderboardMetric `json:"metric"`
GeneratedAt time.Time `json:"generated_at"`
Stale bool `json:"stale"`
Entries []LeaderboardEntry `json:"entries"`
ScoreExplanation *ScoreExplanation `json:"score_explanation,omitempty"`
}

type LeaderboardPeriodMetadata struct {
Expand Down
7 changes: 7 additions & 0 deletions internal/ranking/httpapi/test/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ func (s *rankingProviderStub) Leaderboard(_ context.Context, period ranking.Lead
return ranking.Leaderboard{
Period: period, PeriodKey: "2026-07-24", Metric: metric,
GeneratedAt: time.Date(2026, 7, 24, 4, 0, 0, 0, time.UTC), Entries: []ranking.LeaderboardEntry{},
ScoreExplanation: &ranking.ScoreExplanation{
Version: 2,
Texts: map[string]string{"en": "Overall score V2", "zh": "综合分 V2", "zh-TW": "綜合分 V2"},
},
}, nil
}

Expand Down Expand Up @@ -177,6 +181,9 @@ func TestRankingLeaderboardValidatesAndForwardsSelection(t *testing.T) {
if response.Code != http.StatusOK || provider.boardPeriod != ranking.LeaderboardToday || provider.boardMetric != ranking.MetricOverall {
t.Fatalf("unexpected leaderboard result: status=%d body=%s provider=%+v", response.Code, response.Body.String(), provider)
}
if !strings.Contains(response.Body.String(), `"score_explanation":{"version":2`) || !strings.Contains(response.Body.String(), `"zh":"综合分 V2"`) {
t.Fatalf("leaderboard response dropped score explanation: %s", response.Body.String())
}
if response.Header().Get("Cache-Control") != "no-store" || response.Header().Get("Pragma") != "no-cache" || response.Header().Get("Expires") != "0" {
t.Fatalf("leaderboard response allowed browser caching: %+v", response.Header())
}
Expand Down
5 changes: 4 additions & 1 deletion internal/ranking/test/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func TestCenterClientReadsLeaderboardWithKeeperMarker(t *testing.T) {
t.Fatalf("missing Keeper read marker: %v", request.Header)
}
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, `{"period":"today","period_key":"2026-07-24","metric":"overall","generated_at":"2026-07-24T04:05:06Z","stale":false,"entries":[]}`)
_, _ = io.WriteString(w, `{"period":"today","period_key":"2026-07-24","metric":"overall","generated_at":"2026-07-24T04:05:06Z","stale":false,"entries":[],"score_explanation":{"version":2,"texts":{"en":"Overall score V2","zh":"综合分 V2","zh-TW":"綜合分 V2"}}}`)
}))
defer server.Close()

Expand All @@ -197,6 +197,9 @@ func TestCenterClientReadsLeaderboardWithKeeperMarker(t *testing.T) {
if board.Period != ranking.LeaderboardToday || board.Metric != ranking.MetricOverall || board.Entries == nil {
t.Fatalf("unexpected leaderboard: %+v", board)
}
if board.ScoreExplanation == nil || board.ScoreExplanation.Version != 2 || board.ScoreExplanation.Texts["zh"] != "综合分 V2" {
t.Fatalf("score explanation was not preserved: %+v", board.ScoreExplanation)
}
}

func TestCenterClientValidatesFixedMetadataContract(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions web/src/features/ranking/RankingPage.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
min-width: 0;
}

.leaderboardTitle :global(.keeper-card-title-track) {
position: relative;
}

.leaderboardTitle :global(.keeper-card-title) {
overflow: hidden;
text-overflow: ellipsis;
Expand Down Expand Up @@ -107,6 +111,10 @@
}
}

.scoreExplanationHint {
flex: 0 0 18px;
}

.profilePrivacyTooltip {
position: absolute;
z-index: 30;
Expand Down
43 changes: 43 additions & 0 deletions web/src/features/ranking/RankingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@ const formatError = (error: unknown, t: Translate): string => {

const profileErrorKey = (error: RankingProfileError) => `ranking.profile_error_${error}`;

const resolveScoreExplanation = (
board: RankingLeaderboardResponse | null,
metric: RankingMetric,
language: string,
): string => {
if (metric !== 'overall' || board?.metric !== 'overall' || board.score_explanation?.version !== 2) return '';
const texts = board.score_explanation.texts;
if (!texts) return '';
const candidates = [
texts[language as keyof typeof texts],
texts.en,
texts.zh,
texts['zh-TW'],
Comment thread
Willxup marked this conversation as resolved.
];
return candidates.find((text) => text?.trim())?.trim() ?? '';
};

export function RankingPage(props: RankingPageProps) {
const { t, i18n } = useTranslation();
const [displayName, setDisplayName] = useState('');
Expand Down Expand Up @@ -571,15 +588,41 @@ function LeaderboardCard({
t,
language,
}: LeaderboardCardProps) {
const [scoreExplanationOpen, setScoreExplanationOpen] = useState(false);
const scoreExplanationID = useId();
const rows = useMemo(() => board?.entries.slice(0, 100) ?? [], [board]);
const podium = rows.slice(0, 3);
const tableRows = rows;
const scoreExplanation = resolveScoreExplanation(board, metric, language);
return (
<article className={`card ${styles.leaderboardCard}`.trim()} aria-busy={loading}>
<header className={styles.leaderboardHeader}>
<div className={styles.leaderboardTitle} data-ranking-header-title>
<div className="keeper-card-title-track">
<h2 className="keeper-card-title">{t(`ranking.metric_${metric}`)}</h2>
{scoreExplanation ? (
<button
type="button"
className={`${styles.profilePrivacyHint} ${styles.scoreExplanationHint} ${scoreExplanationOpen ? styles.profilePrivacyHintOpen : ''}`.trim()}
aria-label={t('ranking.score_explanation_label')}
aria-describedby={scoreExplanationID}
aria-controls={scoreExplanationID}
aria-expanded={scoreExplanationOpen}
onClick={() => setScoreExplanationOpen((open) => !open)}
onBlur={() => setScoreExplanationOpen(false)}
data-ranking-score-explanation
>
?
<span
id={scoreExplanationID}
className={styles.profilePrivacyTooltip}
role="tooltip"
data-ranking-score-explanation-tooltip
>
{scoreExplanation}
</span>
</button>
) : null}
</div>
{board && (
<div className={styles.boardMeta}>
Expand Down
10 changes: 10 additions & 0 deletions web/src/features/ranking/test/RankingPage.styles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ describe('Ranking table context styles', () => {
expect(styles).toContain('.profilePrivacyHintOpen .profilePrivacyTooltip');
});

it('reuses the participation question style beside the overall title', () => {
const titleTrack = rule('.leaderboardTitle :global(.keeper-card-title-track)');
const scoreHint = rule('.scoreExplanationHint');
expect(source).toContain('styles.profilePrivacyHint');
expect(source).toContain('styles.profilePrivacyTooltip');
expect(source).toContain('data-ranking-score-explanation');
expect(titleTrack).toContain('position: relative;');
expect(scoreHint).not.toContain('position: relative;');
});

it('aligns the title with the top filters and keeps the profile entry directly below them', () => {
const header = rule('.leaderboardHeader');
expect(header).toContain('display: grid;');
Expand Down
44 changes: 44 additions & 0 deletions web/src/features/ranking/test/RankingPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ const leaderboard: RankingLeaderboardResponse = {
metric: 'overall',
generated_at: '2026-07-24T04:05:00Z',
stale: false,
score_explanation: {
version: 2,
texts: {
en: 'Overall score V2 from the ranking center.',
zh: '中心返回的综合分 V2。',
'zh-TW': '中心回傳的綜合分 V2。',
},
},
entries: [
{
rank: 4,
Expand Down Expand Up @@ -147,6 +155,42 @@ describe('RankingPage', () => {
expect(title?.textContent).not.toContain('ranking.period_today');
});

it('shows the center explanation beside only a V2 overall title', async () => {
await renderPage();

const hint = container.querySelector<HTMLButtonElement>('[data-ranking-score-explanation]');
const tooltip = hint?.querySelector<HTMLElement>('[data-ranking-score-explanation-tooltip]');
expect(hint?.textContent).toContain('?');
expect(hint?.getAttribute('title')).toBeNull();
expect(hint?.getAttribute('aria-label')).toBe('ranking.score_explanation_label');
expect(hint?.getAttribute('aria-label')).not.toBe(tooltip?.textContent);
expect(hint?.getAttribute('aria-describedby')).toBe(tooltip?.id);
expect(hint?.getAttribute('aria-expanded')).toBe('false');
expect(tooltip?.getAttribute('role')).toBe('tooltip');
expect(tooltip?.textContent).toBe('Overall score V2 from the ranking center.');
await act(async () => hint?.click());
expect(hint?.getAttribute('aria-expanded')).toBe('true');

await renderPage({
metric: 'total_tokens',
leaderboard: { ...leaderboard, metric: 'total_tokens' },
});
expect(container.querySelector('[data-ranking-score-explanation]')).toBeNull();

await renderPage({ leaderboard: { ...leaderboard, score_explanation: undefined } });
expect(container.querySelector('[data-ranking-score-explanation]')).toBeNull();

await renderPage({
leaderboard: { ...leaderboard, score_explanation: { version: 2, texts: null } },
});
expect(container.querySelector('[data-ranking-score-explanation]')).toBeNull();

await renderPage({
leaderboard: { ...leaderboard, score_explanation: { version: 1, texts: { en: 'Legacy score' } } },
});
expect(container.querySelector('[data-ranking-score-explanation]')).toBeNull();
});

it('keeps the leaderboard as the only page card and moves participation into one large modal', async () => {
await renderPage();

Expand Down
6 changes: 6 additions & 0 deletions web/src/features/ranking/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,18 @@ export interface RankingLeaderboardEntry {
metrics?: Partial<Record<RankingDetailMetric, number>>;
}

export interface RankingScoreExplanation {
version: number;
texts?: Partial<Record<'en' | 'zh' | 'zh-TW', string>> | null;
}

export interface RankingLeaderboardResponse {
period: RankingPeriod;
period_key: string;
metric: RankingMetric;
generated_at: string;
stale: boolean;
score_explanation?: RankingScoreExplanation;
entries: RankingLeaderboardEntry[];
}

Expand Down
3 changes: 3 additions & 0 deletions web/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ const resources = {
period_previous_month: 'Last Month',
metric_label: 'Ranking Metric',
metric_overall: 'Overall Ranking',
score_explanation_label: 'Overall Ranking Algorithm Explanation',
metric_total_tokens: 'Total Token',
metric_request_count: 'Requests',
metric_cache_read_rate: 'Cache Read Rate',
Expand Down Expand Up @@ -1355,6 +1356,7 @@ const resources = {
period_previous_month: '上月榜',
metric_label: '榜单类型',
metric_overall: '综合排名',
score_explanation_label: '综合排名算法说明',
metric_total_tokens: '总 Token',
metric_request_count: '请求数',
metric_cache_read_rate: '缓存读取率',
Expand Down Expand Up @@ -2064,6 +2066,7 @@ const resources = {
period_previous_month: '上月榜',
metric_label: '榜單類型',
metric_overall: '綜合排名',
score_explanation_label: '綜合排名演算法說明',
metric_total_tokens: '總 Token',
metric_request_count: '請求數',
metric_cache_read_rate: '快取讀取率',
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/test/rankingTranslations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const rankingKeys = [
'ranking.period_current_month',
'ranking.period_previous_month',
'ranking.metric_overall',
'ranking.score_explanation_label',
'ranking.metric_total_tokens',
'ranking.metric_request_count',
'ranking.metric_cache_read_rate',
Expand Down