fix(#76): correct frontend availability, retry, and live recovery semantics

Make response freshness React Query-safe, retry the failed Weekend/Preview/Briefing resources with busy gating, preserve distinct embedded Preview notices, and clear fatal Live errors once SSE supplies usable timing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 20:43:51 -04:00
parent eabf87a2b8
commit 01d291507d
17 changed files with 659 additions and 63 deletions

View File

@@ -10,7 +10,12 @@ import {
fetchTrackOutline,
} from '../api'
import { DataNotice, RouteState } from '../components/RouteState'
import { noticeFromResponse, noticeMessage } from '../lib/availability'
import {
noticeFromResponse,
noticeMessage,
shouldShowEmbeddedNotice,
type DataAvailability,
} from '../lib/availability'
import { userFacingError } from '../lib/fetch'
import { countryAccent, countryFlag, formatGpDateRange } from '../lib/gpIdentity'
import {
@@ -272,12 +277,18 @@ export interface RacePreviewPageProps {
season?: number
/** Embedded under Weekend — identity failures stay non-blocking. */
embedded?: boolean
/**
* Availability already disclosed by the Weekend shell. Embedded Preview
* suppresses only an equivalent notice kind; distinct truth stays visible.
*/
shellAvailability?: DataAvailability | null
}
export function RacePreviewPage({
meeting: canonicalMeeting,
season: canonicalSeason,
embedded = false,
shellAvailability = null,
}: RacePreviewPageProps = {}) {
const [now, setNow] = useState(() => Date.now())
@@ -398,6 +409,17 @@ export function RacePreviewPage({
noticeFromResponse(sessionsQuery.data, { includeLocal: false }) ??
noticeFromResponse(priorResultsQuery.data, { includeLocal: false })
const showFreshnessNotice =
Boolean(dataNotice) &&
(!embedded || shouldShowEmbeddedNotice(dataNotice, shellAvailability))
const sessionsFailed = sessionsQuery.isError
const sessionsRetrying = sessionsQuery.isFetching
const retrySessions = () => {
if (!sessionsQuery.isFetching) void sessionsQuery.refetch()
}
if (identityLoading) {
return (
<div className={embedded ? 'preview-embedded' : 'page'} data-testid="preview-loading">
@@ -455,8 +477,7 @@ export function RacePreviewPage({
data-meeting-key={previewMeeting.meeting_key}
data-embedded={embedded ? 'true' : undefined}
>
{/* Weekend shell owns availability disclosure when Preview is embedded. */}
{!embedded && dataNotice && (
{showFreshnessNotice && dataNotice && (
<DataNotice
availability={dataNotice}
message={noticeMessage(dataNotice)}
@@ -464,6 +485,18 @@ export function RacePreviewPage({
/>
)}
{sessionsFailed && (
<RouteState
kind="error"
title="Session schedule unavailable"
error={sessionsQuery.error}
onRetry={retrySessions}
retrying={sessionsRetrying}
testId="preview-sessions-error"
retryTestId="preview-sessions-retry"
/>
)}
{/* Embedded Weekend already shows the canonical countdown header — skip the duplicate. */}
{!embedded && (
<PreviewHeader
@@ -475,6 +508,20 @@ export function RacePreviewPage({
/>
)}
{/* Embedded: still surface recovered schedule once sessions load. */}
{embedded && sessions.length > 0 && (
<div className="preview-schedule" data-testid="preview-schedule">
{sessions.map((session) => (
<div key={session.session_key} className="preview-schedule-item">
<span className="preview-schedule-name">{session.session_name}</span>
<span className="preview-schedule-time">
{formatSessionScheduleTime(session.date_start)}
</span>
</div>
))}
</div>
)}
<div className="preview-grid">
<TrackOutlineCard
outline={trackOutlineQuery.data}