Conversation
The draggable ranked (RCV) ballot let a voter rank any number of candidates, but server-side validation (packages/shared/src/domain_model/Ballot.ts) rejects any ranked ballot with a score above election.settings.max_rankings — so a voter could build a ballot that submit then rejected, with no hint while they were building it. The classic ranked ballot already enforces the limit by only offering that many rank columns. - Extract the classic ballot's limit resolution (max_rankings capped at REACT_APP_MAX_BALLOT_RANKS, defaulting to REACT_APP_DEFAULT_BALLOT_RANKS when the setting is unset) into getMaxRankings() in components/util.tsx and use it from both ranked ballot views, so the two cannot drift. - DraggableIRVBallotView now refuses a drop into "Your Rankings" once the limit is reached (reordering within the list and removing candidates stay allowed), shows a warning Alert explaining the limit, and displays an "N of M rankings used" counter next to the "Your Rankings" heading. - New i18n strings ballot.rankings_used and ballot.max_rankings_reached in en.yaml; other locales fall back to English. - Move RankedBallotView's draggable early-return below its hooks. It returned before them, so paging from a draggable IRV race to another ranked race in the same election changed the hook count between renders and crashed with "Rendered more hooks than during the previous render" (white page). Verified manually with a local dev server against the production API (election cxrf8v, with the limit temporarily lowered to exercise the refusal): drops beyond the limit are refused with the alert shown, the counter tracks, reorder/remove still work at the limit, and paging between the draggable IRV race and the Ranked Robin race no longer crashes. npx tsc --noEmit passes; eslint reports no new issues in the touched files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Deploy Preview for bettervoting ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Warning Review limit reached
Next review available in: 34 minutes Limit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
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. Comment |
Description
The draggable ranked (RCV) ballot ignored the election's
max_rankingssetting: a voter could drag any number of candidates into "Your Rankings", and only found out at submit time, when server-side validation rejected the ballot they had just built — with no hint while they were building it.Where the cap was missing:
packages/frontend/src/components/Election/Voting/DraggableIRVBallotView.tsx—onDragEnd(lines ~78–104 onmain) committed a rank for every candidate dropped into the ranked list, with no limit check.What the server enforces:
packages/shared/src/domain_model/Ballot.tsline 84 readselection.settings.max_rankings, and lines 116–124 reject any ranked-method score above it ("Scores out of bounds").How the classic ranked ballot already enforces it:
packages/frontend/src/components/Election/Voting/RankedBallotView.tsx(lines 31–39 onmain) resolves the limit —min(max_rankings, REACT_APP_MAX_BALLOT_RANKS ?? 8), defaulting toREACT_APP_DEFAULT_BALLOT_RANKS ?? 6when unset — and only renders that many rank columns (candidates.slice(0, maxRankings), line ~104), so exceeding the limit is impossible there.Changes
getMaxRankings()inpackages/frontend/src/components/util.tsx, used by both ranked ballot views, so the two views (and the served-side rule they mirror) cannot drift.DraggableIRVBallotViewnow refuses a drop into "Your Rankings" once the limit is reached — the candidate stays in "Available Candidates" and a warningAlertexplains the limit (it clears when the voter frees up a slot or performs another drag). Reordering within the rankings and removing candidates remain allowed at the limit.en.yaml(ballot.rankings_used,ballot.max_rankings_reached); other locales fall back to English.RankedBallotView's draggable early-return below its hooks. It previously returned before them, so paging from a draggable IRV race to another ranked race in the same election changed the hook count between renders and crashed React ("Rendered more hooks than during the previous render" — white page). Found while verifying this fix on a multi-race election.No server-side validation was changed.
Screenshots / Videos (frontend only)
No screenshots attached; verified manually as described below. The counter renders as grey text after the "Your Rankings" heading ("0 of 6 rankings used"), and the refusal shows a standard MUI warning alert under the rankings list: "You can rank up to 6 candidates on this ballot. To rank this candidate, first drag another one out of your rankings."
Manual verification (local dev server against the production API, election
cxrf8v, 5 candidates, with the limit temporarily lowered to 3 locally to make it reachable):onDragEnd, so it is covered by the same guard.npx tsc --noEmitpasses inpackages/frontend;eslintreports no new issues in the touched files (the frontend has no test suite or Storybook to extend).Related Issues
Part of the draggable-ballot bug list in #1556 (draggable ranked ballot does not respect the maximum-rankings limit).
🤖 Generated with Claude Code