Skip to content

Conversation

@ianpaschal
Copy link
Owner

@ianpaschal ianpaschal commented Feb 10, 2026

Resolves: #287


Summary by CodeRabbit

  • New Features

    • Match result form adapts per selected game system and validates submissions accordingly.
    • Tournament player and faction fields auto-fill from registration when a single option exists.
    • Faction selection can be disabled in contexts where it’s not applicable.
  • Improvements

    • Submission errors show concise toast guidance when game system is missing or form validation fails.
    • Field labels adjust based on tournament type (team vs. individual).

@vercel
Copy link

vercel bot commented Feb 10, 2026

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

Project Deployment Actions Updated (UTC)
combat-command Ready Ready Preview, Comment Feb 10, 2026 0:41am

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Warning

Rate limit exceeded

@ianpaschal has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Updated internal package versions. Refactored match result form schema into a game-system-parameterized factory. Added optional disableFactions prop and precomputed option lists to match result detail components. Tournament player fields now auto-fill player IDs and factions from registrations. Validation toast message made generic.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Bumped @ianpaschal/combat-command-components ^1.8.3 → ^1.8.4 and @ianpaschal/combat-command-game-systems ^1.4.0 → ^1.4.1.
Match Result Detail Components
src/components/MatchResultDetailFields/MatchResultDetailFields.tsx, src/components/MatchResultDetailFields/gameSystems/FlamesOfWarV4MatchResultDetailFields/..., src/components/MatchResultDetailFields/gameSystems/TeamYankeeV2MatchResultDetailFields/...
Added optional disableFactions prop to props/interfaces; switched component to accept single props object and forward via spread; precomputed factionOptions and battlePlanOptions; wired disabled={disableFactions} on faction fields.
Match Result Form Schema & Form
src/components/MatchResultForm/MatchResultForm.schema.ts, src/components/MatchResultForm/MatchResultForm.tsx
Replaced static matchResultFormSchema with getMatchResultFormSchema(gameSystem) that derives details and gameSystemConfig from getGameSystem(gameSystem); updated form to use the dynamic schema for parsing/validation; updated MatchResultSubmitData type to infer from factory return.
Tournament Player Fields
src/components/MatchResultForm/components/TournamentPlayerFields/TournamentPlayerFields.tsx
Added optional tournament prop; changed watched form fields to specific values; added memoized registration lookups; auto-fill logic for player user IDs and factions from registrations (prefill and optionally disable when single faction); adjusted label logic to consider tournament?.useTeams.
Form Validation Utilities
src/utils/validateForm.ts
Changed validation error toast to display a generic message: "Please check the form for errors." instead of echoing the specific field error.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes refactoring changes beyond issue #287 scope: dependency bumps, schema conversion to parameterized function, and generic error messages; these exceed the stated objective of faction auto-fill. Consider separating refactoring changes (schema, dependencies, error handling) into separate PRs to maintain clear scope alignment with issue #287's specific auto-fill requirement.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the PR: auto-filling the faction field on match results when player faction information is known.
Description check ✅ Passed The PR description matches the required template with a resolves reference (#287), but lacks detailed implementation context beyond the issue reference.
Linked Issues check ✅ Passed The code changes successfully implement all requirements from issue #287: auto-fill faction fields from tournament registrations [287], disable when single faction [287], enable when multiple factions [287], and apply to both players [287].
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 287-auto-fill-player-factions

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 (4)
src/components/MatchResultDetailFields/gameSystems/FlamesOfWarV4MatchResultDetailFields/FlamesOfWarV4MatchResultDetailFields.tsx (1)

134-141: ⚠️ Potential issue | 🟡 Minor

Pre-existing: missing key prop in .map() render.

The FormField elements rendered inside the .map() at Line 134 lack a key prop. This will trigger a React warning. While not introduced by this PR, it's worth addressing.

Proposed fix
           {([0, 1] as const).map((i) => (
              <FormField
+               key={i}
                name={`details.scoreOverride.player${i}Score`}
src/components/MatchResultDetailFields/gameSystems/TeamYankeeV2MatchResultDetailFields/TeamYankeeV2MatchResultDetailFields.tsx (1)

134-141: ⚠️ Potential issue | 🟡 Minor

Pre-existing: missing key prop in .map() render — same as FlamesOfWarV4.

Proposed fix
           {([0, 1] as const).map((i) => (
              <FormField
+               key={i}
                name={`details.scoreOverride.player${i}Score`}
src/utils/validateForm.ts (1)

16-22: ⚠️ Potential issue | 🟠 Major

Duplicate toasts: toast.error is called once per validation issue inside the loop.

When a form has multiple validation errors, this fires N identical "Please check the form for errors." toasts. Move the toast call outside the forEach so it fires at most once.

Proposed fix
   if (!result.success) {
     result.error.issues.forEach((issue) => {
       const fieldPath = issue.path.join('.') as Path<TFieldValues>;
       setError(fieldPath, { message: issue.message });
-      toast.error('Error', { description: 'Please check the form for errors.' });
     });
+    toast.error('Error', { description: 'Please check the form for errors.' });
   }
src/components/MatchResultForm/MatchResultForm.schema.ts (1)

32-40: ⚠️ Potential issue | 🟠 Major

When creating a new single match, changing the game system from FlamesOfWarV4 will cause form state to be inconsistent with the UI.

The defaultValues are hardcoded for FlamesOfWarV4. When a user creates a new single match and selects a different game system from the dropdown (line 223), the form's details and gameSystemConfig fields remain set to FlamesOfWarV4 defaults rather than the selected system's defaults. While tournament-initiated matches have their game system updated via setValue (lines 125-126), single matches lack this automatic update when the gameSystem field is manually changed.

The mismatch is caught during validation on submit, but the form state will be out of sync with the rendered UI fields.

🤖 Fix all issues with AI agents
In
`@src/components/MatchResultForm/components/TournamentPlayerFields/TournamentPlayerFields.tsx`:
- Around line 33-34: player0Options and player1Options are being recreated each
render because getCompetitorPlayerOptions returns new arrays, causing dependent
useEffect hooks to re-run; wrap both calls in useMemo using
selectedPairing?.tournamentCompetitor0 and
selectedPairing?.tournamentCompetitor1 as their dependency inputs so the arrays
are stable between renders (similar to how registrations is memoized). Update
the symbols player0Options, player1Options, getCompetitorPlayerOptions and use
the selectedPairing?tournamentCompetitor0/1 dependencies to ensure effects only
run when the underlying competitor changes.
- Around line 30-31: The two long ternary label lines (player0Label and
player1Label) should be simplified: add a small helper function or intermediate
variables (e.g., getPlayerLabel(tournament, selectedPairing, index) or const
comp0Name = selectedPairing?.tournamentCompetitor0?.displayName) and use that to
build the label, referencing tournament.useTeams, selectedPairing, and
tournamentCompetitor0.displayName / tournamentCompetitor1.displayName; replace
the inline nested ternaries with calls to the helper or the intermediate
variables to improve readability.
- Around line 50-55: The auto-fill effect for player0 is overwriting user
choices because useEffect watches player0Faction and resets it; modify the logic
so the effect only auto-sets once when the registration changes: add a local
flag state (e.g., player0FactionAutoFilled) and change the effect to depend on
player0Registration (not player0Faction); if player0Registration?.factions[0]
exists and the autoFilled flag is false, call setValue('details.player0Faction',
player0Registration.factions[0]) and then set the flag true; alternatively, if
you want to prevent edits after auto-fill, set the existing disableFactions prop
when the flag is true so the field becomes read-only.

In `@src/components/MatchResultForm/MatchResultForm.schema.ts`:
- Line 28: The current MatchResultSubmitData alias uses
z.infer<ReturnType<typeof getMatchResultFormSchema>> which collapses
game-system-specific shapes; change the typing so the submit type is tied to the
runtime gameSystem selection — either (A) make getMatchResultFormSchema
generic/overloaded (e.g. getMatchResultFormSchema<TGame extends
GameSystem>(gameSystem: TGame) => z.ZodType<...specific to TGame>) and replace
MatchResultSubmitData with a generic <TGame> alias that infers from
ReturnType<typeof getMatchResultFormSchema<TGame>> so details and
gameSystemConfig stay narrow, or (B) export a generic type alias like
MatchResultSubmitData<TGame extends GameSystem> = z.infer<ReturnType<typeof
getMatchResultFormSchema<TGame>>> and use that at call sites; ensure references
to getMatchResultFormSchema, MatchResultSubmitData, details, and
gameSystemConfig are updated to the new generic/overload form so consumers
retain correct per-game-system types.

In `@src/components/MatchResultForm/MatchResultForm.tsx`:
- Line 237: Inside the MatchResultForm component remove the leftover
commented-out debug line ("{/*
<pre>{JSON.stringify(form.formState.errors)}</pre> */}") so there is no
dead/debug code; update the JSX in MatchResultForm (the component function
MatchResultForm) to delete that comment and ensure formatting/linting remains
valid after removal.
- Around line 133-135: onSubmit currently force-casts selectedGameSystem to
GameSystem when calling getMatchResultFormSchema, which can be undefined; add a
guard before calling getMatchResultFormSchema: read selectedGameSystem from
form.watch('gameSystem') (or the existing selectedGameSystem variable), if it's
undefined set a form error via form.setError (or return early) and do not call
getMatchResultFormSchema; only call getMatchResultFormSchema(selectedGameSystem)
and proceed to validateForm when selectedGameSystem is a defined GameSystem to
avoid passing undefined into getGameSystem.

Comment on lines 33 to 34
const player0Options = getCompetitorPlayerOptions(selectedPairing?.tournamentCompetitor0);
const player1Options = getCompetitorPlayerOptions(selectedPairing?.tournamentCompetitor1);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

player0Options / player1Options are recomputed every render, causing effects to re-evaluate unnecessarily.

getCompetitorPlayerOptions returns a new array reference on each render, so the useEffect hooks listing these as dependencies will re-run every cycle. The guard conditions prevent infinite loops, but wrapping these in useMemo (as you did for the registrations) would be more efficient and idiomatic.

Proposed fix
-  const player0Options = getCompetitorPlayerOptions(selectedPairing?.tournamentCompetitor0);
-  const player1Options = getCompetitorPlayerOptions(selectedPairing?.tournamentCompetitor1);
+  const player0Options = useMemo(
+    () => getCompetitorPlayerOptions(selectedPairing?.tournamentCompetitor0),
+    [selectedPairing?.tournamentCompetitor0],
+  );
+  const player1Options = useMemo(
+    () => getCompetitorPlayerOptions(selectedPairing?.tournamentCompetitor1),
+    [selectedPairing?.tournamentCompetitor1],
+  );

Also applies to: 44-48

🤖 Prompt for AI Agents
In
`@src/components/MatchResultForm/components/TournamentPlayerFields/TournamentPlayerFields.tsx`
around lines 33 - 34, player0Options and player1Options are being recreated each
render because getCompetitorPlayerOptions returns new arrays, causing dependent
useEffect hooks to re-run; wrap both calls in useMemo using
selectedPairing?.tournamentCompetitor0 and
selectedPairing?.tournamentCompetitor1 as their dependency inputs so the arrays
are stable between renders (similar to how registrations is memoized). Update
the symbols player0Options, player1Options, getCompetitorPlayerOptions and use
the selectedPairing?tournamentCompetitor0/1 dependencies to ensure effects only
run when the underlying competitor changes.

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

Caution

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

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

109-122: ⚠️ Potential issue | 🟡 Minor

Unsafe cast matchResult.gameSystem as GameSystem could fail for corrupted data.

If a persisted matchResult has an invalid or missing gameSystem, getMatchResultFormSchema will call getGameSystem(...) with a bad value, potentially throwing during form initialization. A try/catch around the IIFE (or a pre-check) would make this more resilient.

Suggested safeguard
       ...(matchResult ? (() => {
+        try {
           const result = getMatchResultFormSchema(matchResult.gameSystem as GameSystem).safeParse(matchResult);
           if (!result.success) {
             console.error('MatchResultForm schema parsing failed:', result.error);
             console.error('MatchResult data:', matchResult);
           }
           return result.success ? result.data : {};
+        } catch (e) {
+          console.error('Failed to initialize form from matchResult:', e);
+          return {};
+        }
       })() : {}),
🤖 Fix all issues with AI agents
In
`@src/components/MatchResultForm/components/TournamentPlayerFields/TournamentPlayerFields.tsx`:
- Around line 36-41: The useMemo hooks for player0Registration and
player1Registration depend on the whole selectedPairing object which causes
unnecessary recomputations; update the dependency arrays to reference only the
specific competitor/registrations used (e.g., replace selectedPairing in
player0Registration's deps with selectedPairing?.tournamentCompetitor0 or
selectedPairing?.tournamentCompetitor0?.registrations, and similarly use
selectedPairing?.tournamentCompetitor1 or
selectedPairing?.tournamentCompetitor1?.registrations for player1Registration)
so the memo only recalculates when that competitor's registrations or the
playerUserId actually change.
- Around line 50-69: TournamentPlayerFields currently auto-sets factions (using
player0Registration/player1Registration and setValue) but never signals that the
field should be disabled; compute boolean flags like player0DisableFaction =
player0Registration?.factions.length === 1 and player1DisableFaction =
player1Registration?.factions.length === 1 (use the same registration checks you
already have and consider factions.length > 1 to leave enabled) and lift them to
MatchResultForm by either invoking a provided callback or returning them via
props; then ensure MatchResultForm passes those flags as disableFactions to
MatchResultDetailFields so the UI disables the input when exactly one faction is
available while still pre-selecting the first faction via the existing setValue
logic.

…ds/TournamentPlayerFields.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@ianpaschal ianpaschal merged commit 126b299 into main Feb 11, 2026
3 checks passed
@ianpaschal ianpaschal deleted the 287-auto-fill-player-factions branch February 11, 2026 12:56
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