From 686069aa0f575defdc9d735af83e5e3ba32d44e5 Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Mon, 11 May 2026 22:24:29 -0700 Subject: [PATCH] Fix circular dependency and client-bundle server-only code in simulator routes (#410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NCAAM and NCAAW simulators imported getParticipantSimulatorInputs from models/simulator, which imports manifest.ts, which imports registry.ts — creating a cycle back through registry.ts → simulator files. This caused a TDZ ReferenceError in the client bundle. Fix: inline a direct seasonParticipantSimulatorInputs query in both simulators (they already run direct DB queries) instead of going through the model layer, breaking the cycle. Also moves getSimulatorInputPolicy from the component body to the loader on the simulator setup page. The component calling it pulled input-policy.ts → manifest.ts → registry.ts → all simulator implementations into the client bundle, which contain Node.js-only code and caused "Error loading route module" in production. Co-authored-by: Claude Sonnet 4.6 --- app/routes/admin.sports-seasons.$id.simulator.tsx | 7 ++++--- app/services/simulations/ncaam-simulator.ts | 10 +++++++--- app/services/simulations/ncaaw-simulator.ts | 10 +++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/routes/admin.sports-seasons.$id.simulator.tsx b/app/routes/admin.sports-seasons.$id.simulator.tsx index a82a7fe..f59d4c3 100644 --- a/app/routes/admin.sports-seasons.$id.simulator.tsx +++ b/app/routes/admin.sports-seasons.$id.simulator.tsx @@ -55,7 +55,9 @@ export async function loader({ params }: Route.LoaderArgs) { input: inputMap.get(participant.id) ?? null, })); - return { sportsSeason, participants, config, inputRows, readiness }; + const inputPolicy = getSimulatorInputPolicy(config.config); + + return { sportsSeason, participants, config, inputRows, readiness, inputPolicy }; } interface ActionData { @@ -234,12 +236,11 @@ export async function action({ request, params }: Route.ActionArgs): Promise(); const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting"; const setupSections = config.profile.setupSections; - const inputPolicy = getSimulatorInputPolicy(config.config); const sourceEloAlternatives = config.profile.derivableInputs?.sourceElo ?? []; return ( diff --git a/app/services/simulations/ncaam-simulator.ts b/app/services/simulations/ncaam-simulator.ts index 595bd79..870e74f 100644 --- a/app/services/simulations/ncaam-simulator.ts +++ b/app/services/simulations/ncaam-simulator.ts @@ -48,7 +48,6 @@ import type { BracketRegion } from "~/lib/bracket-templates"; import { buildNCAA68SlotMap, matchIndexForSeedSlot, NCAA_68 } from "~/lib/bracket-templates"; import type { Simulator, SimulationResult } from "./types"; import { logger } from "~/lib/logger"; -import { getParticipantSimulatorInputs } from "~/models/simulator"; // ─── Simulation parameters ──────────────────────────────────────────────────── @@ -499,7 +498,10 @@ export class NCAAMSimulator implements Simulator { .select({ id: schema.seasonParticipants.id, name: schema.seasonParticipants.name }) .from(schema.seasonParticipants) .where(inArray(schema.seasonParticipants.id, participantIds)), - getParticipantSimulatorInputs(sportsSeasonId), + db + .select({ participantId: schema.seasonParticipantSimulatorInputs.participantId, rating: schema.seasonParticipantSimulatorInputs.rating }) + .from(schema.seasonParticipantSimulatorInputs) + .where(eq(schema.seasonParticipantSimulatorInputs.sportsSeasonId, sportsSeasonId)), ]); if (participantRows.length < participantIds.length) { @@ -514,7 +516,9 @@ export class NCAAMSimulator implements Simulator { const inputById = new Map(simulatorInputs.map((input) => [input.participantId, input])); const netRatingById = new Map(); for (const { id, name } of participantRows) { - netRatingById.set(id, inputById.get(id)?.rating ?? getNetRating(name)); + const rawRating = inputById.get(id)?.rating; + const rating = rawRating !== null && rawRating !== undefined ? Number(rawRating) : null; + netRatingById.set(id, (rating !== null && Number.isFinite(rating) ? rating : null) ?? getNetRating(name)); } // 7. Build per-round O(1) lookup maps keyed by matchNumber. diff --git a/app/services/simulations/ncaaw-simulator.ts b/app/services/simulations/ncaaw-simulator.ts index f665422..d1977c5 100644 --- a/app/services/simulations/ncaaw-simulator.ts +++ b/app/services/simulations/ncaaw-simulator.ts @@ -46,7 +46,6 @@ import type { BracketRegion } from "~/lib/bracket-templates"; import { buildNCAA68SlotMap, matchIndexForSeedSlot, NCAA_68 } from "~/lib/bracket-templates"; import type { Simulator, SimulationResult } from "./types"; import { logger } from "~/lib/logger"; -import { getParticipantSimulatorInputs } from "~/models/simulator"; // ─── Simulation parameters ──────────────────────────────────────────────────── @@ -462,7 +461,10 @@ export class NCAAWSimulator implements Simulator { .select({ id: schema.seasonParticipants.id, name: schema.seasonParticipants.name }) .from(schema.seasonParticipants) .where(inArray(schema.seasonParticipants.id, participantIds)), - getParticipantSimulatorInputs(sportsSeasonId), + db + .select({ participantId: schema.seasonParticipantSimulatorInputs.participantId, rating: schema.seasonParticipantSimulatorInputs.rating }) + .from(schema.seasonParticipantSimulatorInputs) + .where(eq(schema.seasonParticipantSimulatorInputs.sportsSeasonId, sportsSeasonId)), ]); if (participantRows.length < participantIds.length) { @@ -477,7 +479,9 @@ export class NCAAWSimulator implements Simulator { const inputById = new Map(simulatorInputs.map((input) => [input.participantId, input])); const barthagById = new Map(); for (const { id, name } of participantRows) { - barthagById.set(id, inputById.get(id)?.rating ?? getBarthagRating(name)); + const rawRating = inputById.get(id)?.rating; + const rating = rawRating !== null && rawRating !== undefined ? Number(rawRating) : null; + barthagById.set(id, (rating !== null && Number.isFinite(rating) ? rating : null) ?? getBarthagRating(name)); } // 7. Build per-round O(1) lookup maps keyed by matchNumber.