fix(#75): preserve Race Hub meeting/session on Weekend return

Add an explicit Back to Weekend link that deep-links to /?meeting_key&session_key,
restore that focus on the adaptive Weekend home with a Continue analysis CTA, and
regenerate Race Hub visual baselines against the integrated #80 nav.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 19:40:04 -04:00
parent 5d5987cc7e
commit 469028b07a
20 changed files with 370 additions and 37 deletions

View File

@@ -243,6 +243,19 @@ function renderRaceHub(sessionKey: number) {
</QueryClientProvider>
),
})
const weekendRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
validateSearch: (search: Record<string, unknown>) => {
const meetingKey = Number(search.meeting_key)
const sk = Number(search.session_key)
return {
...(Number.isFinite(meetingKey) && meetingKey > 0 ? { meeting_key: meetingKey } : {}),
...(Number.isFinite(sk) && sk > 0 ? { session_key: sk } : {}),
}
},
component: () => <div data-testid="weekend-stub" />,
})
const raceHubRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/race-hub',
@@ -256,7 +269,7 @@ function renderRaceHub(sessionKey: number) {
},
})
const router = createRouter({
routeTree: rootRoute.addChildren([raceHubRoute]),
routeTree: rootRoute.addChildren([weekendRoute, raceHubRoute]),
history: undefined,
})
@@ -541,10 +554,22 @@ describe('RaceHubPage', () => {
await waitFor(() => expect(screen.getByTestId('race-hub-error')).toBeInTheDocument())
expect(screen.getByTestId('rh-retry')).toBeInTheDocument()
const back = screen.getByTestId('rh-back-weekend')
expect(back).toHaveAttribute('href', '/race-hub?session_key=9472')
expect(back).toHaveAttribute('href', expect.stringMatching(/meeting_key=1229/))
expect(back).toHaveAttribute('href', expect.stringMatching(/session_key=9472/))
expect(back.getAttribute('href')).toMatch(/^\//)
mockFetchRaceHub.mockResolvedValue(raceHub)
fireEvent.click(screen.getByTestId('rh-retry'))
await waitFor(() => expect(screen.getByTestId('race-hub')).toBeInTheDocument())
})
it('exposes a Back to Weekend link that carries meeting and session context', async () => {
renderRaceHub(9472)
await waitFor(() => expect(screen.getByTestId('race-hub')).toBeInTheDocument())
const back = screen.getByRole('link', { name: 'Back to Weekend' })
expect(back).toHaveAttribute('data-testid', 'rh-back-weekend')
expect(back).toHaveAttribute('href', expect.stringMatching(/meeting_key=1229/))
expect(back).toHaveAttribute('href', expect.stringMatching(/session_key=9472/))
})
})

View File

@@ -20,12 +20,14 @@ import type {
TemporalState,
WeekendContext,
} from '../types'
import { parseWeekendSearch } from '../lib/routeSearch'
vi.mock('../api', () => ({
fetchWeekendContext: vi.fn(),
fetchChampionshipHub: vi.fn(),
fetchNews: vi.fn(),
fetchRaceHub: vi.fn(),
fetchWeekend: vi.fn(),
// Consumed transitively by RacePreviewPage (folded into PreSessionView):
fetchSeasons: vi.fn(),
fetchMeetings: vi.fn(),
@@ -40,6 +42,7 @@ import {
fetchChampionshipHub,
fetchNews,
fetchRaceHub,
fetchWeekend,
fetchSeasons,
fetchMeetings,
fetchSessions,
@@ -52,6 +55,7 @@ const mockContext = vi.mocked(fetchWeekendContext)
const mockHub = vi.mocked(fetchChampionshipHub)
const mockNews = vi.mocked(fetchNews)
const mockRaceHub = vi.mocked(fetchRaceHub)
const mockWeekend = vi.mocked(fetchWeekend)
const mockSeasons = vi.mocked(fetchSeasons)
const mockMeetings = vi.mocked(fetchMeetings)
const mockSessions = vi.mocked(fetchSessions)
@@ -152,7 +156,15 @@ function renderAt(path: string) {
</QueryClientProvider>
),
})
const homeRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', component: () => <WeekendPage /> })
const homeRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
validateSearch: (s: Record<string, unknown>) => parseWeekendSearch(s),
component: function HomeRoute() {
const { meeting_key, session_key } = homeRoute.useSearch()
return <WeekendPage focusMeetingKey={meeting_key} focusSessionKey={session_key} />
},
})
const previewRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/preview',
@@ -192,6 +204,19 @@ describe('WeekendPage canonical contract rendering', () => {
mockHub.mockResolvedValue(hub)
mockNews.mockResolvedValue([])
mockRaceHub.mockResolvedValue(raceHub)
mockWeekend.mockResolvedValue({
source: 'local',
meeting_key: 1,
meeting: meeting(),
sessions: [
{
session: session({ session_key: 11, session_name: 'Race' }),
source: 'local',
datasets: {},
},
],
default_session_key: 11,
})
mockSeasons.mockResolvedValue([2026])
mockMeetings.mockResolvedValue([meeting()])
mockSessions.mockResolvedValue([session()])
@@ -306,4 +331,27 @@ describe('WeekendPage canonical contract rendering', () => {
// The between-races surface must NOT be what /preview renders.
expect(screen.queryByTestId('weekend-between-races')).not.toBeInTheDocument()
})
it('restores Race Hub meeting/session focus from the Weekend URL search contract', async () => {
mockContext.mockResolvedValue(
context({
temporal_state: 'season_complete',
previous_completed_session: ctxSession({ session: session({ session_key: 11, session_name: 'Race' }) }),
default_analysis_session: ctxSession({ session: session({ session_key: 11 }) }),
}),
)
renderAt('/?meeting_key=1&session_key=11')
await waitFor(() => expect(screen.getByTestId('wk-focus-context')).toBeInTheDocument())
expect(screen.getByTestId('weekend-page')).toHaveAttribute('data-meeting-key', '1')
expect(screen.getByTestId('weekend-page')).toHaveAttribute('data-session-key', '11')
await waitFor(() =>
expect(screen.getByTestId('wk-focus-meeting')).toHaveTextContent('British Grand Prix'),
)
await waitFor(() =>
expect(screen.getByTestId('wk-focus-session')).toHaveTextContent('Race'),
)
const continueAnalysis = screen.getByRole('link', { name: /Continue analysis/i })
expect(continueAnalysis).toHaveAttribute('href', expect.stringContaining('session_key=11'))
expect(mockWeekend).toHaveBeenCalledWith(1)
})
})

View File

@@ -0,0 +1,24 @@
import { describe, it, expect } from 'vitest'
import { parseRaceHubSearch, parseWeekendSearch, weekendFocusSearch } from '../lib/routeSearch'
describe('routeSearch', () => {
it('parses positive weekend meeting/session keys and drops invalid values', () => {
expect(parseWeekendSearch({ meeting_key: '1229', session_key: '9000' })).toEqual({
meeting_key: 1229,
session_key: 9000,
})
expect(parseWeekendSearch({ meeting_key: '0', session_key: '-1', foo: 'bar' })).toEqual({})
expect(parseWeekendSearch({})).toEqual({})
})
it('parses race-hub session keys', () => {
expect(parseRaceHubSearch({ session_key: '9472' })).toEqual({ session_key: 9472 })
expect(parseRaceHubSearch({ session_key: 'nope' })).toEqual({})
})
it('builds Weekend focus search for Race Hub returns', () => {
expect(weekendFocusSearch(1229, 9000)).toEqual({ meeting_key: 1229, session_key: 9000 })
expect(weekendFocusSearch(null, 9000)).toEqual({ session_key: 9000 })
expect(weekendFocusSearch(1229, undefined)).toEqual({ meeting_key: 1229 })
})
})