CC-43: Replace Registration Details with List Details#334
Conversation
Fixes CC-2
…istration-details-with-list-details
…istration-details-with-list-details
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR refactors tournament registration workflows to be list-centric rather than details-centric. The backend makes lists structurally flexible (optional storage, optional game-specific metadata), while the frontend shifts registration from simple alignment/faction selection to managing multiple game-system-aware lists. Key changes include introducing 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/MatchResultForm/components/MatchResultPlayerField/MatchResultPlayerField.tsx (1)
132-140:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCSS module keys are stale after component rename.
Line 132 and Line 140 reference
styles.PlayerField*, but the module now definesMatchResultPlayerField*. Those classes resolve toundefined, so this section loses intended styling.Proposed fix
- <div className={styles.PlayerField}> + <div className={styles.MatchResultPlayerField}> ... - <div className={sx({ border: 'top' }, styles.PlayerField_Placeholder)}> + <div className={sx({ border: 'top' }, styles.MatchResultPlayerField_Placeholder)}>🤖 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 `@src/components/MatchResultForm/components/MatchResultPlayerField/MatchResultPlayerField.tsx` around lines 132 - 140, The JSX references stale CSS keys styles.PlayerField and styles.PlayerField_Placeholder after the component rename; update these references to the actual class names exported by the CSS module (e.g., styles.MatchResultPlayerField and styles.MatchResultPlayerField_Placeholder) wherever used in MatchResultPlayerField.tsx (around the UserFinder block and the placeholder div) so the styling is applied again; alternatively, rename the CSS module keys to match PlayerField if you prefer to keep the JSX unchanged—ensure consistency between the CSS module exports and the identifiers used in the MatchResultPlayerField component.src/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/TeamYankeeV2MatchResultPlayerFields/TeamYankeeV2MatchResultPlayerFields.tsx (1)
38-51:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve legacy faction fields when resetting details on battle-plan changes.
This reset path reapplies defaults and restores only battle-plan/units-lost values. For records without
player0ListId/player1ListId, it can silently overwritedetails.player0Faction/details.player1Faction, which breaks the fallback-to-details faction behavior.💡 Suggested patch
reset((prev) => ({ ...prev, details: { ...prev.details, // Reset mission and dependent fields if battle plans were changed: ...matchResultDetails.defaultValues, // Preserve per-player fields (and update the required field): + player0Faction: prev.details.player0Faction, + player1Faction: prev.details.player1Faction, player0BattlePlan: index === 0 ? value as BattlePlan : prev.details.player0BattlePlan, player0UnitsLost: prev.details.player0UnitsLost, player1BattlePlan: index === 1 ? value as BattlePlan : prev.details.player1BattlePlan, player1UnitsLost: prev.details.player1UnitsLost, }, }), { keepDirty: true });🤖 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 `@src/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/TeamYankeeV2MatchResultPlayerFields/TeamYankeeV2MatchResultPlayerFields.tsx` around lines 38 - 51, The reset logic in TeamYankeeV2MatchResultPlayerFields.tsx currently reapplies matchResultDetails.defaultValues and only preserves battle-plan and units-lost fields, which can overwrite legacy per-player faction fields; update the reset((prev) => ({...})) block to also preserve player0Faction and player1Faction when the corresponding player list id is missing (i.e., if prev.details.player0ListId is falsy keep prev.details.player0Faction; likewise for player1), ensuring these legacy faction fields are copied into details alongside player0BattlePlan/player1BattlePlan and player0UnitsLost/player1UnitsLost instead of being clobbered by matchResultDetails.defaultValues.
🤖 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 `@convex/_model/lists/table.ts`:
- Around line 1-5: Replace the locally redefined union validator with the shared
listData Zod validator from convex/_model/common/listData.ts: import listData
and pass zodToConvex(listData) into defineTable (and any other places where the
union was redefined on lines noted), so the table schema uses the single
source-of-truth listData validator instead of duplicating the union logic.
In
`@convex/_model/tournamentRegistrations/_helpers/deepenTournamentRegistration.ts`:
- Around line 43-52: The current derivation uses lists.find((l) => !l.storageId)
which only looks at one "bare" list and ignores any other lists (and loses
metadata if that single list later gets a storageId); instead, scan all lists
for metadata and derive alignment/faction from the first non-null meta value or
collect all meta values across lists. Replace registrationList and
derivedAlignment/derivedFaction with logic that: 1) finds the first
lists[].data?.meta?.alignment (and faction) across all lists (not filtered by
storageId) falling back to details.*, and 2) when building alignments/factions
arrays (alignments, factions) include unique values collected from all lists'
meta fields (and the derived fallback) when alignmentsVisible/factionsVisible is
true so visibleDetails.alignment and visibleDetails.faction reflect that
aggregated result.
In
`@convex/_model/tournamentRegistrations/mutations/createTournamentRegistration.ts`:
- Line 27: The code currently inserts lists using the schema v.array(listData)
without checking that each list's schema matches the tournament.gameSystem;
update the createTournamentRegistration mutation to validate each item in
listData against the tournament.gameSystem before building the insert row
(identify and use the variables/function createTournamentRegistration, listData,
and tournament.gameSystem), rejecting or transforming mismatched entries and
returning a validation error; apply the same validation logic to the other
insertion block referenced around lines 107-114 so all inserted rows are
guaranteed to be compatible with tournament.gameSystem.
- Around line 99-105: The insert into tournamentRegistrations in
createTournamentRegistration.ts is missing the scoreAdjustments field so
args.scoreAdjustments is dropped; include scoreAdjustments:
args.scoreAdjustments in the payload passed to ctx.db.insert (alongside active,
confirmed, tournamentCompetitorId, tournamentId, userId) so the caller-provided
adjustments are persisted when creating the tournamentRegistrationId record.
In `@package.json`:
- Line 35: The package.json currently pins
`@ianpaschal/combat-command-game-systems` to a brittle local tarball via
"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1.6.0.tgz"
which breaks CI (npm ci) because the sibling tarball/dir is not provided; change
the dependency strategy by either (A) switching the dependency to a published
registry version (e.g., "`@ianpaschal/combat-command-game-systems`": "1.6.0") and
update package-lock.json accordingly, or (B) convert the repo to a
workspace/local package reference (workspaces config) or (C) add a CI step in
.github/workflows/ci.yml to fetch or create the ../combat-command-game-systems
tarball before running npm ci and then regenerate lockfile so installs are
reproducible; update package.json and package-lock.json to reflect the chosen
approach and ensure npm ci succeeds.
In `@src/components/ListForm/ListForm.tsx`:
- Around line 70-71: The schema is being chosen from the pre-merge form state
(formData.gameSystem) while forcedValues may override gameSystem; first build
the merged payload (merge({}, formData, forcedValues, { userId: user?._id })),
then call createSchema using payload.gameSystem and pass that same payload into
validateForm (i.e., use the merged payload for both schema selection and
validation) to ensure the correct game-system schema is used; update references
around createSchema, validateForm, merge, formData, forcedValues, user?._id, and
validFormData accordingly.
In
`@src/components/ListMetaFields/gameSystems/TeamYankeeV2ListMetaFields/TeamYankeeV2ListMetaFields.tsx`:
- Around line 43-45: The disabled condition on the Faction FormField is using &&
and should be changed to || so the field is disabled when either forceDiagram is
falsy or there are fewer than 2 factionOptions; update the disabled prop in
TeamYankeeV2ListMetaFields (the FormField with name `${namePrefix}.faction`,
using forceDiagram and factionOptions) to use `||` instead of `&&`, and make the
identical change in FlamesOfWarV4ListMetaFields to keep behavior consistent.
In `@src/components/ListMetaFields/ListMetaFields.tsx`:
- Line 24: ListMetaFields currently watches the wrong form field and throws
during render when gameSystem is undefined; change it to read the gameSystem
from the provided namePrefix scope (watch(`${namePrefix}.gameSystem`) or
equivalent using the namePrefix prop) and remove the render-time throw — instead
render a safe fallback (null or a placeholder) when the scoped gameSystem is
missing or unknown, and only render the matched sub-component when a valid
gameSystem is found; update references inside ListMetaFields to use namePrefix
and the scoped field name rather than the root 'gameSystem'.
In
`@src/components/MatchResultForm/components/MatchResultListField/MatchResultListField.tsx`:
- Around line 72-76: The ListForm is creating lists without attaching the
current registration id, so newly created lists may not appear in
registration.lists; update the ListForm usage to include the registration id in
both forcedValues and the onSubmit payload by adding registration.id to
forcedValues (e.g. forcedValues={{ gameSystem, registrationId: registration.id
}}) and passing that registrationId into createList (onSubmit={(data) =>
createList({ ...data, registrationId: registration.id })}), updating references
to ListForm, forcedValues, createList and registration accordingly so created
lists are scoped to the selected registration.
- Around line 38-45: When the watched player userId (userId) or registration
changes, ensure the previously selected list (player${index}ListId /
selectedListId) is cleared so a list from a prior user cannot be submitted: add
an effect that depends on userId, registration (and optionally
tournamentPairingId/gameSystem) which checks whether selectedListId exists in
the current registration's allLists (registration or allLists) and if not calls
setValue(`player${index}ListId`, undefined) or
resetField(`player${index}ListId`) to clear it; reference the existing watch,
selectedListId, registration, allLists and use setValue/resetField inside that
effect.
In `@src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx`:
- Line 225: The div inside TournamentRegistrationForm that renders each list
item erroneously uses the root prop className (className={className}) and lacks
a React key; update the map render so the per-item div does not reuse the root
className (use a specific item class like itemClassName or a static className
for list items, or remove className from the item and keep className on the root
container) and add a unique key prop (e.g., key={participant.id} or key={index})
to that div in the participants.map (or equivalent) callback to fix
reconciliation issues.
- Around line 224-235: The Lists rendering crashes because ListMetaFields relies
on watch('gameSystem') but the TournamentRegistrationForm defaultValues/schema
don't provide gameSystem, so either add a gameSystem form field (initialize it
from tournament.gameSystem in defaultValues and include it in the form schema)
or stop relying on RHF context by passing tournament.gameSystem as a prop into
ListMetaFields and update ListMetaFields to use that prop instead of
watch('gameSystem'); additionally fix the list map in TournamentRegistrationForm
(the listFields.map block) by adding key={field.id} to the root <div> and stop
reusing the outer form className for each row (use a dedicated list-row
className instead) so styling and React keys are correct.
---
Outside diff comments:
In
`@src/components/MatchResultForm/components/MatchResultPlayerField/MatchResultPlayerField.tsx`:
- Around line 132-140: The JSX references stale CSS keys styles.PlayerField and
styles.PlayerField_Placeholder after the component rename; update these
references to the actual class names exported by the CSS module (e.g.,
styles.MatchResultPlayerField and styles.MatchResultPlayerField_Placeholder)
wherever used in MatchResultPlayerField.tsx (around the UserFinder block and the
placeholder div) so the styling is applied again; alternatively, rename the CSS
module keys to match PlayerField if you prefer to keep the JSX unchanged—ensure
consistency between the CSS module exports and the identifiers used in the
MatchResultPlayerField component.
In
`@src/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/TeamYankeeV2MatchResultPlayerFields/TeamYankeeV2MatchResultPlayerFields.tsx`:
- Around line 38-51: The reset logic in TeamYankeeV2MatchResultPlayerFields.tsx
currently reapplies matchResultDetails.defaultValues and only preserves
battle-plan and units-lost fields, which can overwrite legacy per-player faction
fields; update the reset((prev) => ({...})) block to also preserve
player0Faction and player1Faction when the corresponding player list id is
missing (i.e., if prev.details.player0ListId is falsy keep
prev.details.player0Faction; likewise for player1), ensuring these legacy
faction fields are copied into details alongside
player0BattlePlan/player1BattlePlan and player0UnitsLost/player1UnitsLost
instead of being clobbered by matchResultDetails.defaultValues.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f5fe83fe-cd20-4951-a3af-ab3b48db4729
⛔ Files ignored due to path filters (2)
convex/_generated/api.d.tsis excluded by!**/_generated/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (53)
convex/_model/_test/mutations/cleanUp.tsconvex/_model/common/listData.tsconvex/_model/lists/_helpers/checkListApproved.tsconvex/_model/lists/_helpers/checkListSubmittedOnTime.tsconvex/_model/lists/_helpers/deepenList.tsconvex/_model/lists/index.tsconvex/_model/lists/mutations/createList.tsconvex/_model/lists/mutations/updateList.tsconvex/_model/lists/table.tsconvex/_model/matchResults/_helpers/deepenMatchResult.tsconvex/_model/tournamentCompetitors/index.tsconvex/_model/tournamentCompetitors/mutations/createTournamentCompetitor.tsconvex/_model/tournamentRegistrations/_helpers/deepenTournamentRegistration.tsconvex/_model/tournamentRegistrations/mutations/createTournamentRegistration.tsconvex/_model/tournamentRegistrations/table.tsconvex/_model/tournaments/table.tsconvex/migrations.tsconvex/tournamentCompetitors.tspackage.jsonsrc/components/ListDetails/ListDetails.tsxsrc/components/ListForm/ListForm.schema.tssrc/components/ListForm/ListForm.tsxsrc/components/ListMetaFields/ListMetaFields.schema.tssrc/components/ListMetaFields/ListMetaFields.tsxsrc/components/ListMetaFields/gameSystems/FlamesOfWarV4ListMetaFields/FlamesOfWarV4ListMetaFields.tsxsrc/components/ListMetaFields/gameSystems/FlamesOfWarV4ListMetaFields/FlamesOfWarV4ListMetaFields.types.tssrc/components/ListMetaFields/gameSystems/FlamesOfWarV4ListMetaFields/index.tssrc/components/ListMetaFields/gameSystems/TeamYankeeV2ListMetaFields/TeamYankeeV2ListMetaFields.tsxsrc/components/ListMetaFields/gameSystems/TeamYankeeV2ListMetaFields/TeamYankeeV2ListMetaFields.types.tssrc/components/ListMetaFields/gameSystems/TeamYankeeV2ListMetaFields/index.tssrc/components/ListMetaFields/index.tssrc/components/MatchResultForm/MatchResultForm.schema.tssrc/components/MatchResultForm/components/MatchResultListField/MatchResultListField.module.scsssrc/components/MatchResultForm/components/MatchResultListField/MatchResultListField.tsxsrc/components/MatchResultForm/components/MatchResultListField/index.tssrc/components/MatchResultForm/components/MatchResultPlayerField/MatchResultPlayerField.module.scsssrc/components/MatchResultForm/components/MatchResultPlayerField/MatchResultPlayerField.tsxsrc/components/MatchResultForm/components/MatchResultPlayerField/index.tssrc/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/FlamesOfWarV4MatchResultPlayerFields/FlamesOfWarV4MatchResultPlayerFields.tsxsrc/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/FlamesOfWarV4MatchResultPlayerFields/FlamesOfWarV4MatchResultPlayerFields.types.tssrc/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/TeamYankeeV2MatchResultPlayerFields/TeamYankeeV2MatchResultPlayerFields.tsxsrc/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/TeamYankeeV2MatchResultPlayerFields/TeamYankeeV2MatchResultPlayerFields.types.tssrc/components/MatchResultForm/components/PlayerField/index.tssrc/components/TournamentForm/TournamentForm.schema.tssrc/components/TournamentForm/components/CompetitorFields.tsxsrc/components/TournamentForm/components/PairingFields.tsxsrc/components/TournamentRegistrationForm/TournamentRegistrationForm.schema.tssrc/components/TournamentRegistrationForm/TournamentRegistrationForm.tsxsrc/components/TournamentRegistrationForm/components/DetailsFields/DetailsFields.module.scsssrc/components/TournamentRegistrationForm/components/DetailsFields/DetailsFields.tsxsrc/components/TournamentRegistrationForm/components/DetailsFields/index.tssrc/components/TournamentRegistrationProvider/actions/useCreateListAction.tsxsrc/services/tournamentCompetitors.ts
💤 Files with no reviewable changes (9)
- src/components/MatchResultForm/components/PlayerField/index.ts
- src/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/FlamesOfWarV4MatchResultPlayerFields/FlamesOfWarV4MatchResultPlayerFields.types.ts
- convex/tournamentCompetitors.ts
- src/components/TournamentRegistrationForm/components/DetailsFields/index.ts
- convex/_model/tournamentCompetitors/mutations/createTournamentCompetitor.ts
- src/components/TournamentRegistrationForm/components/DetailsFields/DetailsFields.tsx
- src/components/TournamentRegistrationForm/components/DetailsFields/DetailsFields.module.scss
- src/components/MatchResultForm/components/MatchResultPlayerFields/gameSystems/TeamYankeeV2MatchResultPlayerFields/TeamYankeeV2MatchResultPlayerFields.types.ts
- convex/_model/tournamentCompetitors/index.ts
| import { FlamesOfWarV4, TeamYankeeV2 } from '@ianpaschal/combat-command-game-systems'; | ||
| import { GameSystem } from '@ianpaschal/combat-command-game-systems/common'; | ||
| import { defineTable } from 'convex/server'; | ||
| import { v } from 'convex/values'; | ||
| import { zodToConvex } from 'convex-helpers/server/zod'; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use the shared listData validator instead of redefining the union here.
This duplicates the schema contract and can drift from convex/_model/common/listData.ts. Importing the shared validator keeps one source of truth.
♻️ Proposed refactor
-import { FlamesOfWarV4, TeamYankeeV2 } from '`@ianpaschal/combat-command-game-systems`';
import { GameSystem } from '`@ianpaschal/combat-command-game-systems/common`';
import { defineTable } from 'convex/server';
import { v } from 'convex/values';
-import { zodToConvex } from 'convex-helpers/server/zod';
+import { listData } from '../common/listData';
@@
-const listDataValidator = v.union(
- zodToConvex(FlamesOfWarV4.listData.schema),
- zodToConvex(TeamYankeeV2.listData.schema),
-);
-
export const editableFields = {
gameSystem,
storageId: v.optional(v.id('_storage')),
userId: v.id('users'),
tournamentRegistrationId: v.optional(v.id('tournamentRegistrations')),
- data: v.optional(listDataValidator),
+ data: v.optional(listData),
};Also applies to: 11-14, 21-21
🤖 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 `@convex/_model/lists/table.ts` around lines 1 - 5, Replace the locally
redefined union validator with the shared listData Zod validator from
convex/_model/common/listData.ts: import listData and pass zodToConvex(listData)
into defineTable (and any other places where the union was redefined on lines
noted), so the table schema uses the single source-of-truth listData validator
instead of duplicating the union logic.
| const registrationList = lists.find((l) => !l.storageId); | ||
| const derivedAlignment = registrationList?.data?.meta?.alignment ?? details?.alignment ?? null; | ||
| const derivedFaction = registrationList?.data?.meta?.faction ?? details?.faction ?? null; | ||
|
|
||
| const alignments = Array.from(new Set(alignmentsVisible && derivedAlignment ? [derivedAlignment] : [])); | ||
| const factions = Array.from(new Set(factionsVisible && derivedFaction ? [derivedFaction] : [])); | ||
|
|
||
| const visibleDetails = { | ||
| alignment: alignmentsVisible ? details?.alignment ?? null : null, | ||
| faction: factionsVisible ? details?.faction ?? null : null, | ||
| alignment: alignmentsVisible ? derivedAlignment : null, | ||
| faction: factionsVisible ? derivedFaction : null, |
There was a problem hiding this comment.
Derivation logic ignores uploaded lists and additional lists.
Using lists.find((l) => !l.storageId) restricts derivation to one bare list. If that list is later updated with a storageId, alignment/faction can become null even when metadata exists, and any additional lists are ignored.
🔧 Suggested fix
- const registrationList = lists.find((l) => !l.storageId);
- const derivedAlignment = registrationList?.data?.meta?.alignment ?? details?.alignment ?? null;
- const derivedFaction = registrationList?.data?.meta?.faction ?? details?.faction ?? null;
-
- const alignments = Array.from(new Set(alignmentsVisible && derivedAlignment ? [derivedAlignment] : []));
- const factions = Array.from(new Set(factionsVisible && derivedFaction ? [derivedFaction] : []));
+ const listAlignments = lists
+ .map((l) => l.data?.meta?.alignment)
+ .filter((a): a is NonNullable<typeof a> => Boolean(a));
+ const listFactions = lists
+ .map((l) => l.data?.meta?.faction)
+ .filter((f): f is NonNullable<typeof f> => Boolean(f));
+
+ const alignments = Array.from(
+ new Set(alignmentsVisible ? (listAlignments.length ? listAlignments : details?.alignment ? [details.alignment] : []) : []),
+ );
+ const factions = Array.from(
+ new Set(factionsVisible ? (listFactions.length ? listFactions : details?.faction ? [details.faction] : []) : []),
+ );
+
+ const derivedAlignment = alignments[0] ?? null;
+ const derivedFaction = factions[0] ?? null;📝 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.
| const registrationList = lists.find((l) => !l.storageId); | |
| const derivedAlignment = registrationList?.data?.meta?.alignment ?? details?.alignment ?? null; | |
| const derivedFaction = registrationList?.data?.meta?.faction ?? details?.faction ?? null; | |
| const alignments = Array.from(new Set(alignmentsVisible && derivedAlignment ? [derivedAlignment] : [])); | |
| const factions = Array.from(new Set(factionsVisible && derivedFaction ? [derivedFaction] : [])); | |
| const visibleDetails = { | |
| alignment: alignmentsVisible ? details?.alignment ?? null : null, | |
| faction: factionsVisible ? details?.faction ?? null : null, | |
| alignment: alignmentsVisible ? derivedAlignment : null, | |
| faction: factionsVisible ? derivedFaction : null, | |
| const listAlignments = lists | |
| .map((l) => l.data?.meta?.alignment) | |
| .filter((a): a is NonNullable<typeof a> => Boolean(a)); | |
| const listFactions = lists | |
| .map((l) => l.data?.meta?.faction) | |
| .filter((f): f is NonNullable<typeof f> => Boolean(f)); | |
| const alignments = Array.from( | |
| new Set(alignmentsVisible ? (listAlignments.length ? listAlignments : details?.alignment ? [details.alignment] : []) : []), | |
| ); | |
| const factions = Array.from( | |
| new Set(factionsVisible ? (listFactions.length ? listFactions : details?.faction ? [details.faction] : []) : []), | |
| ); | |
| const derivedAlignment = alignments[0] ?? null; | |
| const derivedFaction = factions[0] ?? null; | |
| const visibleDetails = { | |
| alignment: alignmentsVisible ? derivedAlignment : null, | |
| faction: factionsVisible ? derivedFaction : null, |
🤖 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
`@convex/_model/tournamentRegistrations/_helpers/deepenTournamentRegistration.ts`
around lines 43 - 52, The current derivation uses lists.find((l) =>
!l.storageId) which only looks at one "bare" list and ignores any other lists
(and loses metadata if that single list later gets a storageId); instead, scan
all lists for metadata and derive alignment/faction from the first non-null meta
value or collect all meta values across lists. Replace registrationList and
derivedAlignment/derivedFaction with logic that: 1) finds the first
lists[].data?.meta?.alignment (and faction) across all lists (not filtered by
storageId) falling back to details.*, and 2) when building alignments/factions
arrays (alignments, factions) include unique values collected from all lists'
meta fields (and the derived fallback) when alignmentsVisible/factionsVisible is
true so visibleDetails.alignment and visibleDetails.faction reflect that
aggregated result.
| })), | ||
| nameVisibilityConsent: v.optional(v.boolean()), | ||
| scoreAdjustments: v.optional(v.array(scoreAdjustment)), | ||
| lists: v.array(listData), |
There was a problem hiding this comment.
Validate each list payload against tournament.gameSystem before insert.
listData accepts multiple game-system schemas, but inserted rows are always stamped with tournament.gameSystem. Without a compatibility check, this can persist mismatched list metadata under the wrong game system.
🔧 Suggested fix
+ // Validate list payloads are compatible with this tournament's game system.
+ const helpers = getGameSystem(tournament.gameSystem);
+
for (const data of args.lists) {
+ if (!helpers.listData.schema.safeParse(data).success) {
+ throw new ConvexError(getErrorMessage('LIST_DATA_GAME_SYSTEM_MISMATCH'));
+ }
+
await ctx.db.insert('lists', {
gameSystem: tournament.gameSystem,
userId: args.userId,
tournamentRegistrationId,
locked: false,
data,
});
}Also applies to: 107-114
🤖 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
`@convex/_model/tournamentRegistrations/mutations/createTournamentRegistration.ts`
at line 27, The code currently inserts lists using the schema v.array(listData)
without checking that each list's schema matches the tournament.gameSystem;
update the createTournamentRegistration mutation to validate each item in
listData against the tournament.gameSystem before building the insert row
(identify and use the variables/function createTournamentRegistration, listData,
and tournament.gameSystem), rejecting or transforming mismatched entries and
returning a validation error; apply the same validation logic to the other
insertion block referenced around lines 107-114 so all inserted rows are
guaranteed to be compatible with tournament.gameSystem.
| const tournamentRegistrationId = await ctx.db.insert('tournamentRegistrations', { | ||
| active: activePlayerCount < tournament.competitorSize, | ||
| confirmed: args.userId === currentUserId, | ||
| listApproved: false, | ||
| tournamentCompetitorId, | ||
| tournamentId: args.tournamentId, | ||
| userId: args.userId, | ||
| details: args.details, | ||
| }); |
There was a problem hiding this comment.
Persist scoreAdjustments on registration creation.
scoreAdjustments is accepted in the args schema but omitted from the tournamentRegistrations insert payload, so caller input is silently dropped.
🔧 Suggested fix
const tournamentRegistrationId = await ctx.db.insert('tournamentRegistrations', {
active: activePlayerCount < tournament.competitorSize,
confirmed: args.userId === currentUserId,
tournamentCompetitorId,
tournamentId: args.tournamentId,
userId: args.userId,
+ scoreAdjustments: args.scoreAdjustments,
});📝 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.
| const tournamentRegistrationId = await ctx.db.insert('tournamentRegistrations', { | |
| active: activePlayerCount < tournament.competitorSize, | |
| confirmed: args.userId === currentUserId, | |
| listApproved: false, | |
| tournamentCompetitorId, | |
| tournamentId: args.tournamentId, | |
| userId: args.userId, | |
| details: args.details, | |
| }); | |
| const tournamentRegistrationId = await ctx.db.insert('tournamentRegistrations', { | |
| active: activePlayerCount < tournament.competitorSize, | |
| confirmed: args.userId === currentUserId, | |
| tournamentCompetitorId, | |
| tournamentId: args.tournamentId, | |
| userId: args.userId, | |
| scoreAdjustments: args.scoreAdjustments, | |
| }); |
🤖 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
`@convex/_model/tournamentRegistrations/mutations/createTournamentRegistration.ts`
around lines 99 - 105, The insert into tournamentRegistrations in
createTournamentRegistration.ts is missing the scoreAdjustments field so
args.scoreAdjustments is dropped; include scoreAdjustments:
args.scoreAdjustments in the payload passed to ctx.db.insert (alongside active,
confirmed, tournamentCompetitorId, tournamentId, userId) so the caller-provided
adjustments are persisted when creating the tournamentRegistrationId record.
| "@hookform/resolvers": "^3.9.0", | ||
| "@ianpaschal/combat-command-components": "^2.1.1", | ||
| "@ianpaschal/combat-command-game-systems": "^1.6.0", | ||
| "@ianpaschal/combat-command-game-systems": "file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1.6.0.tgz", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Verify whether the referenced sibling tarball exists in this environment:"
ls -l ../combat-command-game-systems/ianpaschal-combat-command-game-systems-1.6.0.tgz
echo
echo "2) Check CI/workflows for install steps that would fail without that sibling path:"
if [ -d .github/workflows ]; then
rg -n -C2 'npm (ci|install)|pnpm install|yarn install' .github/workflows
else
echo ".github/workflows not found"
fiRepository: ianpaschal/combat-command
Length of output: 291
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package.json context =="
nl -ba package.json | sed -n '25,50p'
echo
echo "== Check for referenced tarball/dir mentions in repo =="
rg -n --hidden -S "combat-command-game-systems-1\.6\.0\.tgz|../combat-command-game-systems|`@ianpaschal/combat-command-game-systems`" .
echo
echo "== Check CI/workflows for install steps/provisioning =="
if [ -d .github/workflows ]; then
rg -n --hidden -S 'combat-command-game-systems|1\.6\.0\.tgz|file:../|npm (ci|install)|pnpm install|yarn install' .github/workflows || true
else
echo ".github/workflows not found"
fi
echo
echo "== Does the sibling dir exist in this environment? =="
ls -ld ../combat-command-game-systems 2>/dev/null || echo "sibling dir missing"Repository: ianpaschal/combat-command
Length of output: 157
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package.json context =="
cat -n package.json | sed -n '25,50p'
echo
echo "== Check for referenced tarball/dir mentions in repo =="
rg -n --hidden -S "combat-command-game-systems-1\.6\.0\.tgz|../combat-command-game-systems|`@ianpaschal/combat-command-game-systems`" .
echo
echo "== Check CI/workflows for install steps/provisioning =="
if [ -d .github/workflows ]; then
rg -n --hidden -S 'combat-command-game-systems|1\.6\.0\.tgz|file:../|../combat-command-game-systems|npm (ci|install)|pnpm install|yarn install' .github/workflows || true
else
echo ".github/workflows not found"
fi
echo
echo "== Does the sibling dir exist in this environment? =="
ls -ld ../combat-command-game-systems 2>/dev/null || echo "sibling dir missing"Repository: ianpaschal/combat-command
Length of output: 21260
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== .github/workflows/ci.yml =="
cat .github/workflows/ci.yml
echo
echo "== Search for referenced tarball filename =="
rg -n --hidden -S "ianpaschal-combat-command-game-systems-1\.6\.0\.tgz|../combat-command-game-systems" .
echo
echo "== package-lock resolved entry =="
rg -n --hidden -S "\"`@ianpaschal/combat-command-game-systems`\": \\{" -n package-lock.json
rg -n --hidden -S "resolved\": \"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1\.6\.0\.tgz" package-lock.jsonRepository: ianpaschal/combat-command
Length of output: 22223
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package-lock.json top-level reference =="
rg -n "\"`@ianpaschal/combat-command-game-systems`\": \"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1\\.6\\.0\\.tgz\"" package-lock.json || true
echo
echo "== package-lock.json packages resolved entry (near node_modules/@ianpaschal/combat-command-game-systems) =="
rg -n "node_modules/@ianpaschal/combat-command-game-systems" package-lock.json || true
# Show a small window around the first match for the resolved entry
line=$(rg -n "resolved\": \"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1\\.6\\.0\\.tgz" package-lock.json | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-5)); end=$((line+10))
sed -n "${start},${end}p" package-lock.json | cat
else
echo "No exact resolved entry match found via rg; showing any nearby file:../combat-command-game-systems matches:"
rg -n "resolved\": \"file:../combat-command-game-systems" package-lock.json || true
fi
echo
echo "== Any CI/provisioning mention of ../combat-command-game-systems =="
rg -n --hidden -S "combat-command-game-systems|file:../combat-command-game-systems" .github/workflows || trueRepository: ianpaschal/combat-command
Length of output: 1273
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package-lock.json top-level reference =="
rg -n "\"`@ianpaschal/combat-command-game-systems`\": \"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1\\.6\\.0\\.tgz\"" package-lock.json || true
echo
echo "== package-lock.json packages resolved entry (near node_modules/@ianpaschal/combat-command-game-systems) =="
rg -n "node_modules/@ianpaschal/combat-command-game-systems" package-lock.json || true
# Show a small window around the first match for the resolved entry
line=$(rg -n "resolved\": \"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1\\.6\\.0\\.tgz" package-lock.json | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-5)); end=$((line+10))
sed -n "${start},${end}p" package-lock.json | cat
else
echo "No exact resolved entry match found via rg; showing any nearby file:../combat-command-game-systems matches:"
rg -n "resolved\": \"file:../combat-command-game-systems" package-lock.json || true
fi
echo
echo "== Any CI/provisioning mention of ../combat-command-game-systems =="
rg -n --hidden -S "combat-command-game-systems|file:../combat-command-game-systems" .github/workflows || trueRepository: ianpaschal/combat-command
Length of output: 1273
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package-lock.json top-level reference =="
rg -n "\"`@ianpaschal/combat-command-game-systems`\": \"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1\\.6\\.0\\.tgz\"" package-lock.json || true
echo
echo "== package-lock.json packages resolved entry (near node_modules/@ianpaschal/combat-command-game-systems) =="
rg -n "node_modules/@ianpaschal/combat-command-game-systems" package-lock.json || true
line=$(rg -n "resolved\": \"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1\\.6\\.0\\.tgz" package-lock.json | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-5)); end=$((line+10))
echo
echo "== package-lock.json window around resolved entry =="
sed -n "${start},${end}p" package-lock.json | cat
else
echo
echo "No exact resolved entry match found via rg; showing any nearby file:../combat-command-game-systems matches:"
rg -n "resolved\": \"file:../combat-command-game-systems" package-lock.json || true
fi
echo
echo "== Any CI/provisioning mention of ../combat-command-game-systems =="
rg -n --hidden -S "combat-command-game-systems|file:../combat-command-game-systems" .github/workflows || trueRepository: ianpaschal/combat-command
Length of output: 1327
🌐 Web query:
@ianpaschal/combat-command-game-systems 1.6.0 npm
💡 Result:
I couldn’t find any authoritative source confirming that the npm package @ianpaschal/combat-command-game-systems exists or that version 1.6.0 is published. What I did find: the GitHub account Ian Paschal has a repository named combat-command-game-systems, but the search results I retrieved did not include the corresponding npm registry page/version details needed to answer your exact query (npm + 1.6.0) with evidence. If you want, paste the npm link (or the package’s npm registry URL) and I’ll verify 1.6.0 contents, release notes, and installation instructions against the official registry and/or repo tags.
Citations:
- 1: https://github.com/ianpaschal
- 2: https://devdojo.com/post/tnylea/best-5-npm-command-line-games
- 3: https://knightsandmerchants.net/forum/viewtopic.php?t=3769
- 4: https://github.com/Vishal-sys-code/Command-Line-Game
Fix brittle out-of-repo file: dependency for @ianpaschal/combat-command-game-systems (CI will break).
package.jsonline 35 andpackage-lock.jsonresolve@ianpaschal/combat-command-game-systemsfromfile:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1.6.0.tgz..github/workflows/ci.ymlrunsnpm ciwith no step that creates/provides that../combat-command-game-systemssibling tarball/dir.- The referenced tarball/sibling dir is missing in this environment (so installs won’t be reproducible).
Update the dependency strategy so installs don’t depend on a pre-provisioned sibling directory (e.g., use a registry-published version/workspace, or add a CI step to fetch/vendor the tarball before npm ci, and regenerate the lockfile).
🤖 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 `@package.json` at line 35, The package.json currently pins
`@ianpaschal/combat-command-game-systems` to a brittle local tarball via
"file:../combat-command-game-systems/ianpaschal-combat-command-game-systems-1.6.0.tgz"
which breaks CI (npm ci) because the sibling tarball/dir is not provided; change
the dependency strategy by either (A) switching the dependency to a published
registry version (e.g., "`@ianpaschal/combat-command-game-systems`": "1.6.0") and
update package-lock.json accordingly, or (B) convert the repo to a
workspace/local package reference (workspaces config) or (C) add a CI step in
.github/workflows/ci.yml to fetch or create the ../combat-command-game-systems
tarball before running npm ci and then regenerate lockfile so installs are
reproducible; update package.json and package-lock.json to reflect the chosen
approach and ensure npm ci succeeds.
| return <TeamYankeeV2ListMetaFields {...props} />; | ||
| } | ||
|
|
||
| throw new Error(`Could not find <ListMetaFields/> for game system ${gameSystem}`); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify where ListMetaFields is used and whether gameSystem is always present in those form shapes.
rg -n -C3 '\b<ListMetaFields\b' --type=tsx
rg -n -C3 '\bgameSystem\b' src/components/ListForm/ListForm.schema.ts src/components/TournamentRegistrationForm/TournamentRegistrationForm.schema.ts src/components/MatchResultForm/MatchResultForm.schema.tsRepository: ianpaschal/combat-command
Length of output: 5296
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print ListMetaFields around the throw
ls -la src/components/ListMetaFields/ListMetaFields.tsx
nl -ba src/components/ListMetaFields/ListMetaFields.tsx | sed -n '1,120p'
# Search for ListMetaFields usage and capture surrounding lines
rg -n -C3 "ListMetaFields" src -g"*.tsx" -g"*.ts"
# Find where gameSystem prop is passed into ListMetaFields
rg -n -C3 "ListMetaFields[^\\n]*gameSystem" src -g"*.tsx" -g"*.ts"
rg -n -C3 "<ListMetaFields[^>]*gameSystem" src -g"*.tsx" -g"*.ts"
# Also search for prop definitions in ListMetaFields
rg -n "export (type|interface) Props|interface Props|type Props|gameSystem" src/components/ListMetaFields/ListMetaFields.tsxRepository: ianpaschal/combat-command
Length of output: 227
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print ListMetaFields around the throw
ls -la src/components/ListMetaFields/ListMetaFields.tsx
nl -ba src/components/ListMetaFields/ListMetaFields.tsx | sed -n '1,160p'
# Search for ListMetaFields usage and capture surrounding lines
rg -n -C3 "ListMetaFields" src -g"*.tsx" -g"*.ts"
# Find where gameSystem prop is passed into ListMetaFields
rg -n -C3 "ListMetaFields[^\n]*gameSystem" src -g"*.tsx" -g"*.ts" || true
rg -n -C3 "<ListMetaFields[^>]*gameSystem" src -g"*.tsx" -g"*.ts" || true
# Also search for prop definitions in ListMetaFields
rg -n "export (type|interface) Props|interface Props|type Props|gameSystem" src/components/ListMetaFields/ListMetaFields.tsxRepository: ianpaschal/combat-command
Length of output: 227
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect ListMetaFields.tsx (line-numbered via cat -n)
sed -n '1,200p' src/components/ListMetaFields/ListMetaFields.tsx
echo "----"
cat -n src/components/ListMetaFields/ListMetaFields.tsx | sed -n '1,200p'
# Find all ListMetaFields usage
rg -n "ListMetaFields" src -g"*.tsx" -g"*.ts"
# Find call sites passing gameSystem prop into ListMetaFields
rg -n "<ListMetaFields[^>]*gameSystem=" src -g"*.tsx"
rg -n "gameSystem={[^}]*}[^>]*>\\s*</?ListMetaFields" src -g"*.tsx" || true
rg -n "<ListMetaFields[^>]*\\bgameSystem\\b" src -g"*.tsx"
# Show ListMetaFields props/type exports (if any)
rg -n "gameSystem" src/components/ListMetaFields/ListMetaFields.tsxRepository: ianpaschal/combat-command
Length of output: 5398
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect call sites to see how RHF is initialized and what field path holds gameSystem.
# Print relevant sections around ListMetaFields usage in ListForm.tsx and TournamentRegistrationForm.tsx.
sed -n '1,220p' src/components/ListForm/ListForm.tsx
echo "----"
sed -n '220,420p' src/components/ListForm/ListForm.tsx
echo "==== TournamentRegistrationForm.tsx ===="
sed -n '1,280p' src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx
echo "----"
sed -n '280,520p' src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx
# Inspect ListForm.schema and TournamentRegistrationForm.schema around gameSystem/defaults.
echo "==== ListForm.schema.ts ===="
sed -n '1,220p' src/components/ListForm/ListForm.schema.ts
echo "==== TournamentRegistrationForm.schema.ts ===="
sed -n '1,260p' src/components/TournamentRegistrationForm/TournamentRegistrationForm.schema.ts
# Inspect ListMetaFields.schema (if it defines/validates gameSystem naming).
echo "==== ListMetaFields.schema.ts ===="
sed -n '1,260p' src/components/ListMetaFields/ListMetaFields.schema.ts
# Inspect game system meta field components to see how they use namePrefix and where gameSystem lives.
echo "==== FlamesOfWarV4ListMetaFields.tsx ===="
sed -n '1,260p' src/components/ListMetaFields/gameSystems/FlamesOfWarV4ListMetaFields/FlamesOfWarV4ListMetaFields.tsx
echo "==== TeamYankeeV2ListMetaFields.tsx ===="
sed -n '1,260p' src/components/ListMetaFields/gameSystems/TeamYankeeV2ListMetaFields/TeamYankeeV2ListMetaFields.tsx
# Search for how namePrefix is used to build field names, and whether those components reference gameSystem.
rg -n "\\bnamePrefix\\b|watch\\(|useWatch\\(|\\bgameSystem\\b" src/components/ListMetaFields/gameSystems -g"*.ts*" -C2Repository: ianpaschal/combat-command
Length of output: 33240
Avoid render-time throw for missing/unknown gameSystem in ListMetaFields; render a safe fallback and select the gameSystem field using the namePrefix scope.
In src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx, ListMetaFields is rendered per list with namePrefix={lists.${index}.meta}, but ListMetaFields decides which sub-component to render using watch('gameSystem') (form root). That field isn’t defined at the tournament form root, so the current code can hit the render-time throw and crash the form tree.
throw new Error(`Could not find <ListMetaFields/> for game system ${gameSystem}`);🤖 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 `@src/components/ListMetaFields/ListMetaFields.tsx` at line 24, ListMetaFields
currently watches the wrong form field and throws during render when gameSystem
is undefined; change it to read the gameSystem from the provided namePrefix
scope (watch(`${namePrefix}.gameSystem`) or equivalent using the namePrefix
prop) and remove the render-time throw — instead render a safe fallback (null or
a placeholder) when the scoped gameSystem is missing or unknown, and only render
the matched sub-component when a valid gameSystem is found; update references
inside ListMetaFields to use namePrefix and the scoped field name rather than
the root 'gameSystem'.
| const userId = watch(`player${index}UserId`); | ||
| const tournamentPairingId = watch('tournamentPairingId'); | ||
| const gameSystem = watch('gameSystem'); | ||
| const selectedListId = watch(`player${index}ListId`); | ||
|
|
||
| const competitor = tournamentPairing?.[`tournamentCompetitor${index}`]; | ||
| const registration = (competitor?.registrations ?? []).find((r) => r.userId === userId); | ||
| const allLists = registration?.lists ?? []; |
There was a problem hiding this comment.
Clear stale list selection when player/registration changes.
player{index}ListId is never reset when player{index}UserId changes. A list from a previous user can remain selected and be submitted.
Proposed fix
+import { MouseEvent, useEffect } from 'react';
...
const selectedList = allLists.find((l) => l._id === selectedListId) ?? null;
+
+ useEffect(() => {
+ if (selectedListId && !selectedList) {
+ setValue(`player${index}ListId`, null, { shouldDirty: true });
+ }
+ }, [selectedListId, selectedList, setValue, index]);Also applies to: 52-53
🤖 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
`@src/components/MatchResultForm/components/MatchResultListField/MatchResultListField.tsx`
around lines 38 - 45, When the watched player userId (userId) or registration
changes, ensure the previously selected list (player${index}ListId /
selectedListId) is cleared so a list from a prior user cannot be submitted: add
an effect that depends on userId, registration (and optionally
tournamentPairingId/gameSystem) which checks whether selectedListId exists in
the current registration's allLists (registration or allLists) and if not calls
setValue(`player${index}ListId`, undefined) or
resetField(`player${index}ListId`) to clear it; reference the existing watch,
selectedListId, registration, allLists and use setValue/resetField inside that
effect.
| <ListForm | ||
| hideFileUpload | ||
| forcedValues={{ gameSystem }} | ||
| onSubmit={(data) => createList(data)} | ||
| /> |
There was a problem hiding this comment.
Scope created lists to the selected registration.
Line 72–76 creates a list without passing the current registration id, but Line 44–47/52 only read lists from registration.lists. This can leave newly created lists outside the selectable/editable set for this field.
Proposed fix
<ListForm
hideFileUpload
- forcedValues={{ gameSystem }}
+ forcedValues={{
+ gameSystem,
+ tournamentRegistrationId: registration?._id,
+ }}
onSubmit={(data) => createList(data)}
/>Also applies to: 44-47, 52-52
🤖 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
`@src/components/MatchResultForm/components/MatchResultListField/MatchResultListField.tsx`
around lines 72 - 76, The ListForm is creating lists without attaching the
current registration id, so newly created lists may not appear in
registration.lists; update the ListForm usage to include the registration id in
both forcedValues and the onSubmit payload by adding registration.id to
forcedValues (e.g. forcedValues={{ gameSystem, registrationId: registration.id
}}) and passing that registrationId into createList (onSubmit={(data) =>
createList({ ...data, registrationId: registration.id })}), updating references
to ListForm, forcedValues, createList and registration accordingly so created
lists are scoped to the selected registration.
| {listFields.map((field, index) => ( | ||
| <div className={className}> | ||
| <ListMetaFields namePrefix={`lists.${index}.meta`} /> | ||
| <Button | ||
| disabled={disabled} | ||
| icon={<Trash />} | ||
| intent="danger" | ||
| variant="ghost" | ||
| onClick={(e) => handleRemoveList(index, e)} | ||
| /> | ||
| </div> | ||
| ))} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify ListMetaFields watches gameSystem from form context
ast-grep --pattern $'const { watch } = useFormContext();
$$$
const gameSystem = watch($$$);'Repository: ianpaschal/combat-command
Length of output: 51
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Locate ListMetaFields and inspect watch('gameSystem') usage
LIST_META_FILE=$(fd -a "ListMetaFields.tsx" .)
echo "ListMetaFields file(s):"
echo "$LIST_META_FILE"
echo "---- ListMetaFields: lines around watch/useFormContext ----"
rg -n "useFormContext|watch\(" "$LIST_META_FILE" || true
# print a small window around the gameSystem watcher if present
if rg -n "gameSystem" "$LIST_META_FILE" >/dev/null; then
GAME_LINE=$(rg -n "gameSystem" "$LIST_META_FILE" | head -n1 | cut -d: -f1)
START=$((GAME_LINE-10))
END=$((GAME_LINE+15))
sed -n "${START},${END}p" "$LIST_META_FILE" | nl -ba -w3 -s':'
fi
# 2) Inspect TournamentRegistrationForm: look for gameSystem in schema/defaults/fields
FORM_FILE="src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx"
if [ ! -f "$FORM_FILE" ]; then
echo "Missing expected file: $FORM_FILE" >&2
exit 1
fi
echo "---- TournamentRegistrationForm: occurrences of gameSystem ----"
rg -n "gameSystem" "$FORM_FILE" || true
# Print likely schema/defaults area (first 260 lines to cover new field array work)
echo "---- TournamentRegistrationForm: first 280 lines (context for schema/defaults) ----"
sed -n '1,280p' "$FORM_FILE" | nl -ba -w3 -s':'
# 3) Inspect the specific lists rendering to confirm key/className
echo "---- TournamentRegistrationForm: lines 200-260 (lists section) ----"
sed -n '200,260p' "$FORM_FILE" | nl -ba -w3 -s':'Repository: ianpaschal/combat-command
Length of output: 1453
🏁 Script executed:
#!/bin/bash
set -euo pipefail
LIST_META="src/components/ListMetaFields/ListMetaFields.tsx"
FORM_FILE="src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx"
# 1) Does any file watch('gameSystem')?
echo "---- rg watch('gameSystem') ----"
rg -n "watch\\(['\"]gameSystem['\"]\\)" src/components || true
# 2) Inspect ListMetaFields.tsx fully if reasonably small; otherwise targeted sections
echo "---- wc -l $LIST_META ----"
wc -l "$LIST_META"
echo "---- ListMetaFields.tsx: lines 1-120 ----"
sed -n '1,120p' "$LIST_META" | cat -n
echo "---- ListMetaFields.tsx: lines 120-260 ----"
sed -n '120,260p' "$LIST_META" | cat -n
# 3) Inspect TournamentRegistrationForm relevant parts around where gameSystem appears
echo "---- TournamentRegistrationForm.tsx: rg gameSystem ----"
rg -n "gameSystem" "$FORM_FILE" || true
echo "---- TournamentRegistrationForm.tsx: lines 120-220 ----"
sed -n '120,220p' "$FORM_FILE" | cat -n
echo "---- TournamentRegistrationForm.tsx: lines 200-250 (lists rendering) ----"
sed -n '200,250p' "$FORM_FILE" | cat -nRepository: ianpaschal/combat-command
Length of output: 8206
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FORM_FILE="src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx"
echo "---- wc -l $FORM_FILE ----"
wc -l "$FORM_FILE"
echo "---- TournamentRegistrationForm.tsx: top 220 lines (form/schema init) ----"
sed -n '1,220p' "$FORM_FILE" | cat -n
echo "---- TournamentRegistrationForm.tsx: search for useForm / defaultValues / setValue(gameSystem) ----"
rg -n "useForm\\(|defaultValues|setValue\\(|register\\(|FormData" "$FORM_FILE" || true
rg -n "setValue\\(['\"]gameSystem['\"]|defaultValues.*gameSystem|register\\(['\"]gameSystem['\"]" "$FORM_FILE" || true
echo "---- TournamentRegistrationForm.tsx: search for 'lists' field array registration ----"
rg -n "useFieldArray\\(|name:\\s*['\"]lists['\"]|appendList|removeList|listFields" "$FORM_FILE" || true
echo "---- TournamentRegistrationForm.tsx: lines around where form is created (look for useForm call) ----"
FORM_LINE=$(rg -n "useForm\\(" "$FORM_FILE" | head -n1 | cut -d: -f1 || true)
if [ -n "${FORM_LINE:-}" ]; then
START=$((FORM_LINE-60))
END=$((FORM_LINE+40))
sed -n "${START},${END}p" "$FORM_FILE" | cat -n
fiRepository: ianpaschal/combat-command
Length of output: 10144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
SCHEMA_FILE="src/components/TournamentRegistrationForm/TournamentRegistrationForm.schema.ts"
echo "---- ls for schema file (sanity) ----"
ls -la "$(dirname "$SCHEMA_FILE")" | head -n 50
echo "---- wc -l schema file ----"
wc -l "$SCHEMA_FILE"
echo "---- rg gameSystem in schema file ----"
rg -n "gameSystem" "$SCHEMA_FILE" || true
echo "---- rg defaultValues in schema file ----"
rg -n "defaultValues" "$SCHEMA_FILE" || true
echo "---- show likely form data type / schema structure (entire file if small enough) ----"
if [ "$(wc -l < "$SCHEMA_FILE")" -le 220 ]; then
cat -n "$SCHEMA_FILE"
else
sed -n '1,220p' "$SCHEMA_FILE" | cat -n
echo "---- tail ----"
sed -n '221,440p' "$SCHEMA_FILE" | cat -n
fiRepository: ianpaschal/combat-command
Length of output: 5107
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the generic Form wrapper implementation
# Try locating by filename
FORM_COMP=$(fd -a "Form.tsx" src/components | head -n 20 | tr '\n' '\n')
echo "Candidate Form.tsx files:"
echo "$FORM_COMP"
# Look specifically for generic/Form
GENERIC_FORM=$(fd -a "generic/Form.tsx" src/components || true)
if [ -z "${GENERIC_FORM}" ]; then
GENERIC_FORM=$(fd -a "Form.tsx" src/components/generic | head -n 20 || true)
fi
echo "Using generic form file(s):"
echo "$GENERIC_FORM"
for f in $GENERIC_FORM; do
echo "---- $f (first 200 lines) ----"
sed -n '1,220p' "$f" | cat -n
done
# 2) Inspect TournamentRegistrationForm.module.scss for Lists styling
SCSS="src/components/TournamentRegistrationForm/TournamentRegistrationForm.module.scss"
echo "---- rg Lists in scss ----"
rg -n "Lists" "$SCSS" || true
echo "---- scss around lists definitions ----"
rg -n "Lists" "$SCSS" -n | head -n 30 || true
# print the whole file if it's small
wc -l "$SCSS"
if [ "$(wc -l < "$SCSS")" -le 220 ]; then
sed -n '1,260p' "$SCSS" | cat -n
fiRepository: ianpaschal/combat-command
Length of output: 4887
Fix runtime crash in ListMetaFields + add missing key for list items
-
src/components/ListMetaFields/ListMetaFields.tsxwatchesgameSystemfrom RHF context and throws if it’s notGameSystem.FlamesOfWarV4orGameSystem.TeamYankeeV2.src/components/TournamentRegistrationForm/TournamentRegistrationForm.schema.ts/defaultValuesandTournamentRegistrationForm.tsxdon’t define or set agameSystemfield, sowatch('gameSystem')will beundefinedand crash when rendering the Lists section.- Fix: add a
gameSystemform field (defaulted fromtournament.gameSystem) and include it in the schema, or passtournament.gameSystemintoListMetaFieldsand stop watching it from context.
- Fix: add a
-
src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx(listFields.map, ~224-235): missingkeyon the mapped<div>and it applies the outer formclassNameto each list row.- Fix: use
key={field.id}on the root element and switch to a dedicated list-row class if styling is needed.
- Fix: use
🤖 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 `@src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx`
around lines 224 - 235, The Lists rendering crashes because ListMetaFields
relies on watch('gameSystem') but the TournamentRegistrationForm
defaultValues/schema don't provide gameSystem, so either add a gameSystem form
field (initialize it from tournament.gameSystem in defaultValues and include it
in the form schema) or stop relying on RHF context by passing
tournament.gameSystem as a prop into ListMetaFields and update ListMetaFields to
use that prop instead of watch('gameSystem'); additionally fix the list map in
TournamentRegistrationForm (the listFields.map block) by adding key={field.id}
to the root <div> and stop reusing the outer form className for each row (use a
dedicated list-row className instead) so styling and React keys are correct.
| /> | ||
| </div> | ||
| {listFields.map((field, index) => ( | ||
| <div className={className}> |
There was a problem hiding this comment.
Incorrect className and missing key prop.
Two issues on this line:
className={className}applies the root form'sclassNameprop to each list item div—likely unintended- Missing
keyprop will cause React reconciliation issues
Proposed fix
- {listFields.map((field, index) => (
- <div className={className}>
+ {listFields.map((field, index) => (
+ <div key={field.id} className={styles.TournamentRegistrationForm_ListItem}>📝 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.
| <div className={className}> | |
| <div key={field.id} className={styles.TournamentRegistrationForm_ListItem}> |
🤖 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 `@src/components/TournamentRegistrationForm/TournamentRegistrationForm.tsx` at
line 225, The div inside TournamentRegistrationForm that renders each list item
erroneously uses the root prop className (className={className}) and lacks a
React key; update the map render so the per-item div does not reuse the root
className (use a specific item class like itemClassName or a static className
for list items, or remove className from the item and keep className on the root
container) and add a unique key prop (e.g., key={participant.id} or key={index})
to that div in the participants.map (or equivalent) callback to fix
reconciliation issues.
Resolves CC-43
Summary by CodeRabbit
Release Notes
New Features
Improvements