Skip to content

#286 Add player score adjustments#309

Merged
ianpaschal merged 5 commits intomainfrom
286-add-player-score-adjustments
Mar 2, 2026
Merged

#286 Add player score adjustments#309
ianpaschal merged 5 commits intomainfrom
286-add-player-score-adjustments

Conversation

@ianpaschal
Copy link
Owner

@ianpaschal ianpaschal commented Feb 24, 2026

Resolves: #286


Summary by CodeRabbit

Release Notes

  • New Features

    • Added score adjustments (bonuses and penalties) for tournament registrations and competitors. Organizers can now apply adjustments by round and ranking factor.
    • Added context menus to tournament detail pages for quick actions on competitors and registrations.
    • Improved page headers with dynamic titles and contextual menus for better navigation.
  • Chores

    • Reorganized internal score adjustment components for better code maintainability.
    • Enhanced backend trigger system to automatically refresh tournament results when score adjustments change.

@vercel
Copy link

vercel bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
combat-command Ready Ready Preview, Comment Mar 1, 2026 6:41pm

@ianpaschal ianpaschal marked this pull request as ready for review February 24, 2026 15:48
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This pull request introduces score adjustments functionality across tournament competitions and registrations. The changes include backend mutations updated to accept optional scoreAdjustments arrays, new trigger handlers that refresh tournament results when registrations change, and database schema extensions. Frontend components are refactored with ScoreAdjustmentFormItem renamed to ScoreAdjustmentFields and given a new subjectDisplayName prop. PageWrapper gains contextMenu support, header layouts transition to CSS grid, and new tournament registration forms integrate score adjustment fields with organizer-only visibility. Utility functions are reorganized for score adjustment formatting, and context menus are integrated into tournament detail pages.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is incomplete and lacks substantive detail about the changes, objectives, or rationale. Complete the description by replacing '???' with the actual issue number and adding a comprehensive summary of the changes, motivation, and testing performed.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: adding player score adjustments functionality across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 286-add-player-score-adjustments

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/PageWrapper/PageWrapper.tsx (1)

63-75: ⚠️ Potential issue | 🟡 Minor

contextMenu is silently dropped when the header guard is false.

If a caller passes contextMenu alongside hideTitle={true} (and no showBackButton), the prop is silently ignored because the entire header <div> is gated by showBackButton || (title && !hideTitle). At minimum, contextMenu should be included in the guard condition:

🛡️ Proposed guard fix
-         {(showBackButton || (title && !hideTitle)) && (
+         {(showBackButton || (title && !hideTitle) || contextMenu) && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/PageWrapper/PageWrapper.tsx` around lines 63 - 75, The header
currently renders only when showBackButton || (title && !hideTitle), which drops
contextMenu if hideTitle is true and showBackButton is false; update the render
guard to also check for contextMenu so the header <div
className={styles.PageWrapper_Header}> is shown when contextMenu is provided
(e.g., showBackButton || (title && !hideTitle) || contextMenu), then keep the
existing cloneElement(contextMenu, { className:
clsx(contextMenu.props.className, styles.PageWrapper_ContextMenu) }) logic
intact so contextMenu is not silently ignored; adjust any related variable names
(contextMenu, hideTitle, showBackButton, PageWrapper_Header) accordingly.
convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts (1)

14-37: ⚠️ Potential issue | 🟠 Major

Any user with Edit permission can set scoreAdjustments, not just organizers.

The UI correctly gates score adjustment editing behind isOrganizer, but the backend mutation applies { ...updated } (which includes scoreAdjustments) for anyone who has the Edit action. Per getAvailableActions, captains and the registrant themselves also have Edit on published tournaments. A non-organizer could craft a direct API call to set scoreAdjustments on their own registration.

Consider adding a server-side check that strips or rejects scoreAdjustments when the caller is not an organizer.

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

In
`@convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts`
around lines 14 - 37, The mutation updateTournamentRegistration currently
applies all fields from updated (from updateTournamentRegistrationArgs)
including scoreAdjustments to anyone with TournamentRegistrationActionKey.Edit;
add a server-side guard after computing availableActions to prevent
non-organizers from changing scoreAdjustments: detect organizer status (e.g.,
call an isOrganizer helper or check availableActions for the organizer action
key) and if the caller is not an organizer either strip scoreAdjustments from
the updated payload or throw a ConvexError; update the logic in
updateTournamentRegistration to only allow scoreAdjustments to be applied when
the caller is confirmed organizer before calling ctx.db.patch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@convex/_model/tournamentCompetitors/triggers.ts`:
- Around line 17-31: The current early return on deleted competitors (if
(!newDoc) return) prevents refreshing tournamentResults on deletions; change the
logic to use tournamentId = newDoc?.tournamentId ?? oldDoc?.tournamentId so you
still resolve the tournament via getDocStrict(ctx, tournamentId) when newDoc is
null, and perform the refresh of tournamentResults even for deletions; keep the
scoreAdjustments equality check only for update cases (i.e., only skip when
newDoc exists and scoreAdjustments didn't change), and still preserve the
archived check (tournament.status === 'archived') to avoid making changes for
archived tournaments.

In `@convex/_model/tournamentResults/mutations/refreshTournamentResult.ts`:
- Around line 33-62: The current adjustedRegistrations and adjustedCompetitors
mappers perform repeated linear scans (using find/filter) over
tournamentRegistrations and tournamentCompetitors, producing O(n²) behavior; fix
this by precomputing lookup maps before the maps: build a regById map from
tournamentRegistrations (keyed by _id), a compById map from
tournamentCompetitors (keyed by _id), and a regsByCompetitorId map grouping
registrations by tournamentCompetitorId, then rewrite the logic in
adjustedRegistrations to use regById and compById lookups and in
adjustedCompetitors to use regsByCompetitorId and compById so all operations
become linear; keep using applyScoreAdjustments and preserve the same ordering
of applying registration then competitor adjustments.

In `@src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx`:
- Around line 119-127: The handler handleAddScoreAdjustment uses
[...tournament.rankingFactors].pop()! which can produce undefined for an empty
rankingFactors array; change it to safely derive the last ranking factor before
calling append: compute const last = tournament.rankingFactors &&
tournament.rankingFactors.length > 0 ?
tournament.rankingFactors[tournament.rankingFactors.length - 1] : <saneDefault>;
then call append({ amount: 1, round: tournament.currentRound ?? 0,
rankingFactor: last, reason: '' }); pick a saneDefault that matches the
rankingFactor type (e.g. 0 or 1) and ensure types reflect the fallback.

In
`@src/pages/TournamentCompetitorDetailPage/components/Header/Header.module.scss`:
- Around line 75-79: The media query block that re-declares --avatar-size for
.Header is redundant because .Header already sets --avatar-size: 8rem; remove
the entire `@media` (width >= 688px) { .Header { --avatar-size: 8rem; } } block
from Header.module.scss so there is no duplicate declaration; ensure .Header
still defines --avatar-size in its base rule and run the style build to confirm
no visual changes.

In `@src/pages/TournamentCompetitorDetailPage/TournamentCompetitorDetailPage.tsx`:
- Around line 98-108: When tournament.useTeams is false you currently pass
tournamentCompetitor.registrations[0] into TournamentRegistrationContextMenu
which can be undefined and crash useActions; update the conditional to check
tournamentCompetitor.registrations?.length > 0 (or registrations?.[0]) before
rendering TournamentRegistrationContextMenu and otherwise render null or a safe
fallback (e.g. no contextMenu or a disabled menu). Locate this in
TournamentCompetitorDetailPage where contextMenu is set and ensure
TournamentRegistrationContextMenu only receives a defined tournamentRegistration
prop.

---

Outside diff comments:
In
`@convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts`:
- Around line 14-37: The mutation updateTournamentRegistration currently applies
all fields from updated (from updateTournamentRegistrationArgs) including
scoreAdjustments to anyone with TournamentRegistrationActionKey.Edit; add a
server-side guard after computing availableActions to prevent non-organizers
from changing scoreAdjustments: detect organizer status (e.g., call an
isOrganizer helper or check availableActions for the organizer action key) and
if the caller is not an organizer either strip scoreAdjustments from the updated
payload or throw a ConvexError; update the logic in updateTournamentRegistration
to only allow scoreAdjustments to be applied when the caller is confirmed
organizer before calling ctx.db.patch.

In `@src/components/PageWrapper/PageWrapper.tsx`:
- Around line 63-75: The header currently renders only when showBackButton ||
(title && !hideTitle), which drops contextMenu if hideTitle is true and
showBackButton is false; update the render guard to also check for contextMenu
so the header <div className={styles.PageWrapper_Header}> is shown when
contextMenu is provided (e.g., showBackButton || (title && !hideTitle) ||
contextMenu), then keep the existing cloneElement(contextMenu, { className:
clsx(contextMenu.props.className, styles.PageWrapper_ContextMenu) }) logic
intact so contextMenu is not silently ignored; adjust any related variable names
(contextMenu, hideTitle, showBackButton, PageWrapper_Header) accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3169a58 and 417d38f.

⛔ Files ignored due to path filters (1)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (27)
  • convex/_model/tournamentCompetitors/triggers.ts
  • convex/_model/tournamentRegistrations/index.ts
  • convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts
  • convex/_model/tournamentRegistrations/table.ts
  • convex/_model/tournamentRegistrations/triggers.ts
  • convex/_model/tournamentResults/mutations/refreshTournamentResult.ts
  • convex/functions.ts
  • convex/tournamentRegistrations.ts
  • src/components/PageWrapper/PageWrapper.module.scss
  • src/components/PageWrapper/PageWrapper.tsx
  • src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.module.scss
  • src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.schema.ts
  • src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.tsx
  • src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.types.ts
  • src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.utils.ts
  • src/components/ScoreAdjustmentFields/index.ts
  • src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts
  • src/components/TournamentCompetitorForm/TournamentCompetitorForm.tsx
  • src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.utils.ts
  • src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/index.ts
  • src/components/TournamentRegistrationForm/TournamentRegistrationForm.module.scss
  • src/components/TournamentRegistrationForm/TournamentRegistrationForm.schema.ts
  • src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx
  • src/components/TournamentRegistrationProvider/TournamentRegistrationContextMenu.tsx
  • src/pages/TournamentCompetitorDetailPage/TournamentCompetitorDetailPage.tsx
  • src/pages/TournamentCompetitorDetailPage/components/Header/Header.module.scss
  • src/pages/TournamentCompetitorDetailPage/components/Header/Header.tsx
💤 Files with no reviewable changes (2)
  • src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/index.ts
  • src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.utils.ts

…der.module.scss

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@ianpaschal ianpaschal merged commit b138b29 into main Mar 2, 2026
3 checks passed
@ianpaschal ianpaschal deleted the 286-add-player-score-adjustments branch March 2, 2026 11:15
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