Address review: don't auto-promote primary for golf; lighten window count
- Auto-heal now only promotes a new primary when the deleted event was itself the primary window. Golf-style shared majors intentionally have no primary (scored on the canonical tournament page), so deleting one of their windows no longer flips a sibling into a primary and changes its scoring/guard behavior. Adds a regression test for that case. - Replace the relation-heavy getSportsSeasonsByTournament + double Array.find in the events loader with a new countWindowsByTournament helper (count(distinct sports_season_id)) and a single cached lookup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CBzjCLVkVaQMj1MF3K54t2
This commit is contained in:
parent
7f7ea4e29d
commit
ec89e3eb89
3 changed files with 49 additions and 12 deletions
|
|
@ -119,6 +119,24 @@ describe("deleteScoringEvent", () => {
|
||||||
expect(result.deletedTournament).toBe(false);
|
expect(result.deletedTournament).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not promote a primary when a golf-style tournament (no primary) loses a window", async () => {
|
||||||
|
// Golf-style shared majors intentionally have no primary window — every
|
||||||
|
// linked event is isPrimary=false. Deleting one must not flip a sibling
|
||||||
|
// into a primary, which would change its scoring/guard behavior.
|
||||||
|
const { db } = makeSharedDb({
|
||||||
|
deletedEvent: { tournamentId: "tournament-1", isPrimary: false },
|
||||||
|
remaining: [
|
||||||
|
{ id: "event-2", isPrimary: false },
|
||||||
|
{ id: "event-3", isPrimary: false },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await deleteScoringEvent("event-1", db);
|
||||||
|
|
||||||
|
expect(result.promotedPrimaryId).toBeNull();
|
||||||
|
expect(result.remainingWindows).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
it("promotes the earliest remaining window when the primary window is deleted", async () => {
|
it("promotes the earliest remaining window when the primary window is deleted", async () => {
|
||||||
const { db } = makeSharedDb({
|
const { db } = makeSharedDb({
|
||||||
deletedEvent: { tournamentId: "tournament-1", isPrimary: true },
|
deletedEvent: { tournamentId: "tournament-1", isPrimary: true },
|
||||||
|
|
|
||||||
|
|
@ -305,11 +305,13 @@ export async function deleteScoringEvent(
|
||||||
remainingWindows = remaining.length;
|
remainingWindows = remaining.length;
|
||||||
|
|
||||||
if (remaining.length > 0) {
|
if (remaining.length > 0) {
|
||||||
// Auto-heal: if the deleted event was the primary window (or no sibling is
|
// Auto-heal: if the deleted event was the primary window, promote the
|
||||||
// flagged primary anymore), promote the earliest remaining window so the
|
// earliest remaining window so the shared major stays scorable and
|
||||||
// shared major stays scorable and fan-out stays deterministic.
|
// fan-out stays deterministic. Only heal when we actually removed the
|
||||||
const hasPrimary = remaining.some((e) => e.isPrimary);
|
// primary — golf-style majors intentionally have no primary (scored on the
|
||||||
if (event.isPrimary || !hasPrimary) {
|
// canonical tournament page), and event.isPrimary is false for them, so
|
||||||
|
// they are left untouched.
|
||||||
|
if (event.isPrimary) {
|
||||||
await setPrimaryEvent(remaining[0].id, db);
|
await setPrimaryEvent(remaining[0].id, db);
|
||||||
promotedPrimaryId = remaining[0].id;
|
promotedPrimaryId = remaining[0].id;
|
||||||
}
|
}
|
||||||
|
|
@ -1126,6 +1128,24 @@ export async function getSportsSeasonsByTournament(tournamentId: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the distinct sports-season windows linked to a tournament. Cheaper than
|
||||||
|
* getSportsSeasonsByTournament when only the count is needed (no relations).
|
||||||
|
*/
|
||||||
|
export async function countWindowsByTournament(
|
||||||
|
tournamentId: string,
|
||||||
|
providedDb?: ReturnType<typeof database>
|
||||||
|
): Promise<number> {
|
||||||
|
const db = providedDb || database();
|
||||||
|
const [row] = await db
|
||||||
|
.select({
|
||||||
|
count: sql<number>`count(distinct ${schema.scoringEvents.sportsSeasonId})::int`,
|
||||||
|
})
|
||||||
|
.from(schema.scoringEvents)
|
||||||
|
.where(eq(schema.scoringEvents.tournamentId, tournamentId));
|
||||||
|
return row?.count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The primary scoring event for a tournament — the single window where the admin
|
* The primary scoring event for a tournament — the single window where the admin
|
||||||
* builds the bracket/stages and scoring happens; its results fan out to siblings.
|
* builds the bracket/stages and scoring happens; its results fan out to siblings.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
deleteScoringEvent,
|
deleteScoringEvent,
|
||||||
bulkCreateScoringEvents,
|
bulkCreateScoringEvents,
|
||||||
ensurePrimaryEvent,
|
ensurePrimaryEvent,
|
||||||
getSportsSeasonsByTournament,
|
countWindowsByTournament,
|
||||||
type CreateScoringEventData,
|
type CreateScoringEventData,
|
||||||
} from "~/models/scoring-event";
|
} from "~/models/scoring-event";
|
||||||
import { isBracketMajor } from "~/lib/event-utils";
|
import { isBracketMajor } from "~/lib/event-utils";
|
||||||
|
|
@ -66,15 +66,14 @@ export async function loader({ params }: Route.LoaderArgs) {
|
||||||
const sharedInfo = new Map<string, { name: string; windowCount: number }>();
|
const sharedInfo = new Map<string, { name: string; windowCount: number }>();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[...linkedTournamentIds].map(async (tid) => {
|
[...linkedTournamentIds].map(async (tid) => {
|
||||||
const [tournament, windows] = await Promise.all([
|
const cached = allTournamentsForSport.find((t) => t.id === tid);
|
||||||
allTournamentsForSport.find((t) => t.id === tid)
|
const [tournament, windowCount] = await Promise.all([
|
||||||
? Promise.resolve(allTournamentsForSport.find((t) => t.id === tid)!)
|
cached ?? getTournamentById(tid),
|
||||||
: getTournamentById(tid),
|
countWindowsByTournament(tid),
|
||||||
getSportsSeasonsByTournament(tid),
|
|
||||||
]);
|
]);
|
||||||
sharedInfo.set(tid, {
|
sharedInfo.set(tid, {
|
||||||
name: tournament?.name ?? "shared tournament",
|
name: tournament?.name ?? "shared tournament",
|
||||||
windowCount: windows.length,
|
windowCount,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue