Commit graph

25 commits

Author SHA1 Message Date
Chris Parsons
73f0256ff5 Fix QP standings: filter to drafted/points-earning participants with correct global ranks
Some checks failed
🚀 Deploy / 🧪 Test (pull_request) Failing after 2m33s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m20s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
- Filter the QP table to show only participants with QP > 0 or drafted by any team in the league (undrafted 0-QP participants hidden)
- Compute global ranks with tie handling across the full field before filtering so displayed rank numbers and the top-8 Points Line remain correct after rows are removed
- Fix flaky CI timeouts: world-cup simulator test (100 → 50 iterations), SportsSection userEvent test (explicit 15s timeout)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 09:38:47 -07:00
841760541d Fix snake draft Discord pick numbering and small UX tweaks (#2)
All checks were successful
🚀 Deploy / 🧪 Test (push) Successful in 1m27s
🚀 Deploy / ʦ TypeScript (push) Successful in 1m16s
🚀 Deploy / 🔍 Lint (push) Successful in 1m55s
🚀 Deploy / 🐳 Build (push) Successful in 14m23s
🚀 Deploy / 🚀 Deploy (push) Successful in 10s
## Summary

- **Discord snake draft fix**: pick notifications now show the sequential pick-in-round (e.g. "Round 2, Pick 9") rather than the snake-adjusted slot position ("Round 2, Pick 5"). Also removes the now-unused \`pickInRound\` param from \`notifyPickMadeOnDiscord\` and adds a regression test for the 13-team case.
- **League card**: league name in draft-in-progress cards is now a link to the league homepage (the Enter Draft button still goes to the draft room).
- **Overnight pause label**: shortened to "🌙 Pause" — the "Resumes 4:00 AM" line below already provides context, so "Overnight" was just causing wrapping.
- **Queue & Picks backgrounds**: items use \`bg-card\` instead of \`bg-muted\` for better visual separation from the panel background.

## Test plan

- [x] Discord: in a snake draft, verify pick #22 of 13 shows "Round 2, Pick 9" in Discord
- [x] League card: dashboard with a draft-in-progress league — click name → league homepage, click Enter Draft → draft room
- [x] Overnight pause: pause a draft overnight — cell shows "🌙 Pause" on one line, "Resumes X:XX" below
- [x] Queue/Picks: open draft room and confirm queue items and recent picks visually pop from the sidebar background
- [x] Unit tests: `npm run test:run` passes (13 discord tests)

Co-authored-by: Chris Parsons <chrisparsons1127@gmail.com>
Reviewed-on: #2
2026-05-22 04:18:11 +00:00
Chris Parsons
fbcecb490e
Add Discord draft pick announcements (#460)
* Add Discord draft pick announcements

Posts a message to the league Discord webhook each time a pick is made, announcing the picked participant and pinging the next team on the clock if their owner has opted into Discord notifications. Enabled via a separate toggle in league settings (independent from standings update notifications).

https://claude.ai/code/session_01Tvwsv3LfL9JUqxoLct8dTn

* Address code review feedback on Discord pick announcements

- Fix "on the clock" timing: read currentPickNumber fresh from DB post-autodraft-chain (matches sendOnTheClockEmail guarantee)
- Remove outer try/catch from notifyPickMadeOnDiscord so callers' .catch() is not dead code
- Add missing draft-discord.server.test.ts with 12 tests covering all early-exit and happy paths
- Fix silent empty-string fallback for missing pickedSlot: warn and skip instead
- Eliminate sequential season→league DB queries by accepting leagueId as a direct param
- Show "save webhook URL to configure options" hint when URL is typed but not yet saved
- Remove block-scope braces at both call sites (plain const declarations)
- Remove redundant "Round N, Pick M" description line (title already carries this info)
- Inline pickInRoundFor helper to avoid circular import with draft-utils

https://claude.ai/code/session_01Tvwsv3LfL9JUqxoLct8dTn

* Fix lint errors from review fixes

- Remove unused logger import (no longer needed after removing try/catch)
- Remove unused OWNER_ID constant in test fixture
- Use toSorted() instead of sort() in sendDraftOrderNotification

https://claude.ai/code/session_01Tvwsv3LfL9JUqxoLct8dTn

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-20 19:55:48 -07:00
Chris Parsons
3c757c1f73
Replace settings redirect with toast notification (#445)
* Show toast instead of redirecting after league settings save

Commishes now stay on the settings page when saving, with a Sonner
toast confirming the save rather than being navigated to the league
homepage.

https://claude.ai/code/session_01PewdKxVfAbqasJbmk2UGw4

* Fix toast firing for unrelated intents after settings save

Tag update responses with intent: "update" so the success useEffect
only fires for the main settings form, not for owner/commissioner/
reset-draft actions which also return { success: true }. Also replaces
the remaining ?updated=true redirect (no-season edge case) with the
same response shape for consistency.

https://claude.ai/code/session_01PewdKxVfAbqasJbmk2UGw4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-18 16:08:10 -07:00
Chris Parsons
4bbcac1949
fix: resolve all 48 WCAG 2.2 AA accessibility issues (#439)
* fix: resolve all 48 WCAG 2.2 AA accessibility issues

Critical fixes:
- Add aria-label to all unlabeled inputs/selects in draft dialogs (ParticipantSelectionDialog, TimeBankAdjustmentDialog, AvailableParticipantsSection)
- Add role="dialog" + aria-modal + focus trap to ConnectionOverlay and AuthRecoveryOverlay
- Add aria-live region and connection status announcement to ConnectionOverlay

Serious fixes:
- Add skip-to-content link in root.tsx with id="main-content" on <main>
- Add aria-label to UserMenu trigger button
- Add aria-describedby + role="alert" to all auth form error messages (login, register, onboarding, forgot-password, reset-password)
- Replace emoji column headers in StandingsTable with aria-label + aria-hidden spans
- Add aria-live="assertive" to "It's your turn" desktop and mobile on-clock indicators
- Add aria-live="polite" to draft room countdown timer
- Add pause button to SportTicker (WCAG 2.2.2); add aria-hidden to ticker content
- Fix Footer text contrast (changed from 28% to text-muted-foreground)
- Fix OvernightPauseSettings: add htmlFor/id pairs and role="radiogroup"+aria-checked to mode buttons
- Fix DraftSetupSection: replace broken htmlFor with aria-label on date picker button
- Add aria-label to PeopleSection owner and commissioner selects
- Add labels to ScoringPresetPicker score inputs; add role="radiogroup"+aria-checked to preset buttons
- Add role="radiogroup"+aria-checked to AutodraftSettings option buttons
- Add accessible names, aria-current="step", and <ol> list semantics to WizardStepper

Moderate fixes:
- Add aria-controls to RecentPicksFeed toggle button; wrap picks list in aria-live region
- Add role="tab"+aria-selected+aria-controls to mobile board sub-tabs + role="tabpanel"
- Add role="radiogroup"+aria-checked to TimerModeSelector
- Add aria-current="page" + aria-label to SettingsDesktopNav
- Add aria-label="Admin navigation" to admin sidebar nav
- Add scope="col" + <caption> to StandingsTable and ScoringTables
- Add ARIA table roles (role="table/rowgroup/row/columnheader/rowheader/cell") to DraftSummaryView CSS grid

Minor fixes:
- Add aria-hidden="true" to decorative trend icons in StandingsTable
- Add aria-hidden="true" to desktop column header labels row in AvailableParticipantsSection
- Replace title with aria-label on all icon-only buttons (watchlist, queue) in AvailableParticipantsSection
- Add aria-label to NotificationSettings switchOnly Switch
- Add prefers-reduced-motion check to SlotMachineHeadline JS animation
- Bump --muted-foreground from 55% to 62% opacity for improved contrast margin

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

* Fix code review findings from WCAG compliance pass

- Add Arrow key navigation + roving tabindex to all role=radiogroup
  components (AutodraftSettings x2, TimerModeSelector,
  OvernightPauseSettings, ScoringPresetPicker) per ARIA radio pattern
- Extract shared focus-trap logic into useFocusTrap hook; update
  ConnectionOverlay and AuthRecoveryOverlay to use it
- Add tabIndex={-1} to ConnectionOverlay Card so focus can land in
  spinner-only state (no interactive children)
- Replace aria-live on loading dots container with sr-only span so
  status changes are announced by text content, not aria-label
- Remove contradictory aria-hidden+role=columnheader from
  AvailableParticipantsSection visual-only header row
- Remove invalid scope="col" from div[role=columnheader] in
  DraftSummaryView (scope is only valid on <th>)
- Remove redundant aria-label from ParticipantSelectionDialog sport
  select (htmlFor label is sufficient)
- Change WizardStepper connector <li> to role=presentation
- Revert muted-foreground from 62% to 55% (original already passes
  contrast; footer was fixed separately via text-muted-foreground)

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

* Fix lint error and update tests for WCAG role changes

- Replace el! non-null assertion with optional chaining in useFocusTrap
- Update AutodraftSettings tests to query role="radio" instead of
  role="button" (buttons have an explicit radio role since the WCAG pass)
- Update AvailableParticipantsSection watchlist tests to use
  getByRole/getAllByRole instead of getByTitle/getAllByTitle (watchlist
  buttons now use aria-label instead of title)

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 20:11:38 -07:00
Chris Parsons
d468385d90
Add auto-start draft at scheduled time (#437) (#438)
Commissioners can now opt in to having a draft start automatically at
its scheduled draftDateTime rather than requiring a manual "Start Draft"
click. The 1-second timer loop checks for eligible seasons each tick and
calls the new shared startDraft() service, which is also used by the
existing commissioner HTTP route.

- Add autoStartDraft boolean to seasons table (migration 0108)
- Extract core start logic into app/services/draft-autostart.ts so both
  the API route and the timer can call it without AsyncLocalStorage
- Add checkAndAutoStartDrafts() to the timer tick; disables autoStartDraft
  and stops retrying if no draft order is set at fire time
- Guard against null draftDateTime in both the timer query (isNotNull)
  and server actions (autoStartDraft forced false when datetime is null)
- Add "Auto-start at scheduled time" checkbox to league creation wizard
  (step 3) and league settings, gated on date+time being set
- Show countdown + "Start Now" button in draft room when auto-start is
  scheduled; reuses the existing nowForPauseCheck 1-second ticker
- Show orange warning banner on league homepage to commissioners when
  auto-start is within 1 hour and no draft order has been configured

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 23:37:29 -07:00
Chris Parsons
faecfd8ecc
Rename queue page to Set Pre-Draft Queue, remove VORP, fix re-add race condition (#436)
* Rename queue page to Set Pre-Draft Queue, remove VORP, fix re-add race condition

- Rename page title, h1, button, error message, and tests from
  "Pre-Draft Rankings" to "Set Pre-Draft Queue"
- Remove VORP values and "Sorted by VORP" labels from the all-players list
- Fix bug where removing a player then immediately re-adding would fail
  with "already in queue": track in-flight removes in a ref and guard
  handleAdd against firing while a remove is still in-flight

https://claude.ai/code/session_01TGAV9A2c72PNkL1ffY7Xeq

* Address code review findings on Set Pre-Draft Queue page

- Rename component function from PreDraftRankings to SetPreDraftQueue
- Guard handleRemove against temp IDs — items added optimistically but
  not yet written to the DB have no server-side record to delete; skip
  the API call and just drop them from local state
- Surface a toast when handleAdd is blocked by a pending remove instead
  of silently no-oping
- Group both refs before their shared useEffect for readability
- Drop justify-between from the All Players desktop heading (sole child)
- Update test describe blocks to "Set Pre-Draft Queue Access"

https://claude.ai/code/session_01TGAV9A2c72PNkL1ffY7Xeq

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 18:47:36 -07:00
Chris Parsons
973e56142f
Add pre-draft queue builder so users can rank players before draft order is set (#435)
* Add pre-draft queue builder so users can rank players before draft order is set

Creates a new /leagues/:leagueId/draft-queue/:seasonId page that lets team
owners browse participants by VORP and build their autopick queue during the
pre_draft phase. The queue uses the existing draftQueue table so it carries
seamlessly into the live draft room.

Adds a "Build Your Queue" button to DraftInfoCard that shows only when draft
order has not been set yet (replaced by "Enter Draft Room" once it is).

https://claude.ai/code/session_01Gu2DkTWL3nv74EMuGPpxhG

* Improve pre-draft rankings UX: mobile tabs, add/remove toggle, rename

On mobile, show tabs ("All Players" / "My Rankings") instead of a stacked
layout that buries the queue below a long participant list. On the All Players
list, the button now toggles between Add and Remove so users never need to
switch tabs just to drop someone. Desktop keeps the side-by-side panel layout.

Also renames the feature throughout from "queue builder" to "pre-draft rankings"
and the DraftInfoCard button to "Set Pre-Draft Rankings".

https://claude.ai/code/session_01Gu2DkTWL3nv74EMuGPpxhG

* Fix all code review issues in pre-draft rankings feature

- Replace useFetcher with direct fetch() for add/remove/reorder so rapid
  clicks no longer cancel in-flight requests
- Add toast.error() on all failure paths (matching live draft room pattern)
  and revert optimistic state when operations fail
- Add useRef to give handleReorder a stable reference without a localQueue
  closure dependency, preventing unnecessary QueueSection re-renders
- Convert allPlayersPanel and rankingsPanel to useMemo
- Remove leagueId from loader return; read from useParams() instead
- Extract queueBuilderHref to a variable in the league home component
- Add emptyMessage prop to QueueSection with a correct message for the
  pre-draft context ("Add players from All Players...")
- Add draft-queue-access.test.ts covering all loader access control paths:
  401/403 errors, status-based redirects, and redirect URL correctness

https://claude.ai/code/session_01Gu2DkTWL3nv74EMuGPpxhG

* Fix lint: use !== null check instead of != null

oxlint enforces eqeqeq; replace != null with !== null && !== undefined.

https://claude.ai/code/session_01Gu2DkTWL3nv74EMuGPpxhG

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 17:26:36 -07:00
Chris Parsons
55828e9f42
Simplify sports season display and add local timezone hints (#434)
* Show season name instead of year/bracket format; add local timezone hint to draft time

Replace the raw year number and scoringType string in the league creation
sport picker, review step, and league settings sports section with the
human-readable season name (e.g. "2025 NBA Season"), matching how the
league homepage sports list already displays seasons.

Add a client-side local timezone label below the draft date & time fields
on both the league creation wizard and the league settings page, so users
know that draft times are in their local timezone.

https://claude.ai/code/session_01GtJowGruAc1A4jURkSDVjX

* Address code review: shared tz hook, short abbrev, sort fix, dead type cleanup, test update

- Extract timezone detection into useLocalTimezone hook to avoid duplication
  across DraftSetupSection and Step3DraftSettings
- Use Intl short timezone abbreviation (e.g. "EST") instead of raw IANA name,
  matching DraftInfoCard's existing pattern
- Sort review step sport list by season name instead of sport.name so the
  visible order matches the displayed text
- Remove unused scoringType and year fields from SportsSection's local type
- Update SportsSection tests to use realistic season names and the new
  display format

https://claude.ai/code/session_01GtJowGruAc1A4jURkSDVjX

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 16:04:07 -07:00
Chris Parsons
d92f73e449
Extract chess clock presets to shared constant (#432)
* Fix custom chess-clock timer detection and save settings blocker

- Extract getInitialDraftSpeed() helper so both useState init and
  resetSettingsFormState use the same logic; unrecognized presets
  now produce a "custom:{bank}:{incr}" string instead of falling
  back to "standard", so the custom section auto-expands correctly
  (e.g. 8 hr bank + 30 min increment is no longer shown as Standard)
- resetSettingsFormState was not resetting draftSpeed at all; fixed
- Clear hasUnsavedSettingsChanges in the Form's onSubmit so the
  useBlocker check is false at the moment the save navigation starts,
  preventing the "Leave without saving?" dialog from firing on save
- Replace the cleared-on-success useEffect with one that re-marks
  dirty when the action returns an error, so the warning reappears
  after a failed save

https://claude.ai/code/session_01DgHNkvJXGizx41CE2tkVS1

* Address code review: single source of truth for presets, useEffect guard, tests

- Extract CHESS_CLOCK_PRESETS (pure data) into draft-timer.ts as the
  canonical source of truth; DraftSpeedPicker.tsx now spreads those
  values into CHESS_PRESETS rather than duplicating the numbers
- getInitialDraftSpeed moved to draft-timer.ts and rewritten to use
  CHESS_CLOCK_PRESETS.find() — no more hardcoded bankSec/incrSec values
  in three separate places
- parseDraftSpeed switch replaced with CHESS_CLOCK_PRESETS.find() and
  an explicit fallback comment; eliminates the silent default-handles-
  "standard" fragility called out in review
- useEffect that re-marks settings dirty now guards against non-settings
  errors (draft-order, commissioner actions) which carry a `section`
  field — previously any error actionData would spuriously set
  hasUnsavedSettingsChanges=true
- Add parseDraftSpeed and getInitialDraftSpeed test suites to
  draft-timer.test.ts, including parametrised preset coverage via
  it.each(CHESS_CLOCK_PRESETS)

https://claude.ai/code/session_01DgHNkvJXGizx41CE2tkVS1

* Fix lint: use !== null/undefined instead of != null

oxlint enforces eqeqeq; the null check in getInitialDraftSpeed used
!= which triggered two errors.

https://claude.ai/code/session_01DgHNkvJXGizx41CE2tkVS1

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 14:02:49 -07:00
Chris Parsons
d4a9fc8aaf
Improve draft info panel formatting (#428) 2026-05-14 23:42:04 -07:00
Chris Parsons
6c3065f6f2
Show commish-only leagues in a separate dashboard section, fixes #336 (#412)
Leagues where the user is a commissioner but has no team are now shown
in a "Leagues I Commish" subsection below their member leagues, rather
than silently appearing with no stats. A live draft always takes
priority over the commish-only row so commissioners still see the draft
banner.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:57:04 -07:00
Chris Parsons
777315541b
Use sport icons in league views (#394) 2026-05-07 23:06:49 -07:00
Chris Parsons
6df4f86920
Improve flag config validation and remove settings completion tracking (#390)
* Fix mobile league settings: prevent sports scroll and remove completion checkmarks

- Add overflow-hidden to sport label text span so flex truncation is properly contained
- Add min-w-0 to SettingsSection header inner flex item to prevent title/description overflow
- Remove completion check marks from mobile grid nav buttons (league settings has no completion concept)
- Remove "X of Y set" counter from mobile nav header
- Remove isComplete field from SettingsGridSection type and all data

https://claude.ai/code/session_01T7iTb9YZLuWJFtKV753tdB

* Fix race window in user creation and restore FlagSvg safety fallback

auth.server.ts: switch generateId to application-generated UUIDs so the
ID is known before the insert, then move flagConfig assignment from
create.after (a separate UPDATE) to create.before so it lands in the
initial INSERT with no race window.

FlagSvg.tsx: validate the config with parseFlagConfig before rendering;
corrupt or missing data falls back to a neutral gray triband flag rather
than silently rendering blank SVG shapes.

https://claude.ai/code/session_01T7iTb9YZLuWJFtKV753tdB

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-07 10:09:45 -07:00
Chris Parsons
bbe5bc4053
Fix standings page team icons to use selected team avatar (#388)
* Fix standings page team icons to use selected team avatar

The full standings page was not passing avatar data (logoUrl, flagConfig,
avatarType, ownerAvatarData) to StandingsPreview entries, so all teams
showed the default initials fallback instead of their chosen avatar.

Fetches the avatar columns from the teams table in the loader, resolves
owner avatar data for teams using the owner avatar type, and threads
all avatar fields through formattedStandings into previewEntries.

https://claude.ai/code/session_01CVt5Vo2PDGY4G3wSWbsmRG

* Add StandingsPreview component tests covering avatar rendering

Tests were missing for the StandingsPreview component, which is the
component that actually renders team avatars on the full standings page.
Covers uploaded image, flag SVG, owner avatar inheritance, and the
generated-flag fallback, plus basic content rendering (team names,
owner names, points, description, full standings link).

https://claude.ai/code/session_01CVt5Vo2PDGY4G3wSWbsmRG

* Fix StandingsPreview test ambiguous text query for team names

FlagSvg renders a <title> element with the team name for accessibility,
so getByText("Team Alpha") matched two elements. Scope the query to the
<p> element to target only the visible team name display.

https://claude.ai/code/session_01CVt5Vo2PDGY4G3wSWbsmRG

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-06 18:08:46 -07:00
Chris Parsons
46a81a6435
Fix avatar display bugs in navbar, standings, and draft views (#387)
- Remove rounded clipping on navbar user avatar button (was showing dark circular background)
- Fix "My Avatar" preview in team settings showing circular instead of square shape
- Pass owner avatar data through to TeamAvatar in standings, draft room, and draft board so teams with avatarType="owner" correctly show the user's avatar instead of the generated flag
- Consolidate user lookups in draft board loader to avoid duplicate DB queries
- Fix DraftSlotWithTeam interface to use RawFlagConfig type and include logoUrl/flagConfig/avatarType fields

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:03:34 -07:00
Chris Parsons
05fe1493a3
Refactor league settings into per-section components (#379)
* Refactor league settings into per-section components (#347)

Extract all settings sections from the monolithic 1009-line route file into
individual components under app/components/league/settings/. Route file drops
to ~300 lines. Separates draft-order dirty state from general settings dirty
state, deduplicates section-change handling, and fixes several bugs found
during review (typo in pointsFor5th, wrong mock in tests, lint violations).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix Stop hook loop: suppress output on typecheck success

The Stop hook was producing stdout on every run, causing Claude Code to
feed it back as context and rewake the model each turn. Now emits a
systemMessage JSON only on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Typescript fix

* Remove type asserting

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:19:50 -07:00
Chris Parsons
d0998458c9
Fix horizontal scroll on league page from long event names (#360)
Add min-w-0 to Link wrapper and overflow-hidden to SportRow flex
container so grid/flex children can shrink below intrinsic content
size, allowing truncate to work properly on mobile.
2026-04-30 10:51:14 -07:00
Chris Parsons
5268e07365
Migrate auth from Clerk to BetterAuth (#354)
* Fix BetterAuth field mapping and add owner-prefixed team names

- Fix auth.server.ts: use camelCase Drizzle field names for BetterAuth
  adapter (was snake_case, causing user inserts to fail); add
  generateId: "uuid" so PostgreSQL UUID columns accept generated IDs
- Add prependOwnerToTeamName / stripOwnerFromTeamName utilities
- Invite join, league creation, and settings assign/remove all now
  manage the owner-prefix on team names atomically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Complete BetterAuth migration: auth flows, rename, and cleanup

- Add /forgot-password and /reset-password pages (full password reset flow)
- Add 'Forgot password?' link on login page
- Fix register.tsx: add explicit window.location fallback after signUp
- Rename commissionerAuditLog.actorClerkId → actorUserId (migration 0084)
  and update all references in model, routes, components, and tests
- Rewrite docs/agents/auth.md to document BetterAuth (removes all Clerk refs)
- Update test fixtures and schema comments to remove Clerk ID references;
  use UUID-format IDs throughout test data
- Update docs/agents/domain-models.md ownerId description

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix BetterAuth ID generation, clean up review issues

- Set generateId: false so BetterAuth omits id from inserts; add
  $defaultFn(crypto.randomUUID) to accounts/sessions/verifications
  so Drizzle fills it in (fixes null id constraint violation on signup)
- Move renameTeam to static import; rename before removeTeamOwner so
  a failed rename leaves owner intact rather than leaving a stale prefix
- Clarify stripOwnerFromTeamName toTitleCase assumption in comment
- Annotate reset-password token fallback to explain loader guarantee

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:03:50 -07:00
Chris Parsons
e201ecd28a
Extract reusable league wizard components; wire settings page (#350)
* Extract reusable league wizard components; wire settings page (#103)

Extracts 9 domain components from new.tsx and $leagueId.settings.tsx into
app/components/league/ (each with a Storybook story), consolidates wizard
form-building into wizard-state.ts, and updates the settings page to use
the shared components instead of hand-rolled duplicates. new.tsx shrinks
from ~2000 → ~1280 lines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix sports-season test: add participants to mock data

findDraftableSportsSeasons now returns participantCount, which requires
participants in the mock db response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix sports-season test: loosen makeMockDb type to Record<string, unknown>[]

typeof mockSeasons became too strict after adding participants to the mock
data, breaking inline arrays in other tests that don't include participants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 22:24:13 -07:00
Chris Parsons
dbc23f14ce
Add overnight pause for draft timers (#335)
* Add overnight pause feature for draft timers

Protects players from having their pick timer expire while asleep.
Admins configure a nightly window (league-wide or per-user timezone);
the timer freezes during that window while autodraft can still fire.

Fixes #66

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix TS error: guard getUserDisplayName against undefined user

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 22:31:52 -07:00
Chris Parsons
e8e4981465
Add opencode oxlint plugin and UI refinements (#323)
* Add opencode oxlint plugin and UI refinements

- Add .opencode/plugins/oxlint.ts to run oxlint automatically on file edits
- Refactor RankingDisplay into shared StatHelpers component
- Add tied rank display support in home loader
- Update navbar with NavLink for active state styling
- Replace .reverse() with .toReversed() in RecentPicksFeed

* Remove unused RankChangeIndicator import in LeagueRow
2026-04-24 11:11:20 -07:00
Chris Parsons
01dacbce27
Add Storybook stories for new components from staged changes (#321) 2026-04-23 22:09:25 -07:00
Chris Parsons
6926fb96bc
Cache-bust logos via Vite ?url import and extract shared stat helpers (#310)
Logo SVGs referenced by string path (/logo.svg) were cached by CDNs and
browsers after deployments. Import them with Vite's ?url suffix so the
build emits content-hashed filenames that force a refresh on change.

LeagueRow and StandingsPreview had duplicated StatColumn, StatDivider,
and rank-change indicator helpers. Extract them into a shared
StatHelpers module so both pages render rank and points identically
(#3 style with consistent rank-change arrows).
2026-04-23 13:52:34 -07:00
Chris Parsons
9ed0282fd0
New design (#309)
* 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>
2026-04-23 13:14:55 -07:00