From ba970cd5716d9288e3eda2b2680fc32475189cfd Mon Sep 17 00:00:00 2001
From: Chris Parsons
Date: Sun, 31 May 2026 23:42:35 -0700
Subject: [PATCH] Fix simulate route crash and surface actual error messages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When a simulation action threw (e.g. readiness check failure), React Router
tried to render the simulate route via GET to display the error — but the
route had no loader, so the real error was completely swallowed by React
Router's own 400 "no loader" message.
Four issues fixed:
- Move simulate into the intent-dispatch pattern on the season page (matching
delete/rescore/sync-standings/finalize-standings), so errors surface via
actionData instead of being lost
- Eliminate the ?simulationError= query param approach, which left stale error
messages in browser history and reflected user-controlled text in the UI
- Match the existing bg-destructive/15 error styling used elsewhere on the page
- Strip the now-unused action from simulate.tsx; keep a loader-only redirect
stub so any bookmarked URLs degrade gracefully to the season page
Co-Authored-By: Claude Sonnet 4.6
---
.../admin.sports-seasons.$id.simulate.tsx | 21 +++++--------------
app/routes/admin.sports-seasons.$id.tsx | 20 +++++++++++++++++-
2 files changed, 24 insertions(+), 17 deletions(-)
diff --git a/app/routes/admin.sports-seasons.$id.simulate.tsx b/app/routes/admin.sports-seasons.$id.simulate.tsx
index 5dace5c..49dce26 100644
--- a/app/routes/admin.sports-seasons.$id.simulate.tsx
+++ b/app/routes/admin.sports-seasons.$id.simulate.tsx
@@ -1,24 +1,13 @@
/**
- * Admin: Run EV Simulation for a Sports Season
+ * Admin: Simulate action endpoint (stub)
*
- * Action-only route. POSTing here:
- * 1. Delegates validation + execution to the shared simulator runner
- * 6. Redirects back to the sports season admin page
+ * Simulation is now handled by intent="simulate" on the season admin page.
+ * This stub redirects any direct GET navigation to that page.
*/
import { redirect } from "react-router";
import type { Route } from "./+types/admin.sports-seasons.$id.simulate";
-import { runSportsSeasonSimulation } from "~/services/simulations/runner";
-export async function action({ params }: Route.ActionArgs) {
- const sportsSeasonId = params.id;
- try {
- await runSportsSeasonSimulation(sportsSeasonId);
- } catch (error) {
- throw new Response(error instanceof Error ? error.message : "Simulation failed", {
- status: error instanceof Error && error.message.includes("already running") ? 409 : 400,
- });
- }
-
- return redirect(`/admin/sports-seasons/${sportsSeasonId}/expected-values`);
+export async function loader({ params }: Route.LoaderArgs) {
+ return redirect(`/admin/sports-seasons/${params.id}`);
}
diff --git a/app/routes/admin.sports-seasons.$id.tsx b/app/routes/admin.sports-seasons.$id.tsx
index fb84899..43aab95 100644
--- a/app/routes/admin.sports-seasons.$id.tsx
+++ b/app/routes/admin.sports-seasons.$id.tsx
@@ -11,6 +11,7 @@ import { database } from "~/database/context";
import { participantEvSnapshots, seasonSports } from "~/database/schema";
import { eq, desc } from "drizzle-orm";
import { getSimulatorInfo, type SimulatorType } from "~/services/simulations/registry";
+import { runSportsSeasonSimulation } from "~/services/simulations/runner";
import { syncStandings } from "~/services/standings-sync/index";
import {
getPendingStandingsMappings,
@@ -280,6 +281,17 @@ export async function action(args: Route.ActionArgs) {
}
}
+ if (intent === "simulate") {
+ try {
+ await runSportsSeasonSimulation(params.id);
+ return redirect(`/admin/sports-seasons/${params.id}/expected-values`);
+ } catch (error) {
+ return {
+ simulateError: error instanceof Error ? error.message : "Simulation failed",
+ };
+ }
+ }
+
// Update
const name = formData.get("name");
const year = formData.get("year");
@@ -669,7 +681,8 @@ export default function EditSportsSeason({ loaderData, actionData }: Route.Compo
)}
{simulatorInfo && (
-
+ {"simulateError" in (actionData ?? {}) && actionData?.simulateError && (
+
+ {actionData.simulateError}
+
+ )}
--
2.45.3