Skip to content

Round-aware multi-round cap leaderboard display - #475

Open
zacjones93 wants to merge 4 commits into
mainfrom
codex/multi-round-cap-leaderboard-seed
Open

Round-aware multi-round cap leaderboard display#475
zacjones93 wants to merge 4 commits into
mainfrom
codex/multi-round-cap-leaderboard-seed

Conversation

@zacjones93

@zacjones93 zacjones93 commented May 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add round-aware multi-round cap breakdowns to public and preview leaderboards
  • Preserve aggregate parent scores as summed round times while showing capped rounds as cap time plus reps-at-cap
  • Add MWFC demo seed data for validating explicit per-round cap status and reps-at-cap
  • Surface backfill fallback diagnostics for missing score type metadata

Verification

  • pnpm --filter wodsmith-start type-check
  • pnpm --filter wodsmith-start test test/components/leaderboard-page-content.test.tsx
  • lat check
  • git diff --check
  • pre-push: pnpm lint and pnpm type-check
  • Manual public + organizer preview checks against PlanetScale demo branch

Summary by cubic

Adds round-aware multi-round cap breakdowns to leaderboards and adds a new tiebreak: when cap count and aggregate time are equal, rank by total reps-at-cap. Aggregate remains the sum of round times; capped rounds show CAP with cap time and reps-at-cap.

  • New Features
    • Show per-round breakdowns under the aggregate in both competition and online leaderboards; capped rounds render “CAP (time, reps)”.
    • In the cap bucket: fewer capped rounds first, then total time, then total reps-at-cap when time matches.
    • Server returns ordered per-round rows (number, value, status, reps-at-cap) and recomputes sort keys using cap count and reps-at-cap (from timeCap or summed round reps).
    • Backfill now reports scoreType fallback diagnostics with counts and per-score warnings.
    • Added MWFC seed data validating explicit per-round caps/reps, plus ADR and tests.

Written for commit 0374968. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features
    • Competition leaderboards now display per-round score breakdowns for multi-round events, showing individual round results with capped rounds clearly marked and optional secondary metrics (such as reps) displayed beneath aggregate scores. Available on both desktop and mobile views.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR extends multi-round time-with-cap scoring by adding a per-round breakdown display to competition leaderboards. The server API exposes fetched per-round cap status and values; client UI components render these breakdowns under aggregate scores; backfill diagnostics track scoreType fallback risks; seed data validates the flow; and ADR-0014 documents the design.

Changes

Round-aware competition leaderboard

Layer / File(s) Summary
Server API contract and round-cap fetching
src/server/competition-leaderboard.ts
CompetitionLeaderboardEntry.eventResults[].rounds added to expose ordered per-round breakdown. fetchRoundCapSummaries queries and accumulates persisted per-round state (roundNumber, value, status, schemeOverride). formatRoundBreakdowns converts persisted rounds into response format with decoded display values, cap/scored status, and secondaryValue.
Server leaderboard entry construction with rounds
src/server/competition-leaderboard.ts
Leaderboard entry builder integrates fetched rounds into eventResults via formatRoundBreakdowns(...). Scored results get populated rounds array; placeholders and missing results get empty arrays for consistency. Minor formatting updates to statusBreakdown and buildReviewSummary logic.
Round breakdown UI component definition
src/components/competition-leaderboard-table.tsx, src/components/online-competition-leaderboard-table.tsx
Two RoundBreakdown components conditionally render per-round list when result has non-null rawScore and multiple rounds. Highlights capped rounds with "CAP" label and includes optional secondary reps value.
Desktop leaderboard UI round breakdown integration
src/components/competition-leaderboard-table.tsx
RoundBreakdown integrated into EventResultCell primary score display, mobile per-event section, and single-event view score cell. CappedRounds indicator text updated; event sorting reformatted without behavior change.
Online leaderboard UI round breakdown integration
src/components/online-competition-leaderboard-table.tsx
RoundBreakdown integrated into mobile event score cards and desktop selected-event/per-event score cells. ReviewStatusIndicator summary type refined; minor formatting updates to useTeamVideos and TabsContent.
Backfill diagnostics with scoreType fallback tracking
src/server-fns/competition-score-fns.ts
backfillMultiRoundCapScoresFn expanded to track and report scoreType fallback cases separately (score-row fallback vs default fallback) with structured warnings. Early-return cases now return expanded result object with fallback counters and warnings array.
Seed data creation for round-breakdown validation
scripts/seed/index.ts, scripts/seed/seeders/22-mwfc-round-breakdown.ts
New seeder script creates MWFC competition fixture with users, teams, scoring, and video submissions. Includes helpers for deterministic ID generation and per-round cap/reps computation. Seeder registered in seed orchestration for standalone or integrated execution.
Component test coverage and mocks for round breakdowns
test/components/leaderboard-page-content.test.tsx
createMockEntry now defaults to rounds: [] on mocked event results. New test case asserts multi-round breakdown rendering under aggregate scores. P-score and penalty-related mocked eventResults updated with rounds: [] for consistent shape.
Architecture and design documentation
docs/adr/0014-round-aware-multi-round-cap-scoring-followup.md, lat.md/domain.md
ADR-0014 documents round-entry contract, server persistence, public leaderboard requirements, verification semantics, and backfill diagnostics. Domain documentation updated to describe round breakdown display, sorting semantics, write-path changes, and ADR-0014 constraints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wodsmith/thewodapp#396: Overlaps in multi-round leaderboard-cap presentation and capped-round indicator rendering via same leaderboard server/UI code paths.
  • wodsmith/thewodapp#335: Directly overlapping changes to CompetitionLeaderboardEntry.eventResults response payload shape.
  • wodsmith/thewodapp#398: End-to-end multi-round time-with-cap leaderboard and round-status work in same files and server entry points.

Poem

🐰 Rounds now bloom where scores once hid,
Per-round breakdowns, cap-aware and explicit—
From persisted state to leaderboard display,
Each round's truth shines in its own way!
The UI renders, the tests pass with grace,
ADR-0014 marks this architectural space. 🏆

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.31% 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 accurately reflects the main change: adding round-aware multi-round cap breakdowns to leaderboard displays. It is concise, specific, and directly aligned with the primary objective of the PR.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/multi-round-cap-leaderboard-seed

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f88c8244f2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

client,
"scores",
scoredRows.map((row) => {
const value = aggregateScoreValue(row) as number

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve seeded aggregate score from source leaderboard

Use the captured row.scoreValue when inserting scores.score_value here. Recomputing from aggregateScoreValue(row) currently flattens many seeded rows (especially cappedRoundCount === 2) because roundValues() assigns both round values to TIME_CAP_MS, which makes multiple teams land on the same 36:00 aggregate and changes rank/sort behavior versus the MWFC snapshot this seeder is meant to reproduce.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🧹 Nitpick comments (3)
lat.md/domain.md (1)

157-158: 💤 Low value

Consider using wiki link syntax for ADR cross-references.

The coding guidelines specify using wiki link syntax [[target]] or [[target|alias]] for cross-references. While the current Markdown link syntax works, consider whether ADR references should follow the wiki link pattern for consistency:

-[ADR-0014](../docs/adr/0014-round-aware-multi-round-cap-scoring-followup.md) records the next design step:
+[[../docs/adr/0014-round-aware-multi-round-cap-scoring-followup.md|ADR-0014]] records the next design step:

The same pattern appears on lines 243 and 271.

As per coding guidelines: "Use wiki links ([[target]] or [[target|alias]]) to cross-reference sections and source code in lat.md/ files"

🤖 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 `@lat.md/domain.md` around lines 157 - 158, Replace the plain Markdown link to
ADR-0014 with the repo's wiki-link style so ADR cross-references follow the
coding guideline; specifically locate the existing Markdown link text
referencing ADR-0014 (the
“[ADR-0014](../docs/adr/0014-round-aware-multi-round-cap-scoring-followup.md)”
instance) and change it to a wiki link form like
[[docs/adr/0014-round-aware-multi-round-cap-scoring-followup|ADR-0014]] (and do
the same for the other occurrences of the Markdown link pattern noted in the
file), ensuring references to ADR-0014 and related symbols such as
cappedRoundCount use the wiki-link syntax consistently.
apps/wodsmith-start/scripts/seed/index.ts (1)

39-39: ⚡ Quick win

Add // @lat: linkage for the new seeder wiring.

Please add a single code-reference comment near this registration to tie it to the relevant lat.md section for the round-breakdown seed flow.

As per coding guidelines, **/*.{ts,tsx,js,jsx,py,rs,go,c,h}: Add code reference comments (// @lat: [[section-id]] ...) to tie source code to design concepts and test specifications in lat.md.

Also applies to: 64-64

🤖 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 `@apps/wodsmith-start/scripts/seed/index.ts` at line 39, Add a single
code-reference comment linking this seeder to its lat.md section: next to the
import and the place where seedMwfcRoundBreakdown is registered/used (symbol:
seedMwfcRoundBreakdown) add a line comment in the form "// `@lat`: [[section-id]]"
(replace [[section-id]] with the actual round-breakdown section id from lat.md)
so both the import and the registration/usage are annotated.
apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts (1)

317-317: ⚡ Quick win

Add // @lat: references on the seeder entry flow.

Please add code-reference comments that map this exported seeder flow to the relevant lat.md section(s).

As per coding guidelines, **/*.{ts,tsx,js,jsx,py,rs,go,c,h}: Add code reference comments (// @lat: [[section-id]] ...) to tie source code to design concepts and test specifications in lat.md.

🤖 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 `@apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts` at line
317, Add code-reference comments linking this seeder's entry flow to the
appropriate lat.md sections by inserting one or more lines like // `@lat`:
[[section-id]] immediately above the exported seeder flow (the exported function
seed) so the seeder entry is explicitly tied to the design/test spec; ensure the
section IDs you reference map to the relevant lat.md topics for the MWFC round
breakdown and add multiple // `@lat`: entries if the flow spans several sections.
🤖 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 `@apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts`:
- Around line 240-267: The deleteSeedRows cleanup is incomplete: ensure you also
delete the seeded team_memberships, teams, and users (in that dependency order)
to avoid duplicate-ID conflicts on reruns. Update deleteSeedRows to execute
DELETE statements for team_memberships (WHERE team_id IN (...) OR user_id IN
(...)), then DELETE FROM teams WHERE id IN (...), and finally DELETE FROM users
WHERE id IN (...), passing the corresponding id arrays (e.g. derived from rows,
TEAM_IDS, USER_IDS or whatever variables hold inserted ids) as parameters and
keeping the existing pattern of client.execute; ensure you delete dependents
before parents (team_memberships → teams → users).
- Around line 323-586: Wrap the whole multi-table seeding sequence (starting at
deleteSeedRows(client) and including every subsequent batchInsert calls and the
scoredRows/video_submissions logic) in a single database transaction by using
the DB client's transaction API (e.g., client.transaction(async (tx) => { ... })
or db.transaction), replacing usages of client with the transaction handle (tx)
inside the block so deleteSeedRows(tx) and all batchInsert(tx, ...) run in the
same transaction, and ensure you commit on success and rollback on error
(propagate the error after rollback).
- Around line 16-34: Replace the declared type alias "SeedRow" with an interface
by changing "type SeedRow = { ... }" to "interface SeedRow { ... }" so the shape
follows the TS object-shape guideline (keep all property names and unions
identical); then add the required traceability comments "// `@lat`:
[[section-id]]" in this seeder file — place one directly above the new interface
declaration and any other top-level section comments (e.g., above the
dataset/rows or export) so the seeder contains visible // `@lat` entries for
traceability while preserving existing identifiers like SeedRow, registrationId,
userId, and the seeder export.

In `@apps/wodsmith-start/src/components/competition-leaderboard-table.tsx`:
- Around line 184-211: Add the missing design reference comment for the new
component by inserting a single-line `// `@lat`: [[section-id]]` comment
immediately above the RoundBreakdown function declaration (the function named
RoundBreakdown that takes a result prop of type
CompetitionLeaderboardEntry["eventResults"][number]). Ensure the section-id
matches the appropriate design/concept identifier per guidelines and place the
comment in the same file so the component is tied to its design spec.

In `@apps/wodsmith-start/src/components/online-competition-leaderboard-table.tsx`:
- Around line 221-248: RoundBreakdown is duplicated; extract it into a single
shared component (e.g., export function RoundBreakdown) that accepts result:
CompetitionLeaderboardEntry["eventResults"][number], add the required "`@lat`:
[[domain#Leaderboard#Round Breakdown Display]]" comment at the top of the new
file, and replace the local definitions in both
competition-leaderboard-table.tsx files with an import of the shared
RoundBreakdown; ensure the new component returns null when result.rawScore ===
null or result.rounds.length <= 1 and preserves the existing rendering logic for
round.status === "cap" and round.formatted/secondaryValue.

In `@apps/wodsmith-start/test/components/leaderboard-page-content.test.tsx`:
- Around line 229-296: Add a single `@lat`: specification reference comment
immediately adjacent to the test named "displays multi-round score breakdowns
under aggregate scores" (the it(...) block) — place the comment next to that
test (e.g., directly above the it(...) line), ensure there is exactly one `@lat`:
comment for this test and do not add duplicate `@lat`: comments elsewhere in the
file.

---

Nitpick comments:
In `@apps/wodsmith-start/scripts/seed/index.ts`:
- Line 39: Add a single code-reference comment linking this seeder to its lat.md
section: next to the import and the place where seedMwfcRoundBreakdown is
registered/used (symbol: seedMwfcRoundBreakdown) add a line comment in the form
"// `@lat`: [[section-id]]" (replace [[section-id]] with the actual
round-breakdown section id from lat.md) so both the import and the
registration/usage are annotated.

In `@apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts`:
- Line 317: Add code-reference comments linking this seeder's entry flow to the
appropriate lat.md sections by inserting one or more lines like // `@lat`:
[[section-id]] immediately above the exported seeder flow (the exported function
seed) so the seeder entry is explicitly tied to the design/test spec; ensure the
section IDs you reference map to the relevant lat.md topics for the MWFC round
breakdown and add multiple // `@lat`: entries if the flow spans several sections.

In `@lat.md/domain.md`:
- Around line 157-158: Replace the plain Markdown link to ADR-0014 with the
repo's wiki-link style so ADR cross-references follow the coding guideline;
specifically locate the existing Markdown link text referencing ADR-0014 (the
“[ADR-0014](../docs/adr/0014-round-aware-multi-round-cap-scoring-followup.md)”
instance) and change it to a wiki link form like
[[docs/adr/0014-round-aware-multi-round-cap-scoring-followup|ADR-0014]] (and do
the same for the other occurrences of the Markdown link pattern noted in the
file), ensuring references to ADR-0014 and related symbols such as
cappedRoundCount use the wiki-link syntax consistently.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 409f5e74-a360-4866-9584-18a324f94d99

📥 Commits

Reviewing files that changed from the base of the PR and between d96f579 and f88c824.

⛔ Files ignored due to path filters (1)
  • lat.md/.cache/vectors.db is excluded by !**/.cache/**, !**/*.db
📒 Files selected for processing (9)
  • apps/wodsmith-start/scripts/seed/index.ts
  • apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts
  • apps/wodsmith-start/src/components/competition-leaderboard-table.tsx
  • apps/wodsmith-start/src/components/online-competition-leaderboard-table.tsx
  • apps/wodsmith-start/src/server-fns/competition-score-fns.ts
  • apps/wodsmith-start/src/server/competition-leaderboard.ts
  • apps/wodsmith-start/test/components/leaderboard-page-content.test.tsx
  • docs/adr/0014-round-aware-multi-round-cap-scoring-followup.md
  • lat.md/domain.md

Comment thread apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts Outdated
Comment thread apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts
Comment thread apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts Outdated
Comment thread apps/wodsmith-start/src/components/competition-leaderboard-table.tsx Outdated
Comment thread apps/wodsmith-start/src/components/online-competition-leaderboard-table.tsx Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

2 issues found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts">

<violation number="1" location="apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts:493">
P1: `aggregateScoreValue(row)` returns `2 * TIME_CAP_MS` (2,160,000) for every row with `cappedRoundCount === 2` because `roundValues()` sets both round values to the flat cap. This collapses 8 distinct team scores into the same aggregate, breaking rank ordering against the source leaderboard snapshot. Use `row.scoreValue` directly to preserve the captured aggregate.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

client,
"scores",
scoredRows.map((row) => {
const value = aggregateScoreValue(row) as number

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.

P1: aggregateScoreValue(row) returns 2 * TIME_CAP_MS (2,160,000) for every row with cappedRoundCount === 2 because roundValues() sets both round values to the flat cap. This collapses 8 distinct team scores into the same aggregate, breaking rank ordering against the source leaderboard snapshot. Use row.scoreValue directly to preserve the captured aggregate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts, line 493:

<comment>`aggregateScoreValue(row)` returns `2 * TIME_CAP_MS` (2,160,000) for every row with `cappedRoundCount === 2` because `roundValues()` sets both round values to the flat cap. This collapses 8 distinct team scores into the same aggregate, breaking rank ordering against the source leaderboard snapshot. Use `row.scoreValue` directly to preserve the captured aggregate.</comment>

<file context>
@@ -0,0 +1,591 @@
+		client,
+		"scores",
+		scoredRows.map((row) => {
+			const value = aggregateScoreValue(row) as number
+			const status = row.cappedRoundCount > 0 ? "cap" : "scored"
+			return {
</file context>

Comment thread apps/wodsmith-start/scripts/seed/seeders/22-mwfc-round-breakdown.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/wodsmith-start/src/lib/scoring/sort/compare.ts">

<violation number="1" location="apps/wodsmith-start/src/lib/scoring/sort/compare.ts:74">
P2: Comparator secondary tie-breaking no longer matches `computeSortKey`, which can produce different leaderboard orderings between in-memory and persisted sorting.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


const aSecondary = a.timeCap?.secondaryValue ?? 0
const bSecondary = b.timeCap?.secondaryValue ?? 0
const aSecondary = getCapSecondaryValue(a)

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.

P2: Comparator secondary tie-breaking no longer matches computeSortKey, which can produce different leaderboard orderings between in-memory and persisted sorting.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/lib/scoring/sort/compare.ts, line 74:

<comment>Comparator secondary tie-breaking no longer matches `computeSortKey`, which can produce different leaderboard orderings between in-memory and persisted sorting.</comment>

<file context>
@@ -59,8 +71,8 @@ export function compareScores(a: Score, b: Score): number {
 
-    const aSecondary = a.timeCap?.secondaryValue ?? 0
-    const bSecondary = b.timeCap?.secondaryValue ?? 0
+    const aSecondary = getCapSecondaryValue(a)
+    const bSecondary = getCapSecondaryValue(b)
     const secondaryDiff = bSecondary - aSecondary // Higher is better
</file context>

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