* 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>
* Add "Games to Score" widget to admin dashboard
Shows scoring events for today and tomorrow in a tabbed card, with
status badges (Pending/Scored), a scored/total progress counter, and
direct "Score" / "View" links to each event's results page. Excludes
non-scoring schedule_event types so only actionable events surface.
- app/models/scoring-event.ts: add getEventsForDates() model function
that queries scoringEvents joined with sportsSeasons/sport for a
given list of date strings
- app/routes/admin._index.tsx: call getEventsForDates([today, tomorrow])
in the loader and render the GamesToScore component below the stats row
- app/models/__tests__/scoring-event-dashboard.test.ts: 8 unit tests
covering empty input, shape mapping, multi-date filtering, completed
events, and the inArray where conditions
https://claude.ai/code/session_014zKK15o5RJfTaodLvnZpF5
* Fix getEventsForDates to include bracket events by game scheduledAt
The previous implementation only matched on scoringEvents.eventDate,
which is often null for bracket events where individual game times are
set on playoffMatchGames.scheduledAt instead.
Switched to a two-step query mirroring getUpcomingEventsForDraftedParticipants:
1. selectDistinct event IDs via left joins through playoffMatches →
playoffMatchGames, matching where eventDate IN dates OR any game's
scheduledAt falls within the date range's timestamp bounds
2. findMany on the matched IDs with sportsSeason + sport relations
Updated tests to cover the two-step flow, leftJoin usage, and the
scheduledAt timestamp bounds appearing in the where clause.
https://claude.ai/code/session_014zKK15o5RJfTaodLvnZpF5
* Document dual game date storage in CLAUDE.md
scoringEvents.eventDate and playoffMatchGames.scheduledAt both hold game
dates depending on the event type. Added a dedicated section under
Important Patterns explaining when each is used and the two-step
selectDistinct + findMany pattern required for correct date queries.
https://claude.ai/code/session_014zKK15o5RJfTaodLvnZpF5
---------
Co-authored-by: Claude <noreply@anthropic.com>
Every route now exports a meta function so the browser title bar reflects
the current page. Static pages use fixed titles; dynamic pages pull names
from loader data with a sensible fallback (e.g. league name, sport season
name, team name).
Titles follow the pattern "Page Name - Brackt" for user-facing routes and
"Page Name - Brackt Admin" for admin routes.
Fixes#74
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>