2026-03-21 00:12:01 -07:00
import {
Card ,
CardContent ,
CardDescription ,
CardHeader ,
CardTitle ,
} from "~/components/ui/card" ;
import { BarChart3 } from "lucide-react" ;
import { TeamOwnerBadge } from "~/components/ui/team-owner-badge" ;
interface StandingRow {
id : string ;
participantId : string ;
wins : number ;
losses : number ;
otLosses : number | null ;
2026-05-07 11:48:57 -07:00
ties? : number | null ;
tablePoints? : number | null ;
goalsFor? : number | null ;
goalsAgainst? : number | null ;
goalDifference? : number | null ;
2026-03-21 00:12:01 -07:00
winPct : string | null ;
gamesPlayed : number ;
gamesBack : string | null ;
conference : string | null ;
division : string | null ;
conferenceRank : number | null ;
divisionRank : number | null ;
leagueRank : number | null ;
streak : string | null ;
lastTen : string | null ;
homeRecord : string | null ;
awayRecord : string | null ;
2026-03-21 09:44:05 -07:00
syncedAt : Date | string | null ;
2026-03-21 00:12:01 -07:00
participant : { id : string ; name : string ; shortName? : string | null } ;
}
interface TeamOwnership {
teamName : string ;
ownerName : string ;
teamId : string ;
}
interface Props {
standings : StandingRow [ ] ;
teamOwnerships : Record < string , TeamOwnership > ;
userParticipantIds : string [ ] ;
showOtLosses? : boolean ;
2026-05-07 11:48:57 -07:00
showSoccerTable? : boolean ;
2026-03-21 00:12:01 -07:00
/** How many teams per conference qualify for playoffs (flat mode only). 0 = no line. */
playoffSpots? : number ;
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
/** "flat" = single ranked list per conference (NBA). "nhl-divisions" = div top-3 + wild card. "mlb-divisions" = same structure with 3 WC spots, uses "League" label. */
displayMode ? : "flat" | "nhl-divisions" | "mlb-divisions" ;
2026-03-21 00:12:01 -07:00
}
type RankMode = "conference" | "division" | "index" ;
interface TableSection {
heading? : string ;
rows : StandingRow [ ] ;
rankMode : RankMode ;
playoffSpots : number ;
showDivisionLabel? : boolean ;
}
// ─── Formatting ───────────────────────────────────────────────────────────────
function formatWinPct ( winPct : string | null , wins : number , gamesPlayed : number ) : string {
2026-03-21 09:44:05 -07:00
if ( winPct !== null ) {
2026-03-21 00:12:01 -07:00
const pct = parseFloat ( winPct ) ;
if ( ! isNaN ( pct ) ) return pct . toFixed ( 3 ) ;
}
if ( gamesPlayed > 0 ) return ( wins / gamesPlayed ) . toFixed ( 3 ) ;
return "—" ;
}
function formatGB ( gamesBack : string | null ) : string {
2026-03-21 09:44:05 -07:00
if ( gamesBack === null ) return "—" ;
2026-03-21 00:12:01 -07:00
const gb = parseFloat ( gamesBack ) ;
if ( isNaN ( gb ) || gb === 0 ) return "—" ;
return gb % 1 === 0 ? gb . toString ( ) : gb . toFixed ( 1 ) ;
}
function getRank ( row : StandingRow , idx : number , mode : RankMode ) : number {
if ( mode === "division" ) return row . divisionRank ? ? idx + 1 ;
if ( mode === "index" ) return idx + 1 ;
return row . conferenceRank ? ? idx + 1 ;
}
// ─── Grouping ─────────────────────────────────────────────────────────────────
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
type ConferenceGroup = { conference : string ; conferenceLabel : string ; sections : TableSection [ ] } ;
2026-03-21 00:12:01 -07:00
function buildFlatSections (
standings : StandingRow [ ] ,
playoffSpots : number
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
) : ConferenceGroup [ ] {
2026-03-21 00:12:01 -07:00
const hasConferences = standings . some ( ( s ) = > s . conference ) ;
if ( ! hasConferences ) {
return [
{
conference : "" ,
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
conferenceLabel : "Conference" ,
2026-03-21 00:12:01 -07:00
sections : [
{
2026-03-21 09:44:05 -07:00
rows : [ . . . standings ] . toSorted (
2026-03-21 00:12:01 -07:00
( a , b ) = > ( a . leagueRank ? ? 99 ) - ( b . leagueRank ? ? 99 )
) ,
rankMode : "conference" ,
playoffSpots ,
} ,
] ,
} ,
] ;
}
const confMap = new Map < string , StandingRow [ ] > ( ) ;
for ( const row of standings ) {
const conf = row . conference ? ? "Other" ;
if ( ! confMap . has ( conf ) ) confMap . set ( conf , [ ] ) ;
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
confMap . get ( conf ) ? . push ( row ) ;
2026-03-21 00:12:01 -07:00
}
return Array . from ( confMap . entries ( ) ) . map ( ( [ conference , rows ] ) = > ( {
conference ,
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
conferenceLabel : "Conference" ,
2026-03-21 00:12:01 -07:00
sections : [
{
2026-03-21 09:44:05 -07:00
rows : [ . . . rows ] . toSorted (
2026-03-21 00:12:01 -07:00
( a , b ) = >
( a . conferenceRank ? ? a . leagueRank ? ? 99 ) -
( b . conferenceRank ? ? b . leagueRank ? ? 99 )
) ,
rankMode : "conference" as RankMode ,
playoffSpots ,
showDivisionLabel : true ,
} ,
] ,
} ) ) ;
}
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
/ * *
* Shared builder for division - based display modes ( NHL and MLB ) .
* ` divisionSpots ` : how many teams per division qualify directly ( 3 for NHL , 1 for MLB ) .
* ` wcSpots ` : how many wildcard slots exist per conference ( 2 for NHL , 3 for MLB ) .
* ` conferenceLabel ` : the word shown after the conference name in the heading .
* /
function buildDivisionSections (
standings : StandingRow [ ] ,
divisionSpots : number ,
wcSpots : number ,
conferenceLabel : string
) : ConferenceGroup [ ] {
2026-03-21 00:12:01 -07:00
const confMap = new Map < string , StandingRow [ ] > ( ) ;
for ( const row of standings ) {
const conf = row . conference ? ? "Other" ;
if ( ! confMap . has ( conf ) ) confMap . set ( conf , [ ] ) ;
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
confMap . get ( conf ) ? . push ( row ) ;
2026-03-21 00:12:01 -07:00
}
return Array . from ( confMap . entries ( ) ) . map ( ( [ conference , rows ] ) = > {
const divMap = new Map < string , StandingRow [ ] > ( ) ;
for ( const row of rows ) {
const div = row . division ? ? "" ;
if ( ! divMap . has ( div ) ) divMap . set ( div , [ ] ) ;
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
divMap . get ( div ) ? . push ( row ) ;
2026-03-21 00:12:01 -07:00
}
const divisions = Array . from ( divMap . entries ( ) )
. map ( ( [ name , divRows ] ) = > ( {
name ,
qualifiers : divRows
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
. filter ( ( r ) = > ( r . divisionRank ? ? 99 ) <= divisionSpots )
2026-03-21 09:44:05 -07:00
. toSorted ( ( a , b ) = > ( a . divisionRank ? ? 99 ) - ( b . divisionRank ? ? 99 ) ) ,
2026-03-21 00:12:01 -07:00
} ) )
2026-03-21 09:44:05 -07:00
. toSorted (
2026-03-21 00:12:01 -07:00
( a , b ) = >
( a . qualifiers [ 0 ] ? . conferenceRank ? ? 99 ) -
( b . qualifiers [ 0 ] ? . conferenceRank ? ? 99 )
) ;
const wildCard = rows
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
. filter ( ( r ) = > ( r . divisionRank ? ? 99 ) > divisionSpots )
2026-03-21 09:44:05 -07:00
. toSorted (
2026-03-21 00:12:01 -07:00
( a , b ) = >
( a . conferenceRank ? ? a . leagueRank ? ? 99 ) -
( b . conferenceRank ? ? b . leagueRank ? ? 99 )
) ;
const sections : TableSection [ ] = [
. . . divisions . map ( ( div ) = > ( {
heading : ` ${ div . name } Division ` ,
rows : div.qualifiers ,
rankMode : "division" as RankMode ,
playoffSpots : 0 ,
showDivisionLabel : false ,
} ) ) ,
. . . ( wildCard . length > 0
? [
{
heading : "Wild Card" ,
rows : wildCard ,
rankMode : "index" as RankMode ,
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
playoffSpots : wcSpots ,
2026-03-21 00:12:01 -07:00
showDivisionLabel : true ,
} ,
]
: [ ] ) ,
] ;
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
return { conference , conferenceLabel , sections } ;
2026-03-21 00:12:01 -07:00
} ) ;
}
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
function buildNhlSections ( standings : StandingRow [ ] ) : ConferenceGroup [ ] {
return buildDivisionSections ( standings , 3 , 2 , "Conference" ) ;
}
function buildMlbSections ( standings : StandingRow [ ] ) : ConferenceGroup [ ] {
return buildDivisionSections ( standings , 1 , 3 , "League" ) ;
}
2026-03-21 00:12:01 -07:00
// ─── Single table that renders all sections ───────────────────────────────────
function StandingsTable ( {
sections ,
teamOwnerships ,
userParticipantIds ,
showOtLosses ,
2026-05-07 11:48:57 -07:00
showSoccerTable = false ,
2026-03-21 00:12:01 -07:00
} : {
sections : TableSection [ ] ;
teamOwnerships : Record < string , TeamOwnership > ;
userParticipantIds : string [ ] ;
showOtLosses : boolean ;
2026-05-07 11:48:57 -07:00
showSoccerTable? : boolean ;
2026-03-21 00:12:01 -07:00
} ) {
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
const allRows = sections . flatMap ( ( s ) = > s . rows ) ;
const hasStreak = allRows . some ( ( r ) = > r . streak != null ) ;
const hasLastTen = allRows . some ( ( r ) = > r . lastTen != null ) ;
const hasGB = allRows . some ( ( r ) = > r . gamesBack != null ) ;
// # Team GP W [D] L [GF GA GD PTS] [OTL PTS] [PCT] [GB] [L10] [STK] Mgr
// Soccer: 11 fixed columns. Non-soccer: 5 base + GP + optional cols.
// GP and PCT are always counted even when hidden on mobile; colSpan must
// reflect all rendered columns regardless of CSS visibility.
const totalCols = showSoccerTable
? 11
: 5 + 1 /* GP */ + ( showOtLosses ? 2 : 0 ) + 1 /* PCT */ + ( hasGB ? 1 : 0 ) + ( hasLastTen ? 1 : 0 ) + ( hasStreak ? 1 : 0 ) ;
// Non-soccer rows get a secondary sub-row on mobile showing GP/PCT/GB/L10.
// Soccer tables show GP inline (no sub-row) since they already show many columns.
const showSubRow = ! showSoccerTable ;
2026-03-21 00:12:01 -07:00
return (
< div className = "overflow-x-auto -mx-6 px-6" >
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
< table className = "w-full min-w-[360px] text-sm" >
2026-03-21 00:12:01 -07:00
< thead >
< tr className = "text-xs text-muted-foreground uppercase tracking-wide border-b" >
< th className = "text-left py-1.5 pr-2 w-6" > # < / th >
< th className = "text-left py-1.5" > Team < / th >
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
< th className = { ` ${ showSubRow ? "hidden sm:table-cell " : "" } text-right py-1.5 px-2 w-10 ` } > GP < / th >
2026-03-21 00:12:01 -07:00
< th className = "text-right py-1.5 px-2 w-8" > W < / th >
2026-05-07 11:48:57 -07:00
{ showSoccerTable && < th className = "text-right py-1.5 px-2 w-8" > D < / th > }
2026-03-21 00:12:01 -07:00
< th className = "text-right py-1.5 px-2 w-8" > L < / th >
2026-05-07 11:48:57 -07:00
{ showSoccerTable && < th className = "text-right py-1.5 px-2 w-10" > GF < / th > }
{ showSoccerTable && < th className = "text-right py-1.5 px-2 w-10" > GA < / th > }
{ showSoccerTable && < th className = "text-right py-1.5 px-2 w-10" > GD < / th > }
{ showSoccerTable && < th className = "text-right py-1.5 px-2 w-10" > PTS < / th > }
2026-03-21 00:12:01 -07:00
{ showOtLosses && < th className = "text-right py-1.5 px-2 w-10" > OTL < / th > }
{ showOtLosses && < th className = "text-right py-1.5 px-2 w-10" > PTS < / th > }
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
{ ! showSoccerTable && < th className = "hidden sm:table-cell text-right py-1.5 px-2 w-12" > PCT < / th > }
{ ! showSoccerTable && hasGB && < th className = "hidden sm:table-cell text-right py-1.5 px-2 w-10" > GB < / th > }
{ ! showSoccerTable && hasLastTen && < th className = "hidden sm:table-cell text-right py-1.5 px-2 w-12" > L10 < / th > }
{ ! showSoccerTable && hasStreak && < th className = "text-right py-1.5 px-2 w-12" > STK < / th > }
2026-03-21 00:12:01 -07:00
< th className = "text-right py-1.5 pl-4 w-40" > Mgr < / th >
< / tr >
< / thead >
< tbody >
{ sections . flatMap ( ( section , sIdx ) = > {
const sectionRows : React.ReactNode [ ] = [ ] ;
let playoffLineShown = false ;
if ( section . heading ) {
sectionRows . push (
2026-03-21 09:44:05 -07:00
< tr key = { ` h- ${ section . heading } ` } >
2026-03-21 00:12:01 -07:00
< td
colSpan = { totalCols }
className = { ` text-xs font-medium text-muted-foreground/60 uppercase tracking-wider pb-1 ${
sIdx > 0 ? "pt-5" : "pt-2"
} ` }
>
{ section . heading }
< / td >
< / tr >
) ;
}
section . rows . forEach ( ( row , i ) = > {
const rank = getRank ( row , i , section . rankMode ) ;
if ( ! playoffLineShown && section . playoffSpots > 0 && rank > section . playoffSpots ) {
playoffLineShown = true ;
sectionRows . push (
< tr key = { ` pl- ${ sIdx } ` } >
< td colSpan = { totalCols } className = "py-0.5" >
< div className = "flex items-center gap-2" >
< div className = "flex-1 border-t border-dashed border-amber-500/40" / >
< span className = "text-[10px] text-amber-600/70 dark:text-amber-500/70 uppercase tracking-wide whitespace-nowrap font-medium px-1" >
Playoff Line
< / span >
< div className = "flex-1 border-t border-dashed border-amber-500/40" / >
< / div >
< / td >
< / tr >
) ;
}
const isUserTeam = userParticipantIds . includes ( row . participantId ) ;
const ownership = teamOwnerships [ row . participantId ] ;
sectionRows . push (
< tr
key = { row . id }
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
className = { ` ${ showSubRow ? "" : "border-b border-border/50 last:border-0" } ${
2026-03-21 00:12:01 -07:00
isUserTeam ? "bg-primary/5" : "hover:bg-muted/30"
} ` }
>
< td className = "py-2 pr-2 text-muted-foreground tabular-nums" > { rank } < / td >
< td className = "py-2" >
< span className = { ` font-medium ${ isUserTeam ? "text-primary" : "" } ` } >
{ row . participant . shortName ? ? row . participant . name }
< / span >
{ section . showDivisionLabel && row . division && (
< span className = "ml-1.5 text-[10px] text-muted-foreground/50 uppercase tracking-wide" >
{ row . division }
< / span >
) }
< / td >
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
< td className = { ` ${ showSubRow ? "hidden sm:table-cell " : "" } py-2 px-2 text-right tabular-nums text-muted-foreground ` } >
2026-03-21 00:12:01 -07:00
{ row . gamesPlayed }
< / td >
< td className = "py-2 px-2 text-right tabular-nums font-medium" > { row . wins } < / td >
2026-05-07 11:48:57 -07:00
{ showSoccerTable && (
< td className = "py-2 px-2 text-right tabular-nums" > { row . ties ? ? 0 } < / td >
) }
2026-03-21 00:12:01 -07:00
< td className = "py-2 px-2 text-right tabular-nums" > { row . losses } < / td >
2026-05-07 11:48:57 -07:00
{ showSoccerTable && (
< td className = "py-2 px-2 text-right tabular-nums text-muted-foreground" > { row . goalsFor ? ? "—" } < / td >
) }
{ showSoccerTable && (
< td className = "py-2 px-2 text-right tabular-nums text-muted-foreground" > { row . goalsAgainst ? ? "—" } < / td >
) }
{ showSoccerTable && (
< td className = "py-2 px-2 text-right tabular-nums text-muted-foreground" > { row . goalDifference ? ? "—" } < / td >
) }
{ showSoccerTable && (
< td className = "py-2 px-2 text-right tabular-nums font-medium" > { row . tablePoints ? ? row . wins * 3 + ( row . ties ? ? 0 ) } < / td >
) }
2026-03-21 00:12:01 -07:00
{ showOtLosses && (
< td className = "py-2 px-2 text-right tabular-nums" > { row . otLosses ? ? 0 } < / td >
) }
{ showOtLosses && (
< td className = "py-2 px-2 text-right tabular-nums font-medium" >
{ row . wins * 2 + ( row . otLosses ? ? 0 ) }
< / td >
) }
2026-05-07 11:48:57 -07:00
{ ! showSoccerTable && (
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
< td className = "hidden sm:table-cell py-2 px-2 text-right tabular-nums text-muted-foreground" >
2026-05-07 11:48:57 -07:00
{ formatWinPct ( row . winPct , row . wins , row . gamesPlayed ) }
< / td >
) }
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
{ ! showSoccerTable && hasGB && (
< td className = "hidden sm:table-cell py-2 px-2 text-right tabular-nums text-muted-foreground" >
2026-05-07 11:48:57 -07:00
{ formatGB ( row . gamesBack ) }
< / td >
) }
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
{ ! showSoccerTable && hasLastTen && (
< td className = "hidden sm:table-cell py-2 px-2 text-right tabular-nums text-muted-foreground" >
2026-05-07 11:48:57 -07:00
{ row . lastTen ? ? "—" }
< / td >
) }
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
{ ! showSoccerTable && hasStreak && (
2026-05-07 11:48:57 -07:00
< td className = "py-2 px-2 text-right tabular-nums" >
{ row . streak ? (
< span
className = {
row . streak . startsWith ( "W" )
? "text-emerald-500"
: "text-destructive/80"
}
>
{ row . streak }
< / span >
) : (
< span className = "text-muted-foreground" > — < / span >
) }
< / td >
) }
2026-03-21 00:12:01 -07:00
< td className = "py-2 pl-4 text-right" >
{ ownership ? (
< div className = "flex justify-end" >
< TeamOwnerBadge
teamName = { ownership . teamName }
ownerName = { ownership . ownerName || undefined }
align = "right"
/ >
< / div >
) : (
< span className = "text-xs text-muted-foreground" > — < / span >
) }
< / td >
< / tr >
) ;
Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413)
* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout
- mlb.ts: use streakCode alone (the API already returns the full string like
"W3"); appending streakNumber was doubling the digit, causing "L33" display
- Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W")
- RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows
have data, so pre-season or stats-free sports don't show blank columns
- Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden)
so all data stays visible without horizontal scroll on small screens; STK and
W/L remain on the primary row; reduce min-w from 740px to 360px
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
* Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow
- mlb.ts: drop redundant `?? undefined` after optional chain; document that
streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
inline for soccer on all viewports, hidden only for non-soccer (which has
the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data
https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 08:14:34 -07:00
if ( showSubRow ) {
sectionRows . push (
< tr
key = { ` ${ row . id } -sub ` }
className = { ` sm:hidden border-b border-border/50 last:border-0 ${
isUserTeam ? "bg-primary/5" : "hover:bg-muted/30"
} ` }
>
< td colSpan = { totalCols } className = "pb-2 pt-0 pr-2" >
< div className = "flex flex-wrap gap-x-4 gap-y-0.5 text-xs text-muted-foreground pl-4" >
< span >
< span className = "uppercase tracking-wide" > GP < / span > { " " }
< span className = "tabular-nums text-foreground" > { row . gamesPlayed } < / span >
< / span >
< span >
< span className = "uppercase tracking-wide" > PCT < / span > { " " }
< span className = "tabular-nums text-foreground" > { formatWinPct ( row . winPct , row . wins , row . gamesPlayed ) } < / span >
< / span >
{ hasGB && (
< span >
< span className = "uppercase tracking-wide" > GB < / span > { " " }
< span className = "tabular-nums text-foreground" > { formatGB ( row . gamesBack ) } < / span >
< / span >
) }
{ hasLastTen && (
< span >
< span className = "uppercase tracking-wide" > L10 < / span > { " " }
< span className = "tabular-nums text-foreground" > { row . lastTen ? ? "—" } < / span >
< / span >
) }
< / div >
< / td >
< / tr >
) ;
}
2026-03-21 00:12:01 -07:00
} ) ;
return sectionRows ;
} ) }
< / tbody >
< / table >
< / div >
) ;
}
// ─── Main export ──────────────────────────────────────────────────────────────
export function RegularSeasonStandings ( {
standings ,
teamOwnerships ,
userParticipantIds ,
showOtLosses = false ,
2026-05-07 11:48:57 -07:00
showSoccerTable = false ,
2026-03-21 00:12:01 -07:00
playoffSpots = 8 ,
displayMode = "flat" ,
} : Props ) {
if ( standings . length === 0 ) return null ;
const lastSyncedAt = standings
. map ( ( s ) = > s . syncedAt )
. filter ( Boolean )
2026-03-21 09:44:05 -07:00
. toSorted ( )
2026-03-21 00:12:01 -07:00
. at ( - 1 ) ;
const groups =
displayMode === "nhl-divisions"
? buildNhlSections ( standings )
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
: displayMode === "mlb-divisions"
? buildMlbSections ( standings )
2026-03-21 00:12:01 -07:00
: buildFlatSections ( standings , playoffSpots ) ;
2026-05-07 11:48:57 -07:00
const tableProps = { teamOwnerships , userParticipantIds , showOtLosses , showSoccerTable } ;
2026-03-21 00:12:01 -07:00
return (
< Card >
< CardHeader >
< div className = "flex items-center justify-between gap-2" >
< CardTitle className = "flex items-center gap-2" >
< BarChart3 className = "h-5 w-5" / >
Regular Season Standings
< / CardTitle >
{ lastSyncedAt && (
< CardDescription className = "text-xs tabular-nums" >
Updated { new Date ( lastSyncedAt ) . toLocaleDateString ( ) }
< / CardDescription >
) }
< / div >
< / CardHeader >
< CardContent >
{ groups . map ( ( group ) = > (
< div key = { group . conference } className = "mb-6 last:mb-0" >
{ group . conference && (
< h3 className = "text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3" >
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121
- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
(weighted by p_div) and 3 WC teams per league, then simulating
WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
winner per division section + Wild Card section with 3-spot playoff line,
headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
(divisionSpots + wcSpots params); conferenceLabel now flows through group
objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix flaky tennis simulator test: increase trials and lower threshold
500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00
{ group . conference } { group . conferenceLabel }
2026-03-21 00:12:01 -07:00
< / h3 >
) }
< StandingsTable sections = { group . sections } { ...tableProps } / >
< / div >
) ) }
< / CardContent >
< / Card >
) ;
}