* 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>
Primary fix: setTeamTimers now bails out with `return prev` when the
value hasn't changed, preventing a full DraftRoom re-render on every
1-second timer tick (was 33% of profiler samples).
Memoization: wrap AvailableParticipantsSection, TeamsDraftedGrid,
QueueSection, SidebarRecentPicks, and DraftGridSection in React.memo
so timer ticks don't cascade into heavy components that don't use
timer state.
Stable refs: wrap nine action handlers in useCallback and extract two
inline arrow functions from props objects so memo() comparisons
actually bail out. Memoize the { numFlexPicks } object passed to
TeamsDraftedGrid.
socketVersion: expose an incrementing counter from useDraftSocket so
the socket handler effect re-registers on socket recreation.
Async cleanup: add abort flag + in-flight guard to the visibilitychange
JWT refresh handler to prevent concurrent executions and stale state
updates after unmount. Add abort flag to useDraftNotifications
permissions.query() to prevent dangling onchange if unmounted
mid-promise.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add bottom nav bar (Lobby/Board/Roster/Controls) with 56px touch targets
- Add mobile card list view in AvailableParticipantsSection with 44px Draft/Queue buttons
- Add round labels and sticky header row to DraftGridSection grid
- Add mobile Sheet for commissioner actions in DraftGridSection (replaces right-click context menu)
- Use useMediaQuery to render a single layout tree, eliminating duplicate component
instances (previously both desktop and mobile layouts were always in the DOM)
- Add compact header on mobile (text-lg vs text-3xl); hide commissioner buttons
and Exit link in header on mobile — surfaced in Controls tab instead
- Add "On Clock" bar on mobile showing current team and timer during active draft
- Add "your turn" indicator dot on Lobby bottom nav tab
- Default mobile tab to "board" for commissioner-only viewers
- Fix sticky corner cell z-index in TeamsDraftedGrid (z-10 → z-20)
- Fix round column spacer in DraftGridSection header not being sticky-left
- Extract shared prop objects to eliminate copy-paste between layouts
- Extract getParticipantState() helper to deduplicate participant render logic
- Move MobileSheetData type and MOBILE_TABS array to module scope
- Remove unnecessary non-null assertions in DraftGridSection Sheet handlers
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Show manager username instead of team name on draft board
Both the read-only draft board and the active draft room now display
the manager's username (falling back to displayName if no username is
set, and to team name if the team is unassigned) in the column headers
of the draft grid.
https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1
* Address code review: extract ownerMap helper, fix TeamsDraftedGrid
- Extract ownerMap building into app/lib/owner-map.ts to eliminate
duplication across the two loaders
- Guard against null username AND displayName so the map only contains
real string values
- Apply username display to TeamsDraftedGrid (the "Teams" tab in the
active draft room), which was missed in the initial change
https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix flex pick count using unique sports drafted, not total season sports
The flex count was calculated as `totalPicks - totalSportsInSeason`, which
always returned 0 when a team drafted multiple participants from the same
sport (e.g. 2 picks from UEFA Champions League with 2 sports in season =
2 - 2 = 0). Now uses the number of unique sports the team has actually
drafted from, so 2 picks in 1 sport correctly shows 1 flex used.
https://claude.ai/code/session_01DZ6jqgZTDEmJucanBtRCtM
* Code review fixes for TeamsDraftedGrid and draft route
- Fix misleading early-return message: "No picks have been made yet" only
fires when there are no sports configured, so update to say "No sports
have been configured for this season."
- Remove unused draftOrder and team.seasonId from TeamsDraftedGrid props
- Replace availableParticipants prop with sports prop to eliminate
duplicate sports derivation; route already computes seasonSportsData
- Add alphabetical sort to seasonSportsData so ordering is consistent
- Fix availableParticipants: any[] in loader — restructure to ternary so
TypeScript infers the Drizzle select type directly
- Remove any annotation from seasonSportsData forEach callback (now typed)
- Fix teamSportPicks.indexOf(pick) O(n) re-scan — use map callback index i
https://claude.ai/code/session_01DZ6jqgZTDEmJucanBtRCtM
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix flex spot count in draft room Teams Drafted view
The flexSpots column in the database defaults to 0 and was never
populated, causing the Teams Drafted grid to always show "0 of 0 flex".
Calculate numFlexPicks dynamically in the loader as
max(0, draftRounds - seasonSports.length) so teams see the correct
flex count (e.g. 7 rounds - 5 sports = 2 flex).
https://claude.ai/code/session_01CSzF4aWuyppGBDqM4MJmhc
* Clean up draft room flex calculation area
- Fix bug: force pick dialog now uses its own isolated search/sport
filter state so it no longer mutates the main participants tab filters
- TeamsDraftedGrid: remove dead first pass in flexPicksByTeam memo,
trim season prop to numFlexPicks only, drop unused type field from
picks sport shape
- Loader: derive userAutodraftSettings from already-loaded
autodraftSettings list, eliminating a redundant DB query
- Extract shared transformedPicks, transformedParticipants, and
seasonSportsData into their own useMemos so both eligibility memos
reuse them instead of duplicating the work
- Memoize draftGrid, draftedParticipantIds, and uniqueSports which
were being recomputed on every render
https://claude.ai/code/session_01CSzF4aWuyppGBDqM4MJmhc
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Added AvailableParticipantsSection for participant search and filtering.
- Created DraftGridSection for displaying the draft grid with team timers and context menu.
- Developed QueueSection for managing participant queue and autodraft settings.
- Introduced RecentPicksSection to show recent draft picks.
- Implemented TeamsDraftedGrid to visualize drafted participants by team and sport.
- Added collapsible UI components for better layout management.
- Established tabbed navigation for draft board, recent picks, and teams drafted.
- Enhanced responsiveness for mobile and desktop views.
- Updated styles for a cohesive design across components.