Introduces three new schema tables (simulator_profiles,
sports_season_simulator_configs, season_participant_simulator_inputs),
a central model layer (app/models/simulator.ts), and a single runner
entry point so every simulator run follows the same prepare → simulate
→ persist → snapshot → recalculate flow.
Key additions:
- manifest.ts: per-simulator display names, default configs, required/
optional inputs, derivable-input declarations, and setup sections
- input-policy.ts: resolves sourceElo from projectedWins,
projectedTablePoints, or sourceOdds; resolves ratings from sourceOdds;
supports block / fallbackElo / averageKnown / worstKnownMinus strategies
- runner.ts: single entry point for admin simulation runs; materialises
derived inputs, normalises result columns, zeroes omitted participants,
snapshots EVs, and recalculates linked fantasy standings
- /admin/simulators: inventory page with per-season readiness and bulk run
- /admin/sports-seasons/:id/simulator: per-season setup page with readiness
summary, input-policy editor, raw JSON config override, and CSV bulk input
- NCAAM/NCAAW simulators now read ratings from season_participant_simulator_inputs,
falling back to the hardcoded name-keyed maps while DB data is being populated
- Clone flow copies simulator config by default; volatile inputs (odds, Elo)
only copied when explicitly requested
Code-review fixes included in this commit:
- source field in compatibility bridge checked with !== null instead of !== undefined
- sourceEloRequirementLabel no longer appends "configured fallback" when the
participant is already excluded from all resolved sources
- Duplicate inline label maps in input-policy.ts replaced with simulatorInputLabel
- save-config preserves existing inputPolicy when the submitted JSON omits it
- Input table truncation label added (Showing 20 of N)
- CSV description notes values must not contain commas
- N+1 comment added to listSportsSeasonSimulatorSummaries
- assertRegistrySchemaDriftFree called in manifest tests
- Runner test suite added covering happy path, already-running guard,
readiness failure, empty results, and error recovery with status reset
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Clone Season button on the admin sports season detail page. The
clone form pre-fills all settings with the source season's data, bumping
the year by one and shifting all dates accordingly. On submit, the new
season is created in a single transaction that copies participants,
scoring events (with dates shifted), futures odds/Elo rating stubs
(zeroed-out probabilities so the simulator can re-run), and QP config
for qualifying_points seasons.
Fixes#282
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>