Skip to content

Add division allocation summary card to invites page#432

Open
zacjones93 wants to merge 1 commit into
mainfrom
claude/fix-division-allocation-settings-XviOQ
Open

Add division allocation summary card to invites page#432
zacjones93 wants to merge 1 commit into
mainfrom
claude/fix-division-allocation-settings-XviOQ

Conversation

@zacjones93

@zacjones93 zacjones93 commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new DivisionAllocationSummary component to the organizer invites page that displays how qualifying spots are distributed across championship divisions. The component shows per-division totals and allows expanding each division to see the per-source breakdown, giving organizers visibility into which sources contribute spots to each division.

Key Changes

  • New component: DivisionAllocationSummary renders a collapsible card showing:

    • Total qualifying spots across all divisions
    • Per-division spot counts with source breakdown on expand
    • Per-source spot allocation for each division in a table
    • Filters out sources that contribute zero spots to a division
    • Disables expansion for divisions with no contributing sources
  • Enhanced source details page:

    • Now displays the default-spots formula inline for series sources: directSpotsPerComp × seriesCompCount + globalSpots
    • Shows the formula breakdown (e.g., "5 = 2 direct × 2 comps + 1 global") so organizers understand how defaults are calculated
    • Improved visual presentation of the per-division allocation card description
  • Server function enhancement: getInviteSourceByIdFn now returns seriesCompCount for series sources, enabling the details page to render the formula without inferring from the resolved allocation map

  • Integration: Added DivisionAllocationSummary to the main invites page, passing divisions, sources, and the allocation map

  • Comprehensive test coverage: Added 188 lines of tests covering:

    • Championship total calculation
    • Per-source spot isolation in breakdowns
    • Filtering of zero-spot sources
    • Disabled state for divisions with no sources
    • Empty state rendering
    • Per-division totals display

Implementation Details

The component reads the same allocationsBySourceByDivision map used by the Sources/Sent tabs, ensuring a single source of truth for resolved spots per (source, championship-division) pair. Each source's quota is enforced independently at claim time, so the per-source breakdown matches the runtime enforcement scoping organizers care about.

The formula display on the source details page is derived directly from the source row and seriesCompCount rather than inferred from the resolved allocation map, ensuring the displayed formula always matches what the resolver computes.

https://claude.ai/code/session_01AdwTtm2zcKjKBaE9ToAPhU


Summary by cubic

Adds a collapsible Division Allocation Summary card to the invites page so organizers can see per-division totals and which sources contribute spots. Also shows the default-spots formula on the source details page to make the math clear and accurate.

  • New Features
    • New DivisionAllocationSummary card with per-division totals; expand to see per-source counts. Hides zero-spot sources and disables expansion when none.
    • Mounted above the invites tabs so it’s always visible.
    • Source details page now displays the default formula for series: directSpotsPerComp × seriesCompCount + globalSpots, with a numeric breakdown. Derived from the source row and server-provided seriesCompCount.
    • Server: getInviteSourceByIdFn returns seriesCompCount for series sources to power the formula display.
    • Tests cover championship totals, per-source isolation, zero-source filtering, disabled/empty states, and per-division totals.

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

Summary by CodeRabbit

  • New Features

    • Added Division Allocation Summary card displaying qualifying spot allocations per championship division with collapsible per-source breakdowns on the invites page.
  • Tests

    • Added test suite for Division Allocation Summary component covering rendering, expansion behavior, and zero-allocation filtering.
  • Documentation

    • Updated documentation covering allocation formula logic and the new Division Allocation Summary feature.

… source-detail page

- Add `DivisionAllocationSummary` card to the organizer invites route. Mounted
  above the tabs so the per-division spot allocation is visible regardless of
  which tab is active. Each row collapses to show the per-source breakdown so
  organizers can see exactly which sources contribute spots to that division.
- Fix the source-detail page's misleading "Default is 5" copy. For series
  sources the resolved default is `directSpotsPerComp × seriesCompCount +
  globalSpots` — the new copy renders the formula inline (e.g. `5 = 2 direct
  × 2 comps + 1 global`) so the math is never a black box. `seriesCompCount`
  is loaded server-side via `getInviteSourceByIdFn` so the formula always
  matches the resolver's math.
- Source-isolation behavior is unchanged. Each source's quota is still
  enforced independently at claim time + Stripe re-check; the new card just
  surfaces the existing scoping so organizers can trust what they see.
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

A new React component DivisionAllocationSummary is added to display qualifying spot allocations per division with expandable per-source breakdowns. The component is integrated into the invites page, the server function is enhanced to return series competition counts, and the source details page is updated to compute per-division defaults using the new data. Comprehensive tests and documentation are included.

Changes

Cohort / File(s) Summary
DivisionAllocationSummary Component
apps/wodsmith-start/src/components/organizer/invites/division-allocation-summary.tsx, apps/wodsmith-start/test/components/division-allocation-summary.test.tsx
New React component that renders a collapsible card per division showing total allocated spots, with expandable breakdown of contributing sources. Includes comprehensive UI test suite verifying totals, per-source breakdowns, disabled states, and empty-state handling.
Invites Page Integration
apps/wodsmith-start/src/routes/compete/organizer/$competitionId/invites/index.tsx
Mounts DivisionAllocationSummary above the tabbed UI, passing resolved allocation data from the loader to provide allocation context across championship divisions and sources.
Source Details Computation
apps/wodsmith-start/src/routes/compete/organizer/$competitionId/invites/sources/$sourceId.tsx
Updates per-division allocation logic to derive defaults directly from source fields and seriesCompCount via formula: series defaults as directSpotsPerComp × seriesCompCount + globalSpots, competition defaults as globalSpots. Refactors UI to display detailed formula breakdowns instead of inferring from resolved allocations.
Server Function Enhancement
apps/wodsmith-start/src/server-fns/competition-invite-fns.ts
Augments getInviteSourceByIdFn response with seriesCompCount, which counts filtered competitions in the database for series-type sources; returns null for single-competition sources.
Documentation
lat.md/competition-invites.md
Documents the new division allocation summary UI feature, per-division default computation formulas, server-side series count retrieval, and expanded-state rendering behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A summary card hops into place,
Each division gets its own space,
Expanding to show who contributes their spots,
Totals computed without a single knot,
Series and competitions dance side-by-side! 🎪

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the primary change: adding a new DivisionAllocationSummary component to the invites page. The title is concise, specific, and directly reflects the main changeset.
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.

✨ 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/fix-division-allocation-settings-XviOQ

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (1)
apps/wodsmith-start/src/components/organizer/invites/division-allocation-summary.tsx (1)

60-74: Avoid duplicating source-label resolution logic.

sourceLabel duplicates logic already implemented in apps/wodsmith-start/src/routes/compete/organizer/$competitionId/invites/index.tsx (resolveSourceLabel). Consider extracting a shared helper to prevent future drift in fallback behavior/text.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/wodsmith-start/src/components/organizer/invites/division-allocation-summary.tsx`
around lines 60 - 74, The function sourceLabel duplicates the source-label
resolution implemented as resolveSourceLabel in the compete/organizer invites
route; extract a shared helper (e.g., resolveSourceLabel or getSourceLabel) into
a common module and replace the local sourceLabel with an import to that helper
in
apps/wodsmith-start/src/components/organizer/invites/division-allocation-summary.tsx
so both components use the same logic and fallbacks (preserve the existing
fallback strings "Unknown series"/"Unknown competition"); update imports and
remove the duplicate function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/wodsmith-start/test/components/division-allocation-summary.test.tsx`:
- Around line 32-188: Add the required LAT reference comment for this spec by
inserting a single line comment of the form "// `@lat`:<spec-id>" immediately next
to the DivisionAllocationSummary spec block (e.g. directly above or beside the
describe("DivisionAllocationSummary", ...) declaration); use a meaningful spec
id such as "division-allocation-summary" (or the repo's canonical LAT id) and
ensure exactly one "// `@lat`:" comment appears for this spec section (do not add
it at the top of the file or duplicate it next to each it(...) block).

In `@lat.md/competition-invites.md`:
- Around line 57-58: Replace the inline code reference `getInviteSourceByIdFn`
with a source-code wiki link using the project's link format (e.g.
[[path/to/file.ts#getInviteSourceByIdFn]]), updating the text in
lat.md/competition-invites.md so the function is referenced as a proper source
link rather than plain inline code.

---

Nitpick comments:
In
`@apps/wodsmith-start/src/components/organizer/invites/division-allocation-summary.tsx`:
- Around line 60-74: The function sourceLabel duplicates the source-label
resolution implemented as resolveSourceLabel in the compete/organizer invites
route; extract a shared helper (e.g., resolveSourceLabel or getSourceLabel) into
a common module and replace the local sourceLabel with an import to that helper
in
apps/wodsmith-start/src/components/organizer/invites/division-allocation-summary.tsx
so both components use the same logic and fallbacks (preserve the existing
fallback strings "Unknown series"/"Unknown competition"); update imports and
remove the duplicate function.
🪄 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: 0d3d7820-fd61-40d8-a081-f3fa53d1fba5

📥 Commits

Reviewing files that changed from the base of the PR and between 66456c1 and 4d285b3.

📒 Files selected for processing (6)
  • apps/wodsmith-start/src/components/organizer/invites/division-allocation-summary.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/invites/index.tsx
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId/invites/sources/$sourceId.tsx
  • apps/wodsmith-start/src/server-fns/competition-invite-fns.ts
  • apps/wodsmith-start/test/components/division-allocation-summary.test.tsx
  • lat.md/competition-invites.md

Comment on lines +32 to +188
describe("DivisionAllocationSummary", () => {
it("renders the championship total across all divisions", () => {
const sources = [
source({
id: "src_series",
kind: "series",
sourceGroupId: "grp_throwdown",
}),
source({
id: "src_comp",
kind: "competition",
sourceCompetitionId: "comp_global",
}),
]
render(
<DivisionAllocationSummary
divisions={divisions}
sources={sources}
allocationsBySourceByDivision={{
src_series: { div_rxm: 5, div_rxw: 5 },
src_comp: { div_rxm: 3, div_rxw: 3 },
}}
seriesNamesById={{ grp_throwdown: "2025 Throwdown Series" }}
competitionNamesById={{ comp_global: "Global Leaderboard" }}
/>,
)
// 5 + 5 + 3 + 3 = 16
expect(screen.getByText("16")).toBeInTheDocument()
})

it("isolates each source's per-division spots in the breakdown", () => {
const sources = [
source({
id: "src_series",
kind: "series",
sourceGroupId: "grp_throwdown",
}),
source({
id: "src_comp",
kind: "competition",
sourceCompetitionId: "comp_global",
}),
]
render(
<DivisionAllocationSummary
divisions={divisions}
sources={sources}
allocationsBySourceByDivision={{
// Series gives Men's RX 5 spots; Competition source gives 7 (override).
src_series: { div_rxm: 5, div_rxw: 5 },
src_comp: { div_rxm: 7, div_rxw: 4 },
}}
seriesNamesById={{ grp_throwdown: "2025 Throwdown Series" }}
competitionNamesById={{ comp_global: "Global Leaderboard" }}
/>,
)

// Click the Men's RX trigger to expand its breakdown.
const trigger = screen.getByRole("button", {
name: /Men's RX:\s*12 spots/i,
})
fireEvent.click(trigger)

// Both sources appear with their independent spot counts (5 and 7).
expect(screen.getByText("2025 Throwdown Series")).toBeInTheDocument()
expect(screen.getByText("Global Leaderboard")).toBeInTheDocument()
})

it("hides sources that contribute zero spots from the breakdown", () => {
const sources = [
source({
id: "src_series",
kind: "series",
sourceGroupId: "grp_throwdown",
}),
source({
id: "src_zero",
kind: "competition",
sourceCompetitionId: "comp_zero",
}),
]
render(
<DivisionAllocationSummary
divisions={[{ id: "div_rxm", label: "Men's RX" }]}
sources={sources}
allocationsBySourceByDivision={{
src_series: { div_rxm: 5 },
src_zero: { div_rxm: 0 },
}}
seriesNamesById={{ grp_throwdown: "2025 Throwdown Series" }}
competitionNamesById={{ comp_zero: "Excluded Comp" }}
/>,
)

fireEvent.click(
screen.getByRole("button", { name: /Men's RX:\s*5 spots/i }),
)
expect(screen.getByText("2025 Throwdown Series")).toBeInTheDocument()
expect(screen.queryByText("Excluded Comp")).not.toBeInTheDocument()
})

it("disables the toggle when a division has no contributing sources", () => {
render(
<DivisionAllocationSummary
divisions={[{ id: "div_team", label: "Team RX" }]}
sources={[
source({
id: "src_series",
kind: "series",
sourceGroupId: "grp_throwdown",
}),
]}
allocationsBySourceByDivision={{ src_series: {} }}
seriesNamesById={{ grp_throwdown: "2025 Throwdown Series" }}
/>,
)

const trigger = screen.getByRole("button", {
name: /Team RX: 0 spots/i,
})
expect(trigger).toBeDisabled()
})

it("renders the empty-state copy when no divisions are provided", () => {
render(
<DivisionAllocationSummary
divisions={[]}
sources={[]}
allocationsBySourceByDivision={{}}
/>,
)
expect(
screen.getByText(/no divisions yet/i),
).toBeInTheDocument()
})

it("shows the per-division resolved total in the trigger row", () => {
render(
<DivisionAllocationSummary
divisions={[{ id: "div_rxm", label: "Men's RX" }]}
sources={[
source({
id: "src_series",
kind: "series",
sourceGroupId: "grp_throwdown",
}),
]}
allocationsBySourceByDivision={{ src_series: { div_rxm: 5 } }}
seriesNamesById={{ grp_throwdown: "2025 Throwdown Series" }}
/>,
)
const trigger = screen.getByRole("button", {
name: /Men's RX:\s*5 spots/i,
})
expect(within(trigger).getByText("5")).toBeInTheDocument()
})
})

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

Add required // @lat: spec reference comment(s) next to the test section.

This suite is missing the required LAT linkage comment in the spec block.

🧩 Suggested minimal fix
 describe("DivisionAllocationSummary", () => {
+  // `@lat`: [[competition-invites#Division allocation summary]]
   it("renders the championship total across all divisions", () => {

As per coding guidelines, "Reference test specs from code with exactly one // @lat: ... per spec section, placed next to the relevant test — not at the top of the file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/wodsmith-start/test/components/division-allocation-summary.test.tsx`
around lines 32 - 188, Add the required LAT reference comment for this spec by
inserting a single line comment of the form "// `@lat`:<spec-id>" immediately next
to the DivisionAllocationSummary spec block (e.g. directly above or beside the
describe("DivisionAllocationSummary", ...) declaration); use a meaningful spec
id such as "division-allocation-summary" (or the repo's canonical LAT id) and
ensure exactly one "// `@lat`:" comment appears for this spec section (do not add
it at the top of the file or duplicate it next to each it(...) block).

Comment on lines +57 to +58
The per-division allocation card surfaces the **default-spots formula** inline so the resolved number is never a black box: for series sources it renders `defaultPerDivision = directSpotsPerComp × seriesCompCount + globalSpots` (e.g. `5 = 2 direct × 2 comps + 1 global`), and for single-comp sources it renders `defaultPerDivision = top globalSpots qualifies, applied to every division`. The series comp count is loaded server-side by `getInviteSourceByIdFn` (one extra count query when `source.sourceGroupId` is set) and threaded through the loader so the formula always matches the resolver's math — no client-side inference from the resolved allocation map.

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

Use a wiki source-code link for the server function reference.

getInviteSourceByIdFn is referenced as inline code text, but this section should use a source-code wiki link format for function references.

✏️ Suggested doc fix
-The per-division allocation card surfaces the **default-spots formula** inline so the resolved number is never a black box: for series sources it renders `defaultPerDivision = directSpotsPerComp × seriesCompCount + globalSpots` (e.g. `5 = 2 direct × 2 comps + 1 global`), and for single-comp sources it renders `defaultPerDivision = top globalSpots qualifies, applied to every division`. The series comp count is loaded server-side by `getInviteSourceByIdFn` (one extra count query when `source.sourceGroupId` is set) and threaded through the loader so the formula always matches the resolver's math — no client-side inference from the resolved allocation map.
+The per-division allocation card surfaces the **default-spots formula** inline so the resolved number is never a black box: for series sources it renders `defaultPerDivision = directSpotsPerComp × seriesCompCount + globalSpots` (e.g. `5 = 2 direct × 2 comps + 1 global`), and for single-comp sources it renders `defaultPerDivision = top globalSpots qualifies, applied to every division`. The series comp count is loaded server-side by [[apps/wodsmith-start/src/server-fns/competition-invite-fns.ts#getInviteSourceByIdFn]] (one extra count query when `source.sourceGroupId` is set) and threaded through the loader so the formula always matches the resolver's math — no client-side inference from the resolved allocation map.

As per coding guidelines, "Use source code links in lat.md/ files to reference functions, classes, constants, and methods with format [[path/to/file.ts#symbol]]..."

📝 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
The per-division allocation card surfaces the **default-spots formula** inline so the resolved number is never a black box: for series sources it renders `defaultPerDivision = directSpotsPerComp × seriesCompCount + globalSpots` (e.g. `5 = 2 direct × 2 comps + 1 global`), and for single-comp sources it renders `defaultPerDivision = top globalSpots qualifies, applied to every division`. The series comp count is loaded server-side by `getInviteSourceByIdFn` (one extra count query when `source.sourceGroupId` is set) and threaded through the loader so the formula always matches the resolver's math — no client-side inference from the resolved allocation map.
The per-division allocation card surfaces the **default-spots formula** inline so the resolved number is never a black box: for series sources it renders `defaultPerDivision = directSpotsPerComp × seriesCompCount + globalSpots` (e.g. `5 = 2 direct × 2 comps + 1 global`), and for single-comp sources it renders `defaultPerDivision = top globalSpots qualifies, applied to every division`. The series comp count is loaded server-side by [[apps/wodsmith-start/src/server-fns/competition-invite-fns.ts#getInviteSourceByIdFn]] (one extra count query when `source.sourceGroupId` is set) and threaded through the loader so the formula always matches the resolver's math — no client-side inference from the resolved allocation map.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lat.md/competition-invites.md` around lines 57 - 58, Replace the inline code
reference `getInviteSourceByIdFn` with a source-code wiki link using the
project's link format (e.g. [[path/to/file.ts#getInviteSourceByIdFn]]), updating
the text in lat.md/competition-invites.md so the function is referenced as a
proper source link rather than plain inline code.

@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.

No issues found across 6 files

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