2026-05-25 01:10:44 -04:00
|
|
|
export interface DatasetInfo {
|
2026-05-25 14:33:14 -04:00
|
|
|
status: 'available' | 'missing' | 'skipped'
|
|
|
|
|
source: 'local' | 'openf1' | 'none' | 'na'
|
2026-05-25 01:10:44 -04:00
|
|
|
count?: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Meeting {
|
|
|
|
|
meeting_key: number
|
|
|
|
|
meeting_name: string
|
|
|
|
|
meeting_official_name: string
|
|
|
|
|
location: string
|
|
|
|
|
country_name: string
|
|
|
|
|
country_code: string
|
|
|
|
|
country_flag: string
|
2026-07-11 17:59:00 -04:00
|
|
|
circuit_key?: number
|
2026-05-25 01:10:44 -04:00
|
|
|
circuit_short_name: string
|
|
|
|
|
date_start: string
|
|
|
|
|
date_end: string
|
|
|
|
|
year: number
|
2026-07-03 02:57:18 -04:00
|
|
|
is_cancelled?: boolean
|
2026-05-25 01:10:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Session {
|
|
|
|
|
session_key: number
|
|
|
|
|
session_name: string
|
|
|
|
|
session_type: string
|
2026-07-11 17:59:00 -04:00
|
|
|
circuit_key?: number
|
2026-05-25 01:10:44 -04:00
|
|
|
meeting_key: number
|
|
|
|
|
date_start: string
|
|
|
|
|
date_end: string
|
|
|
|
|
gmt_offset: string
|
2026-07-03 02:57:18 -04:00
|
|
|
is_cancelled?: boolean
|
2026-05-25 01:10:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Driver {
|
|
|
|
|
driver_number: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
full_name: string
|
|
|
|
|
first_name: string
|
|
|
|
|
last_name: string
|
|
|
|
|
team_name: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
headshot_url: string
|
|
|
|
|
broadcast_name: string
|
|
|
|
|
session_key: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface EnrichedResult {
|
|
|
|
|
driver_number: number
|
|
|
|
|
position: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
full_name: string
|
|
|
|
|
team_name: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
dnf: boolean
|
|
|
|
|
dns: boolean
|
|
|
|
|
dsq: boolean
|
|
|
|
|
duration: number | number[] | null
|
|
|
|
|
gap_to_leader: number | string | number[] | null
|
|
|
|
|
number_of_laps: number
|
|
|
|
|
points: number
|
|
|
|
|
session_key: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface EnrichedGrid {
|
|
|
|
|
driver_number: number
|
|
|
|
|
position: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
full_name: string
|
|
|
|
|
team_name: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
session_key: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
lap_duration: number | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RaceHub {
|
2026-07-03 02:57:18 -04:00
|
|
|
source: 'local' | 'partial' | 'none' | 'cancelled'
|
2026-05-25 01:10:44 -04:00
|
|
|
session_key: number
|
|
|
|
|
datasets: Record<string, DatasetInfo>
|
|
|
|
|
meeting?: Meeting
|
|
|
|
|
session?: Session
|
|
|
|
|
drivers: Driver[]
|
|
|
|
|
results: EnrichedResult[]
|
|
|
|
|
starting_grid: EnrichedGrid[]
|
2026-05-25 02:04:16 -04:00
|
|
|
stints: Stint[]
|
|
|
|
|
pit_stops: PitStop[]
|
|
|
|
|
positions: PositionSample[]
|
|
|
|
|
race_control: RaceControlMessage[]
|
|
|
|
|
weather: WeatherSample[]
|
|
|
|
|
laps: Lap[]
|
2026-07-11 18:04:44 -04:00
|
|
|
chapters: Chapter[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Chapter {
|
|
|
|
|
kind: 'start' | 'safety_car' | 'virtual_safety_car' | 'red_flag' | 'pit_phase' | 'decisive_swing' | 'finish' | string
|
|
|
|
|
title: string
|
2026-07-11 18:23:11 -04:00
|
|
|
headline: string
|
2026-07-11 18:04:44 -04:00
|
|
|
start_lap: number
|
|
|
|
|
end_lap: number
|
|
|
|
|
start_time?: string
|
|
|
|
|
end_time?: string
|
|
|
|
|
driver_numbers: number[]
|
2026-05-25 02:04:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Stint {
|
|
|
|
|
session_key: number
|
|
|
|
|
driver_number: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
stint_number: number
|
|
|
|
|
compound: string
|
|
|
|
|
lap_start: number
|
|
|
|
|
lap_end: number
|
|
|
|
|
tyre_age_at_start: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PitStop {
|
|
|
|
|
session_key: number
|
|
|
|
|
driver_number: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
lap_number: number
|
|
|
|
|
date: string
|
|
|
|
|
pit_duration: number
|
|
|
|
|
lane_duration: number
|
|
|
|
|
stop_duration: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PositionSample {
|
|
|
|
|
session_key: number
|
|
|
|
|
driver_number: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
date: string
|
|
|
|
|
position: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RaceControlMessage {
|
|
|
|
|
session_key: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
date: string
|
|
|
|
|
category: string
|
|
|
|
|
flag: string
|
|
|
|
|
message: string
|
|
|
|
|
scope: string
|
|
|
|
|
driver_number: number | null
|
|
|
|
|
lap_number: number | null
|
|
|
|
|
sector: number | null
|
|
|
|
|
qualifying_phase: number | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface WeatherSample {
|
|
|
|
|
session_key: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
date: string
|
|
|
|
|
air_temperature: number
|
|
|
|
|
track_temperature: number
|
|
|
|
|
humidity: number
|
|
|
|
|
pressure: number
|
|
|
|
|
rainfall: number
|
|
|
|
|
wind_direction: number
|
|
|
|
|
wind_speed: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Lap {
|
|
|
|
|
session_key: number
|
|
|
|
|
driver_number: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
lap_number: number
|
|
|
|
|
date_start: string
|
|
|
|
|
lap_duration: number | null
|
|
|
|
|
is_pit_out_lap: boolean
|
2026-05-25 01:10:44 -04:00
|
|
|
}
|
2026-05-25 02:31:35 -04:00
|
|
|
|
2026-07-04 00:18:10 -04:00
|
|
|
export interface CarDataSample {
|
|
|
|
|
brake: number
|
|
|
|
|
date: string
|
|
|
|
|
driver_number: number
|
|
|
|
|
drs: number
|
|
|
|
|
meeting_key: number
|
|
|
|
|
n_gear: number
|
|
|
|
|
rpm: number
|
|
|
|
|
session_key: number
|
|
|
|
|
speed: number
|
|
|
|
|
throttle: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ComparisonLap extends Lap {
|
|
|
|
|
compound?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ComparisonDriver {
|
|
|
|
|
driver_number: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
laps: ComparisonLap[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LapsComparisonResponse {
|
|
|
|
|
session_key: number
|
|
|
|
|
sc_periods: unknown[]
|
|
|
|
|
pit_laps: Record<string, number[]>
|
|
|
|
|
drivers: ComparisonDriver[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 02:31:35 -04:00
|
|
|
export interface WeekendSession {
|
|
|
|
|
session: Session
|
2026-07-03 02:57:18 -04:00
|
|
|
source: 'local' | 'partial' | 'none' | 'cancelled'
|
2026-05-25 02:31:35 -04:00
|
|
|
datasets: Record<string, DatasetInfo>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Weekend {
|
2026-07-03 02:57:18 -04:00
|
|
|
source: 'local' | 'partial' | 'none' | 'cancelled'
|
2026-05-25 02:31:35 -04:00
|
|
|
meeting_key: number
|
|
|
|
|
meeting: Meeting
|
|
|
|
|
sessions: WeekendSession[]
|
|
|
|
|
default_session_key?: number
|
|
|
|
|
}
|
2026-05-25 02:56:06 -04:00
|
|
|
|
|
|
|
|
export interface LiveStateResponse {
|
|
|
|
|
is_live: boolean
|
|
|
|
|
data: LiveStreamData | null
|
2026-07-04 01:40:58 -04:00
|
|
|
last_snapshot?: LiveStreamData | null
|
|
|
|
|
last_positions?: Record<string, LivePosition> | null
|
|
|
|
|
last_snapshot_at?: string
|
2026-05-25 02:56:06 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-03 19:34:09 -04:00
|
|
|
export interface LivePosition {
|
|
|
|
|
x: number
|
|
|
|
|
y: number
|
|
|
|
|
z: number
|
|
|
|
|
status: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveTelemetry {
|
|
|
|
|
Speed: number
|
|
|
|
|
Throttle: number
|
|
|
|
|
Brake: number
|
|
|
|
|
DRS: number
|
|
|
|
|
NGear: number
|
|
|
|
|
RPM: number
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 02:56:06 -04:00
|
|
|
export interface LiveSectorData {
|
|
|
|
|
Value: string
|
|
|
|
|
PersonalFastest: boolean
|
|
|
|
|
OverallFastest: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveDriverData {
|
|
|
|
|
RacingNumber: string
|
|
|
|
|
Position: number
|
|
|
|
|
PrevPosition: number
|
|
|
|
|
GapToLeader: string
|
|
|
|
|
Interval: string
|
|
|
|
|
LastLapTime: string
|
|
|
|
|
LastLapPB: boolean
|
|
|
|
|
LastLapOB: boolean
|
|
|
|
|
BestLapTime: string
|
|
|
|
|
BestLapPB: boolean
|
|
|
|
|
BestLapOB: boolean
|
|
|
|
|
BestLapNum: number
|
|
|
|
|
InPit: boolean
|
|
|
|
|
PitOut: boolean
|
|
|
|
|
Retired: boolean
|
|
|
|
|
KnockedOut: boolean
|
|
|
|
|
Cutoff: boolean
|
|
|
|
|
OnFlyingLap: boolean
|
|
|
|
|
NumberOfLaps: number
|
|
|
|
|
SpeedTrap: string
|
|
|
|
|
Sectors: LiveSectorData[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveDriverInfo {
|
|
|
|
|
RacingNumber: string
|
|
|
|
|
BroadcastName: string
|
|
|
|
|
Tla: string
|
|
|
|
|
TeamName: string
|
|
|
|
|
TeamColour: string
|
|
|
|
|
FirstName: string
|
|
|
|
|
LastName: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveTyreData {
|
|
|
|
|
Compound: string
|
|
|
|
|
New: boolean
|
|
|
|
|
Age: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveRCMessage {
|
|
|
|
|
Time: string
|
|
|
|
|
Category: string
|
|
|
|
|
Flag: string
|
|
|
|
|
Message: string
|
|
|
|
|
Lap: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveWeatherData {
|
|
|
|
|
AirTemp: number
|
|
|
|
|
TrackTemp: number
|
|
|
|
|
Humidity: number
|
|
|
|
|
WindSpeed: number
|
|
|
|
|
WindDir: number
|
|
|
|
|
Rainfall: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveSessionMeta {
|
|
|
|
|
MeetingName: string
|
|
|
|
|
CircuitName: string
|
|
|
|
|
SessionType: string
|
|
|
|
|
SessionName: string
|
2026-07-03 23:56:19 -04:00
|
|
|
Path: string
|
2026-05-25 02:56:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LiveStintData {
|
|
|
|
|
Compound: string
|
|
|
|
|
New: boolean
|
|
|
|
|
Laps: number
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 23:56:19 -04:00
|
|
|
export interface LiveRadioCapture {
|
|
|
|
|
Utc: string
|
|
|
|
|
RacingNumber: string
|
|
|
|
|
Path: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 02:56:06 -04:00
|
|
|
export interface LiveStreamData {
|
|
|
|
|
Drivers: Record<string, LiveDriverData>
|
|
|
|
|
DriverInfo: Record<string, LiveDriverInfo>
|
|
|
|
|
Tyres: Record<string, LiveTyreData>
|
2026-07-03 19:34:09 -04:00
|
|
|
Telemetry?: Record<string, LiveTelemetry>
|
2026-05-25 02:56:06 -04:00
|
|
|
RCMessages: LiveRCMessage[]
|
|
|
|
|
Weather: LiveWeatherData
|
|
|
|
|
Session: LiveSessionMeta
|
2026-07-03 23:56:19 -04:00
|
|
|
TeamRadio: LiveRadioCapture[]
|
2026-07-04 01:40:58 -04:00
|
|
|
SessionStatus?: string
|
2026-05-25 02:56:06 -04:00
|
|
|
TrackStatus: string
|
|
|
|
|
CurrentLap: number
|
|
|
|
|
TotalLaps: number
|
|
|
|
|
Clock: string
|
|
|
|
|
ClockRefTime: string
|
|
|
|
|
ClockExtrapolating: boolean
|
|
|
|
|
Stints: Record<string, LiveStintData[]>
|
|
|
|
|
}
|
2026-05-25 13:04:29 -04:00
|
|
|
|
2026-07-03 19:34:09 -04:00
|
|
|
export interface TrackPoint {
|
|
|
|
|
x: number
|
|
|
|
|
y: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TrackBounds {
|
|
|
|
|
minX: number
|
|
|
|
|
maxX: number
|
|
|
|
|
minY: number
|
|
|
|
|
maxY: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TrackOutline {
|
|
|
|
|
circuit_key: number
|
|
|
|
|
points: TrackPoint[]
|
|
|
|
|
bounds: TrackBounds
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-11 18:17:15 -04:00
|
|
|
export interface ReplayCarPosition {
|
|
|
|
|
x: number
|
|
|
|
|
y: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ReplayFrame {
|
|
|
|
|
t: number
|
|
|
|
|
cars: Record<string, ReplayCarPosition>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ReplayFramesResponse {
|
|
|
|
|
session_key: number
|
|
|
|
|
interval_ms: number
|
|
|
|
|
start_time: string
|
|
|
|
|
frames: ReplayFrame[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 00:09:47 -04:00
|
|
|
export interface ChampHubDriver {
|
|
|
|
|
driver_number: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
full_name: string
|
|
|
|
|
team_name: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
points: number
|
|
|
|
|
position: number
|
|
|
|
|
wins: number
|
|
|
|
|
podiums: number
|
|
|
|
|
poles: number
|
|
|
|
|
form: number[]
|
|
|
|
|
cumulative: number[]
|
2026-07-11 18:29:52 -04:00
|
|
|
round_positions: number[]
|
2026-07-03 00:09:47 -04:00
|
|
|
teammate_wins: number
|
|
|
|
|
teammate_losses: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ChampHubTeam {
|
|
|
|
|
team_name: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
points: number
|
|
|
|
|
position: number
|
|
|
|
|
wins: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ChampionshipHub {
|
|
|
|
|
season: number
|
|
|
|
|
round: number
|
|
|
|
|
total_rounds: number
|
|
|
|
|
rounds_left: number
|
|
|
|
|
last_race: string
|
|
|
|
|
round_labels: string[]
|
|
|
|
|
drivers: ChampHubDriver[]
|
|
|
|
|
teams: ChampHubTeam[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-11 18:12:00 -04:00
|
|
|
export interface DriverSummaryRound {
|
|
|
|
|
meeting_key: number
|
|
|
|
|
meeting_name: string
|
|
|
|
|
country_code: string
|
|
|
|
|
country_name: string
|
|
|
|
|
race_position: number
|
|
|
|
|
grid_position: number
|
|
|
|
|
quali_position?: number
|
|
|
|
|
points: number
|
|
|
|
|
dnf: boolean
|
|
|
|
|
dns: boolean
|
|
|
|
|
dsq: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface DriverSummary {
|
|
|
|
|
season: number
|
|
|
|
|
driver_number: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
full_name: string
|
|
|
|
|
team_name: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
headshot_url: string
|
|
|
|
|
points: number
|
|
|
|
|
position: number
|
|
|
|
|
wins: number
|
|
|
|
|
podiums: number
|
|
|
|
|
poles: number
|
|
|
|
|
form: number[]
|
|
|
|
|
cumulative: number[]
|
|
|
|
|
round_labels: string[]
|
|
|
|
|
rounds: DriverSummaryRound[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 18:09:43 -04:00
|
|
|
// ── Weekend Context ──
|
fix(#73): consume canonical Weekend Context contract and repair navigation
Address the independent review blockers on PR #80 after rebasing onto the
authoritative #72 Weekend Context API.
- Replace the invented frontend WeekendContext with the exact backend contract
(temporal_state, previous/focus/next meetings, previous_completed/active/next/
default_analysis sessions with availability). Every valid canonical payload now
maps to a designed state via a total resolveViewState; a well-formed response
can never fall through to the limited-data placeholder.
- Make the canonical read the single source of truth: useWeekendContext no longer
fans out to season/meetings/per-weekend/OpenF1/live queries. Only supplementary
championship + news reads run, and only once the canonical context resolves.
- Fix the Prepare/analysis flow: /preview is a stable alias that renders the
preparation surface (PreSessionView) instead of redirecting back to the same
between-races screen.
- One primary navigation system per breakpoint: the mobile top-bar links are
hidden so the bottom bar is the sole primary nav, and Admin is moved out of
every Primary landmark into an operator-utilities toolbar.
- Add Vitest coverage for the contract mapping, every temporal state, loading/
error/limited surfaces, the no-fanout guarantee, the /preview CTA, and the nav
hierarchy; add hermetic Playwright journeys (seeded + injected canonical
payloads), 390/768/1440 overflow checks, and Weekend visual snapshots. Retire
the stale Command Center specs/snapshots.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 18:38:08 -04:00
|
|
|
// Canonical local-first contract for the adaptive Weekend home, served verbatim
|
|
|
|
|
// by /api/v1/weekend-context (backend story #72, internal/query/context.go).
|
|
|
|
|
// The frontend consumes this shape as the single source of truth; view-model
|
|
|
|
|
// derivation lives in lib/weekendContext.ts.
|
|
|
|
|
|
|
|
|
|
// TemporalState mirrors query.TemporalState exactly (the JSON `temporal_state`).
|
|
|
|
|
export type TemporalState =
|
|
|
|
|
| 'no_season'
|
|
|
|
|
| 'between_weekends'
|
2026-07-12 18:09:43 -04:00
|
|
|
| 'pre_session'
|
|
|
|
|
| 'session_live'
|
|
|
|
|
| 'session_settling'
|
fix(#73): consume canonical Weekend Context contract and repair navigation
Address the independent review blockers on PR #80 after rebasing onto the
authoritative #72 Weekend Context API.
- Replace the invented frontend WeekendContext with the exact backend contract
(temporal_state, previous/focus/next meetings, previous_completed/active/next/
default_analysis sessions with availability). Every valid canonical payload now
maps to a designed state via a total resolveViewState; a well-formed response
can never fall through to the limited-data placeholder.
- Make the canonical read the single source of truth: useWeekendContext no longer
fans out to season/meetings/per-weekend/OpenF1/live queries. Only supplementary
championship + news reads run, and only once the canonical context resolves.
- Fix the Prepare/analysis flow: /preview is a stable alias that renders the
preparation surface (PreSessionView) instead of redirecting back to the same
between-races screen.
- One primary navigation system per breakpoint: the mobile top-bar links are
hidden so the bottom bar is the sole primary nav, and Admin is moved out of
every Primary landmark into an operator-utilities toolbar.
- Add Vitest coverage for the contract mapping, every temporal state, loading/
error/limited surfaces, the no-fanout guarantee, the /preview CTA, and the nav
hierarchy; add hermetic Playwright journeys (seeded + injected canonical
payloads), 390/768/1440 overflow checks, and Weekend visual snapshots. Retire
the stale Command Center specs/snapshots.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 18:38:08 -04:00
|
|
|
| 'between_sessions'
|
2026-07-12 18:09:43 -04:00
|
|
|
| 'post_weekend'
|
|
|
|
|
| 'season_complete'
|
|
|
|
|
|
fix(#73): consume canonical Weekend Context contract and repair navigation
Address the independent review blockers on PR #80 after rebasing onto the
authoritative #72 Weekend Context API.
- Replace the invented frontend WeekendContext with the exact backend contract
(temporal_state, previous/focus/next meetings, previous_completed/active/next/
default_analysis sessions with availability). Every valid canonical payload now
maps to a designed state via a total resolveViewState; a well-formed response
can never fall through to the limited-data placeholder.
- Make the canonical read the single source of truth: useWeekendContext no longer
fans out to season/meetings/per-weekend/OpenF1/live queries. Only supplementary
championship + news reads run, and only once the canonical context resolves.
- Fix the Prepare/analysis flow: /preview is a stable alias that renders the
preparation surface (PreSessionView) instead of redirecting back to the same
between-races screen.
- One primary navigation system per breakpoint: the mobile top-bar links are
hidden so the bottom bar is the sole primary nav, and Admin is moved out of
every Primary landmark into an operator-utilities toolbar.
- Add Vitest coverage for the contract mapping, every temporal state, loading/
error/limited surfaces, the no-fanout guarantee, the /preview CTA, and the nav
hierarchy; add hermetic Playwright journeys (seeded + injected canonical
payloads), 390/768/1440 overflow checks, and Weekend visual snapshots. Retire
the stale Command Center specs/snapshots.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 18:38:08 -04:00
|
|
|
// ContextAvailability mirrors query.ContextAvailability. Every field is present
|
|
|
|
|
// in a canonical payload except the optional `observed_at`.
|
|
|
|
|
export interface ContextAvailability {
|
|
|
|
|
schedule: string
|
|
|
|
|
live_transport: string
|
|
|
|
|
live_session: string
|
|
|
|
|
archive: string
|
|
|
|
|
local_analysis: string
|
|
|
|
|
freshness: string
|
|
|
|
|
observed_at?: string
|
|
|
|
|
limitations: string[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ContextSession mirrors query.ContextSession: a session identity coupled with
|
|
|
|
|
// its meeting and structured availability contract.
|
|
|
|
|
export interface ContextSession {
|
|
|
|
|
session: Session
|
|
|
|
|
meeting?: Meeting
|
|
|
|
|
availability: ContextAvailability
|
2026-07-12 18:09:43 -04:00
|
|
|
}
|
|
|
|
|
|
fix(#73): consume canonical Weekend Context contract and repair navigation
Address the independent review blockers on PR #80 after rebasing onto the
authoritative #72 Weekend Context API.
- Replace the invented frontend WeekendContext with the exact backend contract
(temporal_state, previous/focus/next meetings, previous_completed/active/next/
default_analysis sessions with availability). Every valid canonical payload now
maps to a designed state via a total resolveViewState; a well-formed response
can never fall through to the limited-data placeholder.
- Make the canonical read the single source of truth: useWeekendContext no longer
fans out to season/meetings/per-weekend/OpenF1/live queries. Only supplementary
championship + news reads run, and only once the canonical context resolves.
- Fix the Prepare/analysis flow: /preview is a stable alias that renders the
preparation surface (PreSessionView) instead of redirecting back to the same
between-races screen.
- One primary navigation system per breakpoint: the mobile top-bar links are
hidden so the bottom bar is the sole primary nav, and Admin is moved out of
every Primary landmark into an operator-utilities toolbar.
- Add Vitest coverage for the contract mapping, every temporal state, loading/
error/limited surfaces, the no-fanout guarantee, the /preview CTA, and the nav
hierarchy; add hermetic Playwright journeys (seeded + injected canonical
payloads), 390/768/1440 overflow checks, and Weekend visual snapshots. Retire
the stale Command Center specs/snapshots.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 18:38:08 -04:00
|
|
|
// WeekendContext mirrors query.WeekendContext (the canonical endpoint body).
|
|
|
|
|
export interface WeekendContext {
|
|
|
|
|
season?: number
|
|
|
|
|
temporal_state: TemporalState
|
|
|
|
|
previous_meeting?: Meeting
|
|
|
|
|
focus_meeting?: Meeting
|
|
|
|
|
next_meeting?: Meeting
|
|
|
|
|
previous_completed_session?: ContextSession
|
|
|
|
|
active_session?: ContextSession
|
|
|
|
|
next_session?: ContextSession
|
|
|
|
|
default_analysis_session?: ContextSession
|
|
|
|
|
championship_round: number
|
|
|
|
|
total_championship_rounds: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Weekend view model (client-only) ──
|
|
|
|
|
// The rendered Weekend home layers a small set of non-canonical UI states
|
|
|
|
|
// (loading/error) plus supplementary data (championship movers, briefing) on top
|
|
|
|
|
// of the canonical context. None of these are part of the #72 contract.
|
|
|
|
|
export type WeekendViewState =
|
|
|
|
|
| 'loading'
|
|
|
|
|
| 'error'
|
|
|
|
|
| 'no_season'
|
|
|
|
|
| 'between_weekends'
|
|
|
|
|
| 'pre_session'
|
|
|
|
|
| 'session_live'
|
|
|
|
|
| 'session_settling'
|
|
|
|
|
| 'between_sessions'
|
|
|
|
|
| 'post_weekend'
|
|
|
|
|
| 'season_complete'
|
2026-07-12 18:09:43 -04:00
|
|
|
|
|
|
|
|
export interface WeekendChampionshipMover {
|
|
|
|
|
position: number
|
|
|
|
|
driver_number: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
points: number
|
|
|
|
|
delta?: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface WeekendChampionshipImpact {
|
|
|
|
|
leaders: WeekendChampionshipMover[]
|
|
|
|
|
note?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface WeekendBriefingItem {
|
|
|
|
|
category?: string
|
|
|
|
|
title: string
|
|
|
|
|
url: string
|
|
|
|
|
source: string
|
|
|
|
|
published_at?: string
|
|
|
|
|
image_url?: string
|
|
|
|
|
}
|
|
|
|
|
|
fix(#73): consume canonical Weekend Context contract and repair navigation
Address the independent review blockers on PR #80 after rebasing onto the
authoritative #72 Weekend Context API.
- Replace the invented frontend WeekendContext with the exact backend contract
(temporal_state, previous/focus/next meetings, previous_completed/active/next/
default_analysis sessions with availability). Every valid canonical payload now
maps to a designed state via a total resolveViewState; a well-formed response
can never fall through to the limited-data placeholder.
- Make the canonical read the single source of truth: useWeekendContext no longer
fans out to season/meetings/per-weekend/OpenF1/live queries. Only supplementary
championship + news reads run, and only once the canonical context resolves.
- Fix the Prepare/analysis flow: /preview is a stable alias that renders the
preparation surface (PreSessionView) instead of redirecting back to the same
between-races screen.
- One primary navigation system per breakpoint: the mobile top-bar links are
hidden so the bottom bar is the sole primary nav, and Admin is moved out of
every Primary landmark into an operator-utilities toolbar.
- Add Vitest coverage for the contract mapping, every temporal state, loading/
error/limited surfaces, the no-fanout guarantee, the /preview CTA, and the nav
hierarchy; add hermetic Playwright journeys (seeded + injected canonical
payloads), 390/768/1440 overflow checks, and Weekend visual snapshots. Retire
the stale Command Center specs/snapshots.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 18:38:08 -04:00
|
|
|
export interface WeekendPodiumEntry {
|
|
|
|
|
position: number
|
|
|
|
|
driver_number: number
|
|
|
|
|
name_acronym: string
|
|
|
|
|
team_name: string
|
|
|
|
|
team_colour: string
|
|
|
|
|
gap: string
|
2026-07-12 18:09:43 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-25 13:04:29 -04:00
|
|
|
export interface NewsItem {
|
|
|
|
|
source: string
|
|
|
|
|
title: string
|
|
|
|
|
url: string
|
|
|
|
|
published_at?: string
|
|
|
|
|
summary?: string
|
|
|
|
|
category?: string
|
|
|
|
|
fetched_at: string
|
2026-05-25 15:16:00 -04:00
|
|
|
og_image_url?: string
|
|
|
|
|
og_description?: string
|
|
|
|
|
read_at?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ArticleContent {
|
|
|
|
|
title: string
|
|
|
|
|
byline?: string
|
|
|
|
|
excerpt?: string
|
|
|
|
|
image_url?: string
|
|
|
|
|
content: string
|
|
|
|
|
site_name?: string
|
2026-05-25 13:04:29 -04:00
|
|
|
}
|