* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes#88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix computeEliminatedByRound() to track participant1Id/participant2Id
in addition to winnerId/loserId, so a 7v8 play-in loser placed in a
PIR2 slot (incomplete match) is not shown as eliminated on the league page
- Replace separate Recalculate Floors and Reprocess Eliminations buttons
with a single Reprocess Bracket action that replays all matches and
re-marks non-bracket participants as eliminated
- Add NBA play-in test cases for the elimination computation logic
* Emit standings-updated socket event after recalculate-floors so league pages refresh automatically
When the admin runs "Recalculate Floors" on the bracket page, the league
sports-season homepage was showing stale elimination data because there was
no mechanism to notify it of the change.
Fix: after recalculate-floors updates participant_results, emit a
standings-updated socket event to all fantasy-season draft rooms linked to
the sports season. The sports-season page now joins its draft room and
revalidates its loader whenever it receives that event for the matching
sports season.
https://claude.ai/code/session_01D3NbnVQdaH82Fk7Jm8y3sB
* Fix recalculate-floors incorrectly eliminating play-in losers who still advance
The playoff_matches.isScoring column defaults to true in the database. Brackets
created before this column was added (or before the migration set correct values)
have isScoring=true on play-in rounds that should be false. When recalculate-floors
replayed those matches, it took the "scoring round" path; since "Play-In Round 1"
isn't in ROUND_CONFIG, config===null, and the loser was assigned finalPosition=0
regardless of loserAdvances — permanently eliminating teams like the Suns who had
a second play-in game remaining.
Fix: build a round→isScoring lookup from the bracket template before replaying
matches and use it as the source of truth, falling back to the DB field only when
the template doesn't define the round. This ensures non-scoring play-in rounds
are always processed with isScoring=false so the loserAdvances guard fires
correctly.
Also revert unrelated socket changes from the previous (wrong) commit.
https://claude.ai/code/session_01D3NbnVQdaH82Fk7Jm8y3sB
* Fix isScoring fallback and loserAdvances gaps across all match-processing paths
Three issues found in code review around the recalculate-floors fix:
1. set-winner and set-round-winners (bracket.server.ts) used `match.isScoring ?? true`
just like recalculate-floors did. Both now derive isScoring from the bracket
template as the source of truth, falling back to the DB field only when the round
isn't defined in the template.
2. processPlayoffEvent (scoring-calculator.ts) used `matches[0]?.isScoring ?? true`
with the same DB-default problem. Now uses BRACKET_TEMPLATES[bracketTemplateId]
to look up the round's isScoring before falling back to the DB field.
3. doesLoserAdvance (playoff-match.ts) was missing the AFL afl_10 Qualifying Finals
case: both losers advance to Semi-Finals. Without this, AFL QF losers would
incorrectly receive finalPosition=0 when processed through the non-scoring path.
https://claude.ai/code/session_01D3NbnVQdaH82Fk7Jm8y3sB
* Fix no-non-null-assertion lint errors in isScoring Map lookups
Replace Map.has(key) ? Map.get(key)! : fallback pattern with
Map.get(key) ?? fallback to satisfy oxlint no-non-null-assertion rule.
https://claude.ai/code/session_01D3NbnVQdaH82Fk7Jm8y3sB
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix NBA Play-In 7/8 loser incorrectly marked as eliminated
processPlayoffEvent (called by autoCompleteRoundIfDone when all round matches
finish) was marking every non-scoring round loser as eliminated without
checking doesLoserAdvance. This caused the 7v8 loser, who should advance to
Play-In Round 2, to get finalPosition=0 as soon as the full round completed.
Fixes:
- processPlayoffEvent now calls doesLoserAdvance per match before writing a
0-pt elimination result, matching the guard already in processMatchResult
- recalculate-floors handler now passes loserAdvances to processMatchResult
so a full reprocess also respects the loser-advances rule
- recalculateAffectedLeagues gains a skipDiscord option; recalculate-floors
uses it so clicking the admin "Recalculate Floors" button corrects the bad
data without re-announcing results on Discord
- Add two tests confirming 7v8 loser is not eliminated and 9v10 loser is
https://claude.ai/code/session_01QmvezscLYY38gN4GbvXZbA
* Address code review feedback on Play-In loserAdvances fix
- Use outer `round` variable instead of match.round in processPlayoffEvent
(they're identical, but consistent with surrounding code)
- Add comment at recalculate-floors call site explaining skipDiscord intent
- Combine two redundant test cases into one covering all four assertions
- Add West conference matches (M3/M4) to test fixture — East-only was
insufficient given doesLoserAdvance checks matchNumber 1 & 3
- Add guard test: when bracketTemplateId is null all losers are eliminated,
catching any future refactor that drops the field from the DB query
https://claude.ai/code/session_01QmvezscLYY38gN4GbvXZbA
---------
Co-authored-by: Claude <noreply@anthropic.com>
The loser of NBA Play-In Round 1 matches 1 and 3 (East/West 7v8 games)
advances to Play-In Round 2 rather than being eliminated, but was being
assigned finalPosition=0 and appearing in Discord as knocked out.
- Add `loserAdvances` param to `processMatchResult` to skip recording an
elimination result when the loser continues to another match
- Extract `doesLoserAdvance()` to `playoff-match.ts` as the single source
of truth for which play-in matches have advancing losers (replaces
duplicated hardcoded checks at both call sites in bracket.server.ts)
- Extract `isLoserNotifiable()` as a pure exported helper so Discord
notification eligibility is testable; losers only appear when their
team's score changed OR they have a finalized (non-partial) result,
correctly suppressing advancing losers while still surfacing 0-pt
eliminations that produce no score delta
- Add 14 new unit tests covering `doesLoserAdvance`, `isLoserNotifiable`,
and the advancing-loser/eliminated-loser `processMatchResult` branches
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Remove redundant processPlayoffEvent loop from finalize-bracket action
All bracket rounds are already processed (with scoring and Discord
notifications) as each match winner is set via set-winner/set-round-winners.
By the time the Finalize button is clicked, placements are current and
standings are up to date. The re-processing loop was firing recalculations
and Discord notifications once per round needlessly.
The finalize action now only assigns 0 points to non-bracket participants,
marks the event complete, and runs one final standings recalculation.
https://claude.ai/code/session_01RhQS6FQRh6iYtVNaryCEf5
* Fix stale comment in finalize-bracket action
The template is now fetched only as a validity guard, not for round
order iteration (which was removed in the previous commit).
https://claude.ai/code/session_01RhQS6FQRh6iYtVNaryCEf5
* Fix complete-round redundant Discord/recalculation and stale comment
complete-round was calling processPlayoffEvent without skipRecalculate,
firing recalculateAffectedLeagues and Discord even though each match
winner had already triggered those side effects via set-winner. Add
skipRecalculate: true to match the autoCompleteRoundIfDone pattern.
Also fix autoCompleteRoundIfDone comment which incorrectly said
"non-bracket eliminations are recorded" — that's a separate step in
finalize-bracket; processPlayoffEvent records bracket round placements.
https://claude.ai/code/session_01RhQS6FQRh6iYtVNaryCEf5
---------
Co-authored-by: Claude <noreply@anthropic.com>
When generating groups for a tournament (e.g. FIFA World Cup), any
participants in the sport season who were not assigned to a group are
now automatically marked as eliminated (finalPosition = 0) — the same
result as running "Reprocess Eliminations" manually.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add FIFA World Cup 2026 support with group stage display and Monte Carlo simulator, fixes#127
- New `groupStageMatches` table for recording group play results (W/D/L, scores, matchday, schedule)
- `computeGroupStandings()` model function: pts → GD → GF → name tiebreaker ordering
- `GroupStageStandings` component showing all 12 groups with standings table and manager column
- Admin bracket UI: group match score entry, per-group standings, "Recalculate Floors" action
- `WorldCupSimulator`: 50k Monte Carlo covering group stage + best-8 3rd-place + knockout + 3rd place game
- Fuzzy name matching for national team Elo lookup (exact → substring → word-overlap), warns on miss
- Partial group completion: completed matches replayed with real scores, remaining matches simulated
- Elo priority: admin-entered sourceElo > futures odds converted to Elo > hardcoded national team ratings
- `fifa_48` bracket template: added Third Place Game round with `loserFeedsInto` on Semifinals
- Scoring rules: distinct 3rd/4th place for `fifa_48` (not averaged), QF losers share 5th–8th equally
- Floor scoring: SF participants guaranteed 4th (provisional), finalized after 3rd place game
- `recalculate-floors` admin action deletes and replays all results from scratch (fixes stale guard bug)
- Unique index on `(tournamentGroupId, participant1Id, participant2Id)` to prevent duplicate pairings
- Batch `findMatchesByGroupIds()` replacing N sequential queries in the sport season loader
- League home mini-standings now shows `actualPoints` (includes floor) instead of `totalPoints` only
- Elo ratings admin page supports World Cup (same bulk-import flow as snooker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors: update GroupStandingData type to use findMatchesByGroupIds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Increase Node heap to 4GB for unit tests in CI to prevent OOM
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix OOM in CI: make WorldCupSimulator simulation count configurable for tests
Tests now pass numSimulations=500 instead of the production default of 50,000.
Six simulator tests × 50k iterations each was exhausting the 4GB heap on GitHub
Actions runners. Also reduce simGroupMatch stat tests from 50k to 5k iterations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add app/lib/logger.ts: dev passes through to console; prod routes errors
to Sentry.captureException and warnings to Sentry.captureMessage, with
extra context preserved. Uses captureMessage (not captureException) for
string-only args to avoid fabricated stack traces.
- Add server/logger.ts: dev passes through; prod silences log/info but
keeps warn/error on stderr (Sentry not initialized in that process).
- Replace all console.* calls across 44 app files and 4 server files.
- Upgrade no-console from warn → error in oxlint; exempt logger files and
scripts/** via overrides.
- Add typescript/no-inferrable-types rule; fix violations in services and
simulators. Exempt test files (intentional string widening for switch/if
tests would break under literal type inference).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add oxlint and fix all lint errors
- Install oxlint, add .oxlintrc.json with rules for TypeScript/React
- Add npm run lint / lint:fix scripts
- Add Claude PostToolUse hook to run oxlint on every edited file
- Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array
- Fix no-array-index-key (use stable keys or suppress positional cases)
- Fix exhaustive-deps missing dependency in useEffect
- Promote exhaustive-deps and no-array-index-key to errors
- Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-explicit-any warnings and upgrade tsconfig to ES2023
- Replace all `any` types with proper types or `unknown` across ~20 files
- Add typed socket payload interfaces in draft route and useDraftSocket
- Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch)
- Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed
- Fix cascading type errors uncovered by removing any: Map.get narrowing,
participant relation types, ChartDataPoint, Partial<NewSeason> indexing
- Add ParticipantResultWithParticipant type to participant-result model
- Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult)
- Fix duplicate getQPStandings import in sportsSeasonId.server.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Promote no-explicit-any to error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add unique index on (team_id, season_id, snapshot_date) in team_standings_snapshots
so concurrent writes can't produce duplicate rows (migration 0051)
- Rewrite createDailySnapshot to use INSERT ... ON CONFLICT DO UPDATE inside a
transaction, replacing the SELECT-then-insert/skip pattern (N+1 queries, race-prone)
- Remove early-exit guard in server/snapshots.ts background job so it always
upserts rather than skipping seasons that already have a snapshot for today
- Call createDailySnapshot in recalculateAffectedLeagues after recalculateStandings,
so every bracket score update refreshes today's snapshot; wrapped in try/catch so
a snapshot failure can't block Discord notifications
- Also call createDailySnapshot from the admin rescore and finalize-bracket actions
- Fix UTC date bug: replace toISOString().split('T')[0] with local date parts in
both standings.ts and server/snapshots.ts (and the 7-day lookback query)
- Convert all dynamic await import() calls in bracket.server.ts to static imports
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- NCAAM R64/R32 winners no longer receive T5 floor points; only Sweet
Sixteen winners entering the first scoring round get a floor
- AFL T5-T6 and T7-T8 score as separate tiers (avg([5,6]) and avg([7,8]))
instead of one shared avg([5-8]) pool
- Eliminated teams now always show their correct final rank (e.g. T33 for
NCAAM R64 losers) even during partial round scoring, by deriving the
rank label from the total match count per round rather than the dynamic
still-alive count
- Discord notifications now fire when a drafted participant is eliminated
with 0 points (e.g. R64 losers whose team score doesn't change)
- Discord notifications no longer include all prior matches — scoped to
the current batch via matchIds
- Discord section headers have spacing; rank medals replaced with numbers
- Rounds auto-complete when all matches are marked done; manual Complete
Round dropdown removed
- Fixed double Discord notification when the last match in a round
triggers auto-complete: processPlayoffEvent now accepts skipRecalculate
so the caller controls when the notification fires
- Fixed falsy check on finalPosition=0 in calculateTeamScore
- Undrafted 0-point participants filtered from Eliminated Teams display
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add Discord webhook notifications for standings updates
Adds a Discord webhook integration that posts standings to a configured
Discord channel whenever scores change after a scoring event. Commissioners
set the webhook URL in league settings and can send a test notification.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix Discord notification edge cases from code review
- Fix point delta sign: negative diffs now correctly show -5 pts not +-5 pts
- Replace hardcoded soccer emoji with a neutral bullet (works for all sports)
- Truncate embed description at Discord's 4096-char limit
- Thread eventId through processMatchResult and bracket batch handler so
scored matches appear in single-match notifications too
- Pass eventName to finalizeQualifyingPoints ("Final Standings") and
processSeasonStandings ("Season Complete") so those notifications have context
- Test notification now fetches current season to show "League 2025" format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix process-match-result tests: add sportsSeasons to db mock
recalculateAffectedLeagues now queries sportsSeasons to get the sport
name for Discord notifications; the test mock db needed the stub added.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Partial bracket scoring
- `processMatchResult`: new exported function that scores a single match
immediately (loser → final placement, winner → provisional floor).
Called from `set-winner` and `set-round-winners` so points are awarded
as soon as a winner is set, before the full round is complete.
- `set-winner`: passes `eventName` to `processMatchResult`.
- `set-round-winners`: batches side effects — calls `recalculateAffectedLeagues`
and `updateProbabilitiesAfterResult` once after the loop instead of per-match
(`skipSideEffects: true` per match).
## Code review fixes
- **ROUND_CONFIG** (S1): extracted a `RoundScoringConfig` lookup table +
`getRoundConfig()` helper, eliminating three parallel `if/else` chains in
`processPlayoffEvent`, `processMatchResult`, and `getGuaranteedMinimumPosition`.
AFL template overrides live in `TEMPLATE_ROUND_CONFIG` with an explanatory
comment about why the `bracketTemplateId` guard is required.
- **skipSideEffects** (C2): new param on `processMatchResult`; bracket server
uses it to batch standings/probability recalc in `set-round-winners`.
- **eventName** (W1): passed through `processMatchResult` → `recalculateAffectedLeagues`.
- **Round validation** (W2): `set-round-winners` now guards `match.round === round`
before processing each assignment.
- **Comments** (C3/S3/W3): added notes on non-scoring loser assumption,
`isScoring ?? true` default, and AFL Semi-Finals template requirement.
## PlayoffBracket eliminated-teams fix + tests
- Fixed `computeEliminatedByRound` to track participant *appearances* (not just
wins), so AFL QF losers who advance to Semi-Finals via double-chance are
correctly excluded from the QF eliminated list.
- Extracted the logic as an exported pure function for testability.
- Added 4 tests: standard elimination, AFL QF loser → SF win, AFL QF loser →
SF loss, and normal advancement not protecting a later loser.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the hardcoded 2026 Men's region structure with a per-event
configurable system so the same ncaa_68 template works for Women's
March Madness (different region names and play-in placements each year).
- Add `bracket_region_config jsonb` column to `scoring_events`
- Export `ALL_16_SEEDS` and `BracketRegion`/`BracketPlayIn` interfaces
from bracket-templates.ts so both server and client share the type
- Admin bracket entry form now shows a "Configure Regions" section:
editable region names + add/remove play-in seed selectors (ShadCN
Select) per region; slot map updates live as config changes
- Server action parses region config from form, validates total = 68,
stores it on the event, and passes it to bracket generation
- `advanceFirstFourWinner` reads stored `bracketRegionConfig` from the
event row first, falls back to template.regions for existing events
- Fix opponentSeed formula (was `seedNum <= 8 ? 17-n : n`, always `17-n`)
- Remove duplicate ALL_SEEDS inline definitions (now a shared constant)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add playoff match games and odds storage
Introduces two new tables for bracket matchup detail storage:
- `playoff_match_games`: tracks individual game schedules within a
series matchup (game number, scheduledAt, status, per-game scores,
winner). Supports scheduled/complete/postponed status enum.
- `playoff_match_odds`: stores moneyline odds per participant per
matchup (single upsert record, no isLatest complexity).
Includes:
- Drizzle schema + relations with CASCADE deletes from playoff_matches
- Migration 0040_fat_puma.sql
- playoff-match-game.ts model with pure helpers: computeSeriesScore,
isSeriesComplete, getSeriesLeader — plus full CRUD
- playoff-match-odds.ts model with pure helpers: americanToImpliedProbability,
impliedProbabilityToAmerican, normalizeOdds — plus upsert/read/delete
- findPlayoffMatchesByEventId and findPlayoffMatchById updated to
include games and odds in their query results
- Bracket server route: add-game, update-game, delete-game,
upsert-odds, delete-odds actions
- Bracket admin UI: expandable per-match panel for game schedule
management and moneyline odds entry
- 41 new unit tests (18 game + 23 odds), all 810 tests passing
https://claude.ai/code/session_01Twt3D1bsEK3eXUhMaz6ee7
* Code review fixes: type safety, abstraction, and React correctness
- Derive PlayoffMatchGameStatus from schema enum instead of hardcoding
the string union, eliminating the duplicate source of truth
- updateGame now returns PlayoffMatchGame | undefined to reflect reality
when no row matches the ID
- Remove TOCTOU check-then-act in update-game action: call updateGame
directly and check the return value instead of a pre-flight findGameById
- Add status enum validation before the cast in update-game action
- Move impliedProbability computation inside upsertMatchOdds so callers
only provide moneylineOdds; the model owns the derivation
- Remove unnecessary dynamic import of americanToImpliedProbability in
the upsert-odds action (was already imported from the same module)
- Fix React list reconciliation bug: replace bare <> fragment with
<Fragment key={match.id}> so React can correctly track rows
https://claude.ai/code/session_01Twt3D1bsEK3eXUhMaz6ee7
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Add FIFA_48 bracket template with 12 groups of 4, knockout stage of 32
- Add tournament groups schema, model, and GroupStageDisplay component
- Add projected/expected value scoring to standings and team breakdowns
- Add docker-compose for local PostgreSQL development
- Move DRAFT_ORDER_IMPLEMENTATION.md to plans directory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added loader and action functions for managing expected values in sports seasons.
- Implemented UI for recalculating probabilities based on participant results.
- Created a service to update probabilities after results are finalized, including handling finished and unfinished participants.
- Developed tests for the probability updater service to ensure correct functionality.
- Introduced a preview feature to show potential changes before applying updates.
- Created `TeamScoreBreakdown` component to display all drafted participants, their placements, and points.
- Added new route for team breakdown at `/leagues/:leagueId/standings/:seasonId/teams/:teamId`.
- Enhanced `getTeamScoreBreakdown` model to include `sportsSeasonId` for grouping.
- Updated `StandingsTable` to include clickable team name links.
- Added functionality to finalize brackets in playoff events, including error handling and recalculating standings.
- Implemented tests for `TeamScoreBreakdown` component to ensure proper rendering and functionality.
- Updated routing to support new team breakdown feature and ensure seamless navigation.
- Introduced `generateStandardSeeding()` function to create proper tournament matchups (e.g., 1v16, 8v9).
- Added comprehensive tests for 4, 8, 16, and 32-team brackets to ensure correct seeding and balance.
- Updated testing instructions to reflect new features and improvements in bracket generation.
- Fixed issues with sequential seeding and ensured all matchups sum to n+1 for balance.
- Implemented batch winner selection and duplicate participant prevention for improved user experience.
- Enhanced fantasy points display on event pages for better visibility of participant results.
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.