Skip to content

feat(benchmark): meeting action items — gating, tier-linking UX, scoring lock, copy and seed fixes - #613

Merged
zacjones93 merged 4 commits into
feat/hillerfit-planfrom
claude/benchmark-feature-refinements-7yzcfh
Aug 2, 2026
Merged

feat(benchmark): meeting action items — gating, tier-linking UX, scoring lock, copy and seed fixes#613
zacjones93 merged 4 commits into
feat/hillerfit-planfrom
claude/benchmark-feature-refinements-7yzcfh

Conversation

@zacjones93

@zacjones93 zacjones93 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Benchmark feature refinements from 7/7 meeting

Implements the action items from the Zac/Ian meeting reviewing the benchmark feature on this branch. Each item below lists what was decided, why it was addressed, the solution taken, and questions to consider while reviewing. Work was implemented by parallel subagents, then adversarially reviewed by two independent review agents; both HIGH-severity review findings were fixed in a follow-up round.

Action items addressed

1. Remove the "Tier context is live" card (organizer tiers page)

  • Meeting: "Let's remove that. It doesn't make a lot of sense."
  • Solution: Card removed entirely from scoring/tiers.tsx (icon import cleaned up). The page now flows header → link-events → how-scoring-works → settings → categories → rating bands → thresholds.

2. "Half a tier" → "half a point" + tier-count wording

  • Meeting: the how-scoring-works copy shouldn't hardcode "1 to 10", and "earns half a tier" should say "half a point".
  • Why/solution: The tier count was already interpolated from the configured maxTier (it rendered "10" because the training-guide battery has 10 tiers) — no change needed there. The wording is now "still earns half a point".
  • Review question: technically the value credited is 0.5 tier (worth ~5 points on the 100-point category scale, and the leaderboard shows "Tier 0.5"). "Half a point" is what the meeting asked for but it's the less precise phrasing — happy to reword to "half credit" if you prefer accuracy.

3. "Link events to tier thresholds" section + derive test data from the event

  • Meeting: "Instead of adding event tiers, have a list of events up here that don't have a tier… when you select it, it pops open this modal with the category and the scoring inputs"; "derive all this data from the event that it's tied to — all of this data other than category and model."
  • Solution: New card at the top of the tiers page listing events with no linked benchmark test; clicking one opens the existing Add-test dialog locked to that event with name, scheme, score type, and input unit prefilled from the event (server summary events now carry scheme/scoreType). Category, score model, and thresholds remain for the organizer. All prefilled fields stay editable, and manual names are never clobbered. The per-category "Add event tiers" buttons remain as a secondary path (they get the same prefill for free). The event-edit page's inline dialog also passes the live scheme/scoreType now.
  • Review questions: (a) should the per-category "Add event tiers" buttons be removed entirely ("instead of")? I kept them since standalone tests without events are still legitimate. (b) There is no time-cap field on the test model, so a time-with-cap event's cap isn't prefilled — benchmark_tests.timeCapMs exists but no editor flow writes it; follow-up?

4. Time schemes auto-select the "time" input unit

  • Meeting: "Seems like when you select time, it should auto select input unit is time."
  • Solution: Selecting time, time-with-cap, or emom (also time-entered on the athlete form) in the test editor auto-sets input unit "time"; leaving a time scheme reverts the unit (Add → default, Edit → the test's original) so a stale "time" unit can't corrupt threshold encoding. Choosing the hybrid score model (which forces time-with-cap) routes through the same logic.

5. Bounded linked-event select

  • Meeting: "Select should be a bounded amount… like 10 items."
  • Solution: The linked-event dropdown is capped at ~10 rows (max-h-80) and scrolls. No search combobox — the meeting settled on bounded-with-scroll being fine.

6. Benchmarks lose cohosts, volunteer shifts, and public volunteer signup

  • Meeting: "Probably just remove cohosts from benchmarks for now"; "get rid of volunteer shift, that makes no sense"; "we should probably take this [sign up to volunteer] off of benchmarks" — volunteer roster/inviting and waivers stay.
  • Solution: Three new registry capabilities (cohosts, volunteerShifts, publicVolunteerSignup) — true for in-person and online, false for benchmark, fail-closed for unknown types. Organizer sidebar hides Co-Hosts and Volunteer shifts for benchmarks (roster + registration questions stay); the co-hosts, volunteer-shifts, and public volunteer-signup routes redirect defensively; the "Sign up to volunteer" card is hidden on benchmark public pages. Truth-table, sidebar, and registration-sidebar tests extended; lat.md updated.
  • Review question: the underlying server fns (inviteCohostFn, volunteer-shift fns, submitVolunteerSignupFn) are not capability-gated — the gate is UI + route loaders. Reachable only by scripted calls or a comp switched to benchmark after acquiring cohosts/shifts. Want server-side guards as a follow-up?

7. Benchmark scoring hard-coded to online (tiebreakers kept)

  • Meeting: "This should probably just always be online… we'll hard code this, but you still want the tie breaker."
  • Solution: For benchmark boards the algorithm radio group is replaced with a static "Benchmark boards always use Online scoring" note across all three surfaces that render the form (organizer scoring, organizer settings, cohost scoring); the form forces online at init and on submit; the organizer and cohost save server fns force it server-side; and — per review finding — getCompetitionLeaderboard now forces the effective algorithm to online for benchmark boards at read time, so a freshly created benchmark (which stores no scoringConfig and previously fell back to "traditional") ranks correctly without the organizer ever pressing Save. Tiebreaker and DNF/DNS settings remain fully editable.

8. Leaderboard affiliate placement

  • Meeting: "It should probably be by the athlete."
  • Solution: The dedicated sortable "Affiliate" column (which duplicated the subtext already shown under the athlete name on public boards) is removed from both overall and event views; affiliate renders only under the athlete/team name (desktop + mobile). The affiliate URL filter is untouched. Group-header column math adjusted; a persisted sort on the removed column is dropped safely.
  • Review question: the in-person leaderboard table still has its own affiliate column — intentional (the meeting discussed the benchmark/online board), but flag if you want parity. Sort-by-affiliate is the only lost ability.

9. Stats page athlete selector label

  • Meeting: "Oh, it should probably have a label."
  • Solution: Both the Division and Athlete selects now have visible, accessibly-associated labels, matching the labeled-select idiom used on the home page.

10. "Pre-published" → "Auto-publish"

  • Meeting: "Autopublish might be the better word."
  • Solution: All user-facing strings renamed (selector, option, explanatory paragraph, server error); the resultsAutoPublish settings key and identifiers are unchanged. lat.md updated.

11. Seed data: vertical jump in inches

  • Meeting: "Oh, it's in feet… This should probably be in inches. Four feet is an insane amount of distance."
  • Why: real data bug — the athlete input keyed off the workout scheme ("feet") and collected feet, while the seeded thresholds converted inches→mm. An athlete typing "30" (meaning inches) encoded 30 ft and blew past the top tier.
  • Solution: Vertical Jump switched to scheme points + inputUnit inches (the same pattern the seed already uses for "BikeErg Avg Watts"): thresholds and athlete entries are now plain integer inches in the same number space, verified end-to-end (parse → encode → tier comparison) by review.
  • Review questions: (a) the athlete input for this test is now labeled "Points" and the leaderboard shows "38 pts" — numerically right, cosmetically wrong. The real fix is letting the submission form honor a benchmark test's inputUnit (or adding an inches distance unit); worth a follow-up ticket. (b) inputUnit is stored as "inches" not "in" because both threshold encoders apply an inches→feet→mm conversion when the unit is exactly "in" — consequence: the Edit-test dialog's unit select shows empty for this test (harmless; unchanged fields aren't submitted).

12. Seed data: L-sit hold top standard = 3:00

  • Meeting: "This should probably be three minutes… that's what it is in the CrossFit Journal."
  • Solution: Tier 10 changed 1:30 → 3:00 for both male and female. Tiers 1–9 untouched.
  • Review question: this leaves a big jump from tier 9 (1:21 M / 1:19 F) to 3:00. Rescaling tiers 2–9 would diverge from the source PDF — do you want an interpolated progression instead?

Miscellaneous action items identified but NOT acted on

  • Gender-split leaderboard as a setting — the male/female filter already exists on benchmark boards; making split-by-default a per-benchmark setting was left as tentative in the meeting ("might be a request someone wants"). Needs a product decision + settings schema.
  • Category scores as separate grouped leaderboard columns — debated; Zac preferred the combined Categories summary cell, and category grouping of event columns already shipped. No decision.
  • Submit-score button placement on the benchmark event card ("should probably be on the right hand side… or up there") — no settled placement.
  • Movement-pattern tagging of events (upper-body press, hinge, etc.) and pattern-based visualizations — future idea; requires tagging infrastructure.
  • Personalized athlete dashboard / spider-web chart over time — future idea.
  • Private benchmarks / password-protected private competitions (Competition Corner-style) — future feature.
  • Sponsor/vendor packages, applications, and payments — competitions-scope feature; needs its own design (pricing section vs sponsor section, application workflow, Stripe).
  • Video-proof "require for top scores" first-submitter annoyance (first athlete to log everything must film everything) — no decision was reached in the meeting.
  • "Host to comp" copy for teams without the organizer entitlement — seed/entitlement data issue, not a code change.
  • Testing a from-scratch benchmark with non-default tiers / imperfect data — Zac said he wants to do this manually; existing tests cover non-10 maxTier math, but a full organizer-flow E2E is a good follow-up.
  • Crew-app items (copy-from-previous heats, auto-preview volunteer import, single add-people button, Excel import) — Ian's, in the crew app, out of scope here.
  • Comment cleanup: two non-user-facing comments still say "pre-publish" (src/types/competitions.ts:65, src/server/competition-leaderboard.ts:249).

Notes

  • Team memory (/recall) was unreachable from this environment (worker fetch failed through the proxy), and the GitNexus MCP tools were not connected, so the mandated gitnexus_impact/detect_changes runs could not be performed; blast radius was instead bounded by capability-gate tests, the full suite, and two independent review agents.
  • Validation: full test suite (162 files / 3069 tests), pnpm type-check, Biome on all changed files, and lat check all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01R5MgTsR5FNrj7Tvu7T6Ebv


Generated by Claude Code


Summary by cubic

Implements the 7/7 benchmark items: adds top-of-page event-to-tier linking with smart prefill, locks benchmark boards to online scoring across UI, server, and read-time, and gates benchmark-only features. Also updates copy, affiliate placement, stats labels, and seed data for clarity.

  • New Features

    • “Link events to tier thresholds” at the top of Tiers: shows unlinked events and opens Add-test prefilled (name, scheme, score type, input unit); time and emom schemes auto-set “time”; select is bounded and scrolls.
    • Benchmark boards always rank with the online algorithm: picker hidden, organizer/cohost saves force online, and leaderboard reads resolve to online.
    • Capability gates: benchmarks disable cohosts, volunteerShifts, and publicVolunteerSignup; sidebar items hidden and routes redirect; volunteer roster and waivers remain.
    • Affiliate renders under the athlete name; the dedicated column is removed.
    • Stats page Division/Athlete selects now have labels.
    • Copy: “Pre-published” → “Auto-publish”.
    • Seed: Vertical Jump scores plain inches via points; L-sit Hold tier 10 set to 3:00.
  • Bug Fixes

    • Tier context no longer fails when tests or visible events are unlinked; mapped tests still show on the leaderboard.
    • Public volunteer signup is capability-gated server-side, preventing scripted access when unsupported.

Written for commit c95805a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Benchmark scoring boards now consistently use online scoring, with simplified scoring options.
    • Added clearer benchmark tier linking, event prefill behavior, and time-based scheme handling.
    • Updated stats filters with labeled Division and Athlete selectors.
    • Improved benchmark leaderboard context for partially mapped events.
  • Bug Fixes

    • Volunteer, co-host, and shift options are now hidden when unsupported.
    • Improved leaderboard display by removing the Affiliate column and refining auto-publish wording.
    • Corrected benchmark thresholds for Vertical Jump and L Sit Hold.

claude added 2 commits July 8, 2026 05:46
- Remove the 'Tier context is live' card and say 'half a point' in the
  scoring explainer on the organizer tiers page
- Add a 'Link events to tier thresholds' section listing unlinked events;
  selecting one opens the add-test dialog locked to that event with
  name/scheme/score type/input unit prefilled from the event
- Auto-select the 'time' input unit for time schemes in the test editor
  and bound the linked-event select to a scrollable ~10 items
- Add cohosts/volunteerShifts/publicVolunteerSignup capabilities; benchmarks
  drop cohosts, volunteer shifts, and public volunteer signup (roster and
  waivers stay); sidebar items hidden and routes redirect defensively
- Lock benchmark ranking algorithm to online (UI note + submit force +
  server-side guard) while keeping tiebreakers editable
- Remove the dedicated leaderboard Affiliate column; affiliate stays as
  subtext under the athlete name
- Label the Division and Athlete selects on the stats page
- Rename 'Pre-published' to 'Auto-publish' in division results copy
- Seed: vertical jump now scores in plain inches (points scheme) fixing a
  feet/inches mismatch; L-sit hold top tier raised to 3:00 per the
  CrossFit Journal standard
- Update lat.md capability and organizer-dashboard docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5MgTsR5FNrj7Tvu7T6Ebv
Review-round fixes from adversarial code review:
- Resolve the effective leaderboard scoring config through a helper that
  forces the online algorithm for benchmark boards, so a fresh benchmark
  with no stored scoringConfig no longer silently ranks 'traditional'
- Mirror the benchmark online force in the cohost scoring save path
- Move the 'Link events to tier thresholds' card to the top of the tiers
  page per the meeting's 'list of events up here'
- Treat emom as a time scheme for input-unit auto-selection
- Drop unused timeCapSeconds plumbing from benchmark event options
- Update lat.md docs for the read-time force and tier-linking UX

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5MgTsR5FNrj7Tvu7T6Ebv
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds capability gates for competition features, forces benchmark competitions to use online scoring, adds event-linked benchmark test editing, updates benchmark data and leaderboard rendering, and revises organizer and statistics UI terminology and labels.

Changes

Capability-Gated Competition Features

Layer / File(s) Summary
Capability registry and tests
apps/wodsmith-start/src/lib/competitions/capabilities.ts, apps/wodsmith-start/test/lib/competitions/capabilities.test.ts
Adds co-host and volunteer capabilities for supported competition types.
Navigation and registration gating
apps/wodsmith-start/src/components/competition-sidebar.tsx, apps/wodsmith-start/src/components/registration-sidebar.tsx, apps/wodsmith-start/test/components/*
Gates co-host, volunteer-shift, and volunteer-signup UI.
Route access gates
apps/wodsmith-start/src/routes/compete/...
Redirects routes when the competition lacks the required capability.
Capability documentation
lat.md/competition-type-capabilities.md
Documents capability behavior and related tests.

Benchmark Online Scoring

Layer / File(s) Summary
Scoring form lock
apps/wodsmith-start/src/components/compete/scoring-config-form.tsx, apps/wodsmith-start/src/routes/compete/.../scoring-settings-form.tsx, apps/wodsmith-start/test/components/scoring-config-form.test.tsx
Hides algorithm selection for benchmark boards and keeps the online algorithm in component state.
Route wiring and save enforcement
apps/wodsmith-start/src/routes/compete/.../scoring.tsx, apps/wodsmith-start/src/server-fns/cohost/cohost-competition-fns.ts, apps/wodsmith-start/src/server-fns/competition-detail-fns.ts
Passes the lock flag and forces benchmark scoring saves to online.
Leaderboard read resolution
apps/wodsmith-start/src/server/competition-leaderboard.ts, apps/wodsmith-start/test/server/competition-leaderboard-capability-gates.test.ts
Forces benchmark leaderboard reads to online scoring and clears customTable.

Benchmark Test Editing and Event Linking

Layer / File(s) Summary
Dialog behavior
apps/wodsmith-start/src/components/benchmark-tiers/test-editor-dialogs.tsx, apps/wodsmith-start/test/components/benchmark-test-editor-dialogs.test.tsx
Sets time input units for time-based schemes, prefills linked event fields, and protects manually edited names.
Event metadata flow
apps/wodsmith-start/src/server/benchmark-scoring-tiers.ts, apps/wodsmith-start/src/components/events/event-details-form.tsx
Passes event scheme and score type to benchmark test dialogs.
Tier-linking UI
apps/wodsmith-start/src/routes/compete/organizer/.../scoring/tiers.tsx, apps/wodsmith-start/test/server/benchmark-scoring-tiers.test.ts
Adds a card for linking unlinked events to tier thresholds.

Benchmark Data and Leaderboards

Layer / File(s) Summary
Seed thresholds
apps/wodsmith-start/scripts/seed/data/benchmark-training-guide.ts, apps/wodsmith-start/test/scripts/benchmark-seed-data.test.ts
Changes Vertical Jump to points/inches and L Sit Hold tier 10 to 03:00.
Partial tier context
apps/wodsmith-start/src/server/benchmark-leaderboard.ts, apps/wodsmith-start/test/server/benchmark-leaderboard.test.ts
Keeps mapped benchmark tests and filters categories to mapped tests.
Affiliate rendering
apps/wodsmith-start/src/components/online-competition-leaderboard-table.tsx, apps/wodsmith-start/test/components/online-competition-leaderboard-table.test.tsx, lat.md/organizer-dashboard.md
Removes the standalone Affiliate column. Affiliate text remains in the athlete cell.

Organizer and Statistics UI

Layer / File(s) Summary
Auto-publish terminology
apps/wodsmith-start/src/routes/compete/organizer/.../quick-actions-division-results.tsx, apps/wodsmith-start/src/server-fns/division-results-fns.ts, lat.md/organizer-dashboard.md
Replaces “Pre-published” terminology with “Auto-publish”.
Statistics selectors
apps/wodsmith-start/src/routes/compete/$slug/stats.tsx, apps/wodsmith-start/test/routes/compete/benchmark-stats-route.test.tsx
Adds labeled division and athlete selectors with stable IDs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

A rabbit checks each gate with care,
Online scores wait in the air.
Linked events fill fields just right,
Three-minute holds reach new height.
Auto-publish guides the flow. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the benchmark gating, tier-linking, scoring, copy, and seed-data changes in the pull request.
✨ 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 claude/benchmark-feature-refinements-7yzcfh

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.

@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: 45f6985f9e

ℹ️ 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".

}

// Public volunteer signup is capability-gated (benchmark boards exclude it)
if (!competitionCan(competition.competitionType, "publicVolunteerSignup")) {

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 Enforce volunteer signup capability in the mutations

For benchmark boards this loader hides /volunteer, but the actual write paths (submitVolunteerSignupFn and especially the anonymous createAccountAndApplyAsVolunteerFn) still accept only competitionTeamId and create the volunteer invitation without resolving the competition type. A stale client or direct server-function call can therefore still create public volunteer applications for benchmark competitions, bypassing the new publicVolunteerSignup gate; please enforce the same capability check in the mutation/shared helper before creating the application.

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.

🧹 Nitpick comments (5)
apps/wodsmith-start/test/scripts/benchmark-seed-data.test.ts (1)

139-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a // @lat: reference beside this test.
This benchmark seed-data test should carry the matching spec link inline to follow the test annotation rule.

🤖 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/test/scripts/benchmark-seed-data.test.ts` around lines
139 - 176, Add the required inline // `@lat`: reference to this benchmark
seed-data test so it satisfies the test annotation rule. Update the `it("scores
vertical jump in plain inches and tops the L-sit at three minutes", ...)` block
in `benchmark-seed-data.test.ts` and place the matching spec link beside the
test declaration or its leading comment, keeping the rest of the assertions and
helpers like `buildBenchmarkSeedRows` and `BENCHMARK_SEED_TESTS` unchanged.

Source: Coding guidelines

apps/wodsmith-start/src/routes/compete/organizer/$competitionId/-components/scoring-settings-form.tsx (1)

30-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LGTM!

The three-layer defense (init coercion, save re-force, server enforcement) is solid. One minor consistency note: forceOnlineAlgorithm early-returns when algorithm === "online" without clearing customTable, while the server-side enforcement in updateCompetitionScoringConfigFn always clears it. In practice this is harmless since handleAlgorithmChange already clears customTable when switching to a non-custom algorithm, but you could align them if desired:

Optional: align forceOnlineAlgorithm with server behavior
 function forceOnlineAlgorithm(config: ScoringConfig): ScoringConfig {
-  if (config.algorithm === "online") return config
-  return { ...config, algorithm: "online", customTable: undefined }
+  if (config.algorithm === "online" && !config.customTable) return config
+  return { ...config, algorithm: "online", customTable: undefined }
 }

Also applies to: 72-99, 124-124

🤖 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/src/routes/compete/organizer/`$competitionId/-components/scoring-settings-form.tsx
around lines 30 - 43, `forceOnlineAlgorithm` is slightly inconsistent with
`updateCompetitionScoringConfigFn` because it returns the config unchanged when
`algorithm` is already "online", leaving any stale `customTable` intact. Update
`forceOnlineAlgorithm` in `scoring-settings-form.tsx` to always normalize the
config the same way the server does by clearing `customTable` whenever the
online algorithm is enforced, and keep the save-path coercion and
`handleAlgorithmChange` behavior aligned with that rule.
apps/wodsmith-start/src/routes/compete/cohost/$competitionId/scoring.tsx (1)

79-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider centralizing the lock-algorithm capability check.

competitionCan(competition.competitionType, "benchmarkScoringTiers") is duplicated identically in this file, scoring/index.tsx, and settings.tsx. Extracting a small helper (e.g. isScoringAlgorithmLocked(type) in capabilities.ts) would give a single place to update if the gating capability ever changes.

♻️ Proposed helper
+// apps/wodsmith-start/src/lib/competitions/capabilities.ts
+export function isScoringAlgorithmLocked(type: string): boolean {
+  return competitionCan(type, "benchmarkScoringTiers")
+}
-        lockAlgorithmOnline={competitionCan(
-          competition.competitionType,
-          "benchmarkScoringTiers",
-        )}
+        lockAlgorithmOnline={isScoringAlgorithmLocked(competition.competitionType)}
🤖 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/src/routes/compete/cohost/`$competitionId/scoring.tsx
around lines 79 - 82, The lock-algorithm capability check is duplicated across
scoring-related routes, so centralize it behind a shared helper instead of
repeating competitionCan(competition.competitionType, "benchmarkScoringTiers").
Add a small function such as isScoringAlgorithmLocked(type) in capabilities.ts
and use it from scoring.tsx, scoring/index.tsx, and settings.tsx so the gating
logic lives in one place and is easy to update.
apps/wodsmith-start/src/server/competition-leaderboard.ts (1)

263-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a top-level type import over inline import(...).

ScoringConfig is almost certainly already used/imported elsewhere in this file (e.g. via calculateEventPoints, TiebreakerInput.scoringAlgorithm). Using import("@/types/scoring").ScoringConfig inline here is inconsistent with normal import style.

✨ Proposed cleanup
+import type { ScoringConfig } from "`@/types/scoring`"
...
 export function resolveLeaderboardScoringConfig(params: {
   competitionType: string
   settings: CompetitionSettings | null | undefined
-}): import("`@/types/scoring`").ScoringConfig {
+}): ScoringConfig {
🤖 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/src/server/competition-leaderboard.ts` around lines 263 -
279, The return type in resolveLeaderboardScoringConfig uses an inline import
type, which is inconsistent with the rest of the file’s import style. Add a
top-level ScoringConfig type import from "`@/types/scoring`" alongside the
existing scoring-related imports, then update resolveLeaderboardScoringConfig to
reference that imported type directly instead of
import("`@/types/scoring`").ScoringConfig.
apps/wodsmith-start/src/server-fns/cohost/cohost-competition-fns.ts (1)

408-423: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Force-online save logic is correct; add unit coverage for this path.

The override (algorithm: "online", customTable: undefined) correctly mirrors resolveLeaderboardScoringConfig's read-time override and preserves tiebreaker/statusHandling. However, only competition-leaderboard-capability-gates.test.ts (read path) was added in this cohort — there's no test exercising cohostUpdateScoringConfigFn forcing the algorithm on save for a benchmark competition, or leaving it untouched for non-benchmark types.

Want me to draft a test for cohostUpdateScoringConfigFn covering both branches?

🤖 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/src/server-fns/cohost/cohost-competition-fns.ts` around
lines 408 - 423, Add unit coverage for the save-time override in
cohostUpdateScoringConfigFn: verify that when competitionCan(...,
"benchmarkScoringTiers") is true, the saved scoringConfig forces algorithm to
"online" and clears customTable while preserving the other fields, and verify
that non-benchmark competitions pass data.scoringConfig through unchanged. Use
the existing resolveLeaderboardScoringConfig/read-path test behavior as the
reference for the expected benchmark branch.
🤖 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.

Nitpick comments:
In `@apps/wodsmith-start/src/routes/compete/cohost/`$competitionId/scoring.tsx:
- Around line 79-82: The lock-algorithm capability check is duplicated across
scoring-related routes, so centralize it behind a shared helper instead of
repeating competitionCan(competition.competitionType, "benchmarkScoringTiers").
Add a small function such as isScoringAlgorithmLocked(type) in capabilities.ts
and use it from scoring.tsx, scoring/index.tsx, and settings.tsx so the gating
logic lives in one place and is easy to update.

In
`@apps/wodsmith-start/src/routes/compete/organizer/`$competitionId/-components/scoring-settings-form.tsx:
- Around line 30-43: `forceOnlineAlgorithm` is slightly inconsistent with
`updateCompetitionScoringConfigFn` because it returns the config unchanged when
`algorithm` is already "online", leaving any stale `customTable` intact. Update
`forceOnlineAlgorithm` in `scoring-settings-form.tsx` to always normalize the
config the same way the server does by clearing `customTable` whenever the
online algorithm is enforced, and keep the save-path coercion and
`handleAlgorithmChange` behavior aligned with that rule.

In `@apps/wodsmith-start/src/server-fns/cohost/cohost-competition-fns.ts`:
- Around line 408-423: Add unit coverage for the save-time override in
cohostUpdateScoringConfigFn: verify that when competitionCan(...,
"benchmarkScoringTiers") is true, the saved scoringConfig forces algorithm to
"online" and clears customTable while preserving the other fields, and verify
that non-benchmark competitions pass data.scoringConfig through unchanged. Use
the existing resolveLeaderboardScoringConfig/read-path test behavior as the
reference for the expected benchmark branch.

In `@apps/wodsmith-start/src/server/competition-leaderboard.ts`:
- Around line 263-279: The return type in resolveLeaderboardScoringConfig uses
an inline import type, which is inconsistent with the rest of the file’s import
style. Add a top-level ScoringConfig type import from "`@/types/scoring`"
alongside the existing scoring-related imports, then update
resolveLeaderboardScoringConfig to reference that imported type directly instead
of import("`@/types/scoring`").ScoringConfig.

In `@apps/wodsmith-start/test/scripts/benchmark-seed-data.test.ts`:
- Around line 139-176: Add the required inline // `@lat`: reference to this
benchmark seed-data test so it satisfies the test annotation rule. Update the
`it("scores vertical jump in plain inches and tops the L-sit at three minutes",
...)` block in `benchmark-seed-data.test.ts` and place the matching spec link
beside the test declaration or its leading comment, keeping the rest of the
assertions and helpers like `buildBenchmarkSeedRows` and `BENCHMARK_SEED_TESTS`
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 069c0960-5c71-4fab-b7e0-a1005ceec5a8

📥 Commits

Reviewing files that changed from the base of the PR and between 6a60302 and 45f6985.

📒 Files selected for processing (36)
  • apps/wodsmith-start/scripts/seed/data/benchmark-training-guide.ts
  • apps/wodsmith-start/src/components/benchmark-tiers/test-editor-dialogs.tsx
  • apps/wodsmith-start/src/components/compete/scoring-config-form.tsx
  • apps/wodsmith-start/src/components/competition-sidebar.tsx
  • apps/wodsmith-start/src/components/events/event-details-form.tsx
  • apps/wodsmith-start/src/components/online-competition-leaderboard-table.tsx
  • apps/wodsmith-start/src/components/registration-sidebar.tsx
  • apps/wodsmith-start/src/lib/competitions/capabilities.ts
  • apps/wodsmith-start/src/routes/compete/$slug/stats.tsx
  • apps/wodsmith-start/src/routes/compete/$slug/volunteer.tsx
  • apps/wodsmith-start/src/routes/compete/cohost/$competitionId/scoring.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/-components/quick-actions-division-results.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/-components/scoring-settings-form.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/co-hosts.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/scoring/index.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/scoring/tiers.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/settings.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/volunteers/shifts.tsx
  • apps/wodsmith-start/src/server-fns/cohost/cohost-competition-fns.ts
  • apps/wodsmith-start/src/server-fns/competition-detail-fns.ts
  • apps/wodsmith-start/src/server-fns/division-results-fns.ts
  • apps/wodsmith-start/src/server/benchmark-scoring-tiers.ts
  • apps/wodsmith-start/src/server/competition-leaderboard.ts
  • apps/wodsmith-start/test/components/benchmark-test-editor-dialogs.test.tsx
  • apps/wodsmith-start/test/components/competition-sidebar-capability-gates.test.ts
  • apps/wodsmith-start/test/components/online-competition-leaderboard-table.test.tsx
  • apps/wodsmith-start/test/components/registration-sidebar.test.tsx
  • apps/wodsmith-start/test/components/scoring-config-form.test.tsx
  • apps/wodsmith-start/test/lib/competitions/capabilities.test.ts
  • apps/wodsmith-start/test/routes/compete/benchmark-stats-route.test.tsx
  • apps/wodsmith-start/test/scripts/benchmark-seed-data.test.ts
  • apps/wodsmith-start/test/server/benchmark-scoring-tiers.test.ts
  • apps/wodsmith-start/test/server/competition-leaderboard-capability-gates.test.ts
  • lat.md/competition-type-capabilities.md
  • lat.md/domain.md
  • lat.md/organizer-dashboard.md

@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 36 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/src/routes/compete/$slug/volunteer.tsx">

<violation number="1" location="apps/wodsmith-start/src/routes/compete/$slug/volunteer.tsx:18">
P2: This loader-level redirect prevents navigation to the volunteer page for benchmark boards, but the underlying mutations (`submitVolunteerSignupFn`, `createAccountAndApplyAsVolunteerFn`) still accept a `competitionTeamId` and create volunteer applications without verifying the competition type supports public volunteer signup. A direct server-function call or stale client state could bypass this gate and create volunteer applications for benchmark competitions. Consider adding the same `competitionCan(competitionType, "publicVolunteerSignup")` check inside those mutations before creating the application.</violation>
</file>

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

Re-trigger cubic

Comment thread apps/wodsmith-start/src/components/registration-sidebar.tsx
Comment thread apps/wodsmith-start/src/server/benchmark-scoring-tiers.ts Outdated
}

// Public volunteer signup is capability-gated (benchmark boards exclude it)
if (!competitionCan(competition.competitionType, "publicVolunteerSignup")) {

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: This loader-level redirect prevents navigation to the volunteer page for benchmark boards, but the underlying mutations (submitVolunteerSignupFn, createAccountAndApplyAsVolunteerFn) still accept a competitionTeamId and create volunteer applications without verifying the competition type supports public volunteer signup. A direct server-function call or stale client state could bypass this gate and create volunteer applications for benchmark competitions. Consider adding the same competitionCan(competitionType, "publicVolunteerSignup") check inside those mutations before creating the application.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/routes/compete/$slug/volunteer.tsx, line 18:

<comment>This loader-level redirect prevents navigation to the volunteer page for benchmark boards, but the underlying mutations (`submitVolunteerSignupFn`, `createAccountAndApplyAsVolunteerFn`) still accept a `competitionTeamId` and create volunteer applications without verifying the competition type supports public volunteer signup. A direct server-function call or stale client state could bypass this gate and create volunteer applications for benchmark competitions. Consider adding the same `competitionCan(competitionType, "publicVolunteerSignup")` check inside those mutations before creating the application.</comment>

<file context>
@@ -13,6 +14,11 @@ export const Route = createFileRoute("/compete/$slug/volunteer")({
     }
 
+    // Public volunteer signup is capability-gated (benchmark boards exclude it)
+    if (!competitionCan(competition.competitionType, "publicVolunteerSignup")) {
+      throw redirect({ to: "/compete/$slug", params: { slug: params.slug } })
+    }
</file context>

Comment thread apps/wodsmith-start/src/components/compete/scoring-config-form.tsx

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

🤖 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/src/server/benchmark-leaderboard.ts`:
- Around line 208-223: Add a `// `@lat`: [[section-id]]` reference adjacent to the
partial public tier-context logic around `mappedCategories`, using the
repository’s established section identifier for this behavior. Keep the existing
mapping and denominator logic unchanged.
🪄 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 Plus

Run ID: 877bf892-d8fc-404d-a286-f87d06217e82

📥 Commits

Reviewing files that changed from the base of the PR and between 45f6985 and 3a07e37.

📒 Files selected for processing (4)
  • apps/wodsmith-start/src/server/benchmark-leaderboard.ts
  • apps/wodsmith-start/test/server/benchmark-leaderboard.test.ts
  • lat.md/competition-type-capabilities.md
  • lat.md/organizer-dashboard.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • lat.md/organizer-dashboard.md
  • lat.md/competition-type-capabilities.md

Comment thread apps/wodsmith-start/src/server/benchmark-leaderboard.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.

All reported issues were addressed across 4 files (changes from recent commits).

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

Re-trigger cubic

Comment thread lat.md/organizer-dashboard.md
@zacjones93
zacjones93 merged commit 8f11595 into feat/hillerfit-plan Aug 2, 2026
16 checks passed
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.

2 participants