Fix Team Race duplicate-membership bug and two related team-roster gaps
Reported by the user: creating a team, adding the wrong driver, then moving that driver to another team by ticking them on the new team's edit form left the driver on both teams at once. Root cause: there was never a "move" action - the edit form only ever added a driver to the team being saved, never removed them from wherever they already were. Live laps from a driver on two teams were then silently credited to only the first-matching team, with no clear error. reassignTeamMembers (event_race_logic.js) now strips a driver/car being saved onto one team out of every other team in the same event, and the team checkbox list shows which team a driver is already on before you move them. Also fixed two related gaps found while investigating, at the user's request to fix everything: getTeamDriverPool had regressed to ignoring the event's class/participant scoping entirely (showing every driver in the club), and deleting a driver or car left them behind in team rosters forever. Added a duplicate_team_members Race Day preflight check as defense in depth for any already-corrupted saved state. Verified with new unit tests for reassignTeamMembers, getTeamDriverPool and the new preflight check, plus a new permanent browser test reproducing the user's exact reported scenario end to end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ import { RACE_AUDIO_EVENT_DEFINITIONS, createRaceAudioEngine, normalizeRaceAudio
|
||||
|
||||
import { renderRaceFormatField, renderRaceFormatContextCard, getRaceSummaryItems, getRaceSummaryWarnings, renderManageStatusBadge, renderRaceWizardSteps, renderRaceWizardContent, renderRaceStandingsTable } from "./race_setup_ui.js";
|
||||
|
||||
import { normalizeRaceTeam as normalizeRaceTeamLogic, normalizeStoredRacePreset as normalizeStoredRacePresetLogic, getRaceFormatPresets as getRaceFormatPresetsLogic, applyRaceFormatPreset as applyRaceFormatPresetLogic, buildRaceFormatConfigFromForm as buildRaceFormatConfigFromFormLogic, normalizeEvent as normalizeEventLogic, normalizeBrandingConfig as normalizeBrandingConfigLogic, resolveEventBranding as resolveEventBrandingLogic, getRaceManageStatuses as getRaceManageStatusesLogic, buildDefaultRaceWizardDraft as buildDefaultRaceWizardDraftLogic, getRaceWizardPreset as getRaceWizardPresetLogic, applyRaceWizardPresetDefaults as applyRaceWizardPresetDefaultsLogic, ensureRaceParticipantsConfigured as ensureRaceParticipantsConfiguredLogic, buildRaceSession as buildRaceSessionLogic, buildOpenPracticeBundle as buildOpenPracticeBundleLogic, buildTrackSession as buildTrackSessionLogic, createSponsorRounds as createSponsorRoundsLogic, buildRaceSessionsFromWizard as buildRaceSessionsFromWizardLogic, getRaceWizardSessionPlan as getRaceWizardSessionPlanLogic, getEventDrivers as getEventDriversLogic, getEventTeams as getEventTeamsLogic, getTeamDriverPool as getTeamDriverPoolLogic, findEventTeamForPassing as findEventTeamForPassingLogic, generateQualifyingForRace as generateQualifyingForRaceLogic, reseedUpcomingQualifying as reseedUpcomingQualifyingLogic, generateFinalsForRace as generateFinalsForRaceLogic, applyBumpsForRace as applyBumpsForRaceLogic } from "./event_race_logic.js";
|
||||
import { normalizeRaceTeam as normalizeRaceTeamLogic, normalizeStoredRacePreset as normalizeStoredRacePresetLogic, getRaceFormatPresets as getRaceFormatPresetsLogic, applyRaceFormatPreset as applyRaceFormatPresetLogic, buildRaceFormatConfigFromForm as buildRaceFormatConfigFromFormLogic, normalizeEvent as normalizeEventLogic, normalizeBrandingConfig as normalizeBrandingConfigLogic, resolveEventBranding as resolveEventBrandingLogic, getRaceManageStatuses as getRaceManageStatusesLogic, buildDefaultRaceWizardDraft as buildDefaultRaceWizardDraftLogic, getRaceWizardPreset as getRaceWizardPresetLogic, applyRaceWizardPresetDefaults as applyRaceWizardPresetDefaultsLogic, ensureRaceParticipantsConfigured as ensureRaceParticipantsConfiguredLogic, buildRaceSession as buildRaceSessionLogic, buildOpenPracticeBundle as buildOpenPracticeBundleLogic, buildTrackSession as buildTrackSessionLogic, createSponsorRounds as createSponsorRoundsLogic, buildRaceSessionsFromWizard as buildRaceSessionsFromWizardLogic, getRaceWizardSessionPlan as getRaceWizardSessionPlanLogic, getEventDrivers as getEventDriversLogic, getEventTeams as getEventTeamsLogic, getTeamDriverPool as getTeamDriverPoolLogic, reassignTeamMembers as reassignTeamMembersLogic, findEventTeamForPassing as findEventTeamForPassingLogic, generateQualifyingForRace as generateQualifyingForRaceLogic, reseedUpcomingQualifying as reseedUpcomingQualifyingLogic, generateFinalsForRace as generateFinalsForRaceLogic, applyBumpsForRace as applyBumpsForRaceLogic } from "./event_race_logic.js";
|
||||
|
||||
import { getSessionTypeLabel as getSessionTypeLabelLogic, getStatusLabel as getStatusLabelLogic, isUntimedSession as isUntimedSessionLogic, getActiveSession as getActiveSessionLogic, getSessionTargetMs as getSessionTargetMsLogic, getSessionLapWindow as getSessionLapWindowLogic, isCountedPassing as isCountedPassingLogic, getVisiblePassings as getVisiblePassingsLogic, getPassingValidationLabel as getPassingValidationLabelLogic, getSessionTiming as getSessionTimingLogic, ensureSessionResult as ensureSessionResultLogic, buildLeaderboard as buildLeaderboardLogic, formatLapDelta as formatLapDeltaLogic, formatLeaderboardGap as formatLeaderboardGapLogic, getCompetitorElapsedMs as getCompetitorElapsedMsLogic, getCompetitorPassings as getCompetitorPassingsLogic, getCompetitorSeedMetric as getCompetitorSeedMetricLogic, getSessionEntrants as getSessionEntrantsLogic, buildPracticeStandings as buildPracticeStandingsLogic, getQualifyingPointsValue as getQualifyingPointsValueLogic, isHighPointsTable as isHighPointsTableLogic, compareNumberSet as compareNumberSetLogic, buildQualifyingTieBreakNote as buildQualifyingTieBreakNoteLogic, hasQualifyingPrimaryTie as hasQualifyingPrimaryTieLogic, buildQualifyingStandings as buildQualifyingStandingsLogic, formatTeamActiveMemberLabel as formatTeamActiveMemberLabelLogic, buildTeamRaceStandings as buildTeamRaceStandingsLogic, buildSessionChannelStats as buildSessionChannelStatsLogic, buildTeamStintLog as buildTeamStintLogLogic, getSessionGridEntries as getSessionGridEntriesLogic, getSessionGridOrder as getSessionGridOrderLogic, ensureSessionDriverOrder as ensureSessionDriverOrderLogic, buildFinalStandings as buildFinalStandingsLogic, getTrackRecordMs as getTrackRecordMsLogic } from "./timing_logic.js";
|
||||
import { normalizeTrackLayouts, getTrackLayoutIdAt, getTrackLayoutLabel, nextTrackLayoutLabel } from "./track_layouts_logic.js";
|
||||
@@ -72,6 +72,7 @@ const getRaceWizardSessionPlan = (draft) => getRaceWizardSessionPlanLogic(draft,
|
||||
const getEventDrivers = (event) => getEventDriversLogic(event, { state });
|
||||
const getEventTeams = (event) => getEventTeamsLogic(event, { normalizeRaceTeam });
|
||||
const getTeamDriverPool = (event) => getTeamDriverPoolLogic(event, { getEventDrivers, state });
|
||||
const reassignTeamMembers = (teams, options) => reassignTeamMembersLogic(teams, options);
|
||||
const findEventTeamForPassing = (event, driverId, carId) => findEventTeamForPassingLogic(event, driverId, carId, { getEventTeams });
|
||||
const generateQualifyingForRace = (event) => generateQualifyingForRaceLogic(event, { buildPracticeStandings, getEventDrivers, state, normalizeStartMode, normalizeSession, uid, chunkArray, clearGeneratedQualifying });
|
||||
const reseedUpcomingQualifying = (event) => reseedUpcomingQualifyingLogic(event, { buildQualifyingStandings, buildPracticeStandings, getEventDrivers, getSessionsForEvent, chunkArray });
|
||||
@@ -425,6 +426,7 @@ const TRANSLATIONS = {
|
||||
"race_day.check_participants": "Deltagare",
|
||||
"race_day.check_transponders": "Transpondrar",
|
||||
"race_day.check_duplicate_transponders": "Dubbla transpondrar",
|
||||
"race_day.check_duplicate_team_members": "Förare/bil i flera lag",
|
||||
"race_day.check_grid": "Startgrid",
|
||||
"race_day.check_audio": "Raceljud",
|
||||
"race_day.lap_window_edit": "Justera varvgränser",
|
||||
@@ -1164,6 +1166,7 @@ const TRANSLATIONS = {
|
||||
"events.team_form_drivers": "Markera lagförare innan du sparar laget.",
|
||||
"events.team_form_cars": "Markera lagbilar innan du sparar laget.",
|
||||
"events.team_driver_fallback": "Inga förare matchade race-klassen eller deltagarlistan. Visar alla förare som fallback.",
|
||||
"events.team_member_elsewhere": "Redan i {team} - kryssar du i flyttas den hit",
|
||||
"events.no_teams": "Inga lag skapade ännu.",
|
||||
"events.team_standings": "Lagställning",
|
||||
"events.no_team_results": "Inga teamresultat ännu.",
|
||||
@@ -1929,6 +1932,7 @@ const TRANSLATIONS = {
|
||||
"race_day.check_participants": "Participants",
|
||||
"race_day.check_transponders": "Transponders",
|
||||
"race_day.check_duplicate_transponders": "Duplicate transponders",
|
||||
"race_day.check_duplicate_team_members": "Driver/car on multiple teams",
|
||||
"race_day.check_grid": "Start grid",
|
||||
"race_day.check_audio": "Race audio",
|
||||
"race_day.lap_window_edit": "Adjust lap limits",
|
||||
@@ -2668,6 +2672,7 @@ const TRANSLATIONS = {
|
||||
"events.team_form_drivers": "Select team drivers before saving the team.",
|
||||
"events.team_form_cars": "Select team cars before saving the team.",
|
||||
"events.team_driver_fallback": "No drivers matched the race class or participant list. Showing all drivers as fallback.",
|
||||
"events.team_member_elsewhere": "Already on {team} - checking this moves them here",
|
||||
"events.no_teams": "No teams created yet.",
|
||||
"events.team_standings": "Team standings",
|
||||
"events.no_team_results": "No team results yet.",
|
||||
@@ -5012,6 +5017,7 @@ function renderEventManager(eventId) {
|
||||
getEventDrivers,
|
||||
getTeamDriverPool,
|
||||
getEventTeams,
|
||||
reassignTeamMembers,
|
||||
normalizeBrandingConfig,
|
||||
getSessionTypeChoices,
|
||||
getRaceFormatPresets,
|
||||
|
||||
Reference in New Issue
Block a user