mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
Distinguish FIA feed drops from true session end via terminal SessionStatus, consume canonical /api/v1/weekend-context (post-#72 rebase), poll until analysis-ready, and restore missing live-state styles plus transition/E2E/visual coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,14 +11,23 @@
|
||||
// `deriveLivePhase` collapses the first three inputs into a single UI phase so
|
||||
// the page never, for example, calls a finished session "offline" or lets a
|
||||
// dropped socket masquerade as "archive mode".
|
||||
//
|
||||
// Critical distinction (issue #74 P0): the server's `deactivate()` path moves a
|
||||
// STILL-ACTIVE snapshot into `last_snapshot` whenever the upstream FIA SignalR
|
||||
// connection ends or idles — WITHOUT changing SessionStatus. So `is_live=false`
|
||||
// alone is ambiguous. We rely on the retained snapshot's terminal SessionStatus
|
||||
// (Finished/Ended/…) as the only trustworthy "the session really ended" signal.
|
||||
// A non-terminal retained snapshot means the feed dropped, not that the session
|
||||
// finished, and must render as `disconnected` (retain snapshot, warn, recover),
|
||||
// never `settling`/archive.
|
||||
|
||||
export type TransportHealth = 'connecting' | 'connected' | 'disconnected' | 'error'
|
||||
|
||||
export type LivePhase =
|
||||
| 'connecting' // cold start: no snapshot yet, still opening the feed
|
||||
| 'live' // a session is running and streaming
|
||||
| 'disconnected' // was live, transport dropped — keep the last snapshot, warn
|
||||
| 'settling' // session ended; a final snapshot is retained, analysis pending
|
||||
| 'disconnected' // was live, transport/feed dropped — keep the last snapshot, warn
|
||||
| 'settling' // session truly ended; a final snapshot is retained, analysis pending
|
||||
| 'archive' // user opened the retained snapshot as an explicit read-only view
|
||||
| 'inactive' // no session and nothing retained — show weekend context instead
|
||||
|
||||
@@ -27,10 +36,39 @@ export interface LiveStateInputs {
|
||||
/** The feed reports an active session AND carries a snapshot for it. */
|
||||
isLive: boolean
|
||||
hasActiveSnapshot: boolean
|
||||
/** A final snapshot from the last session is retained. */
|
||||
/** A retained snapshot from the last stream is held (may be a dropped feed). */
|
||||
hasArchive: boolean
|
||||
/** The user explicitly opened the archive as a read-only timing view. */
|
||||
archiveMode: boolean
|
||||
/**
|
||||
* The retained snapshot ended on a *terminal* FIA SessionStatus. Only true
|
||||
* here means the session genuinely finished (vs. the transport dropping).
|
||||
*/
|
||||
sessionEndedCleanly: boolean
|
||||
/** We have observed this session live at least once in this page session. */
|
||||
wasLive: boolean
|
||||
/**
|
||||
* The initial `/api/v1/live/state` query has resolved. `connecting` is a
|
||||
* cold-start-only phase: once we know the current state (even if the SSE
|
||||
* transport handshake is still pending), an idle feed is `inactive`, not a
|
||||
* perpetual "connecting…" spinner.
|
||||
*/
|
||||
stateLoaded: boolean
|
||||
}
|
||||
|
||||
// Raw FIA SessionStatus values that mean the session is genuinely over. Mirrors
|
||||
// the backend `terminalSessionStatus` used by the weekend-context resolver so
|
||||
// the client and server agree on "finished".
|
||||
const TERMINAL_STATUSES = new Set(['finished', 'finalised', 'finalized', 'ended', 'aborted'])
|
||||
|
||||
function normalizeStatus(status: string | undefined | null): string {
|
||||
if (!status) return ''
|
||||
return status.replace(/[^a-zA-Z]/g, '').toLowerCase()
|
||||
}
|
||||
|
||||
/** Whether a raw FIA SessionStatus represents a genuinely completed session. */
|
||||
export function terminalSessionStatus(status: string | undefined | null): boolean {
|
||||
return TERMINAL_STATUSES.has(normalizeStatus(status))
|
||||
}
|
||||
|
||||
export function transportDown(transport: TransportHealth): boolean {
|
||||
@@ -40,10 +78,19 @@ export function transportDown(transport: TransportHealth): boolean {
|
||||
/**
|
||||
* Map transport + session + archive inputs to one UI phase. Session lifecycle
|
||||
* and transport health are deliberately orthogonal: a live session with a
|
||||
* dropped socket is `disconnected` (snapshot retained), never `archive`.
|
||||
* dropped socket is `disconnected` (snapshot retained), never `archive` or
|
||||
* `settling`.
|
||||
*/
|
||||
export function deriveLivePhase(input: LiveStateInputs): LivePhase {
|
||||
const { transport, isLive, hasActiveSnapshot, hasArchive, archiveMode } = input
|
||||
const {
|
||||
transport,
|
||||
isLive,
|
||||
hasActiveSnapshot,
|
||||
hasArchive,
|
||||
archiveMode,
|
||||
sessionEndedCleanly,
|
||||
stateLoaded,
|
||||
} = input
|
||||
|
||||
// Explicit read-only archive wins — it is a user-chosen mode.
|
||||
if (archiveMode && hasArchive) return 'archive'
|
||||
@@ -54,11 +101,20 @@ export function deriveLivePhase(input: LiveStateInputs): LivePhase {
|
||||
return transportDown(transport) ? 'disconnected' : 'live'
|
||||
}
|
||||
|
||||
// No active session but we still hold the final snapshot -> settling handoff.
|
||||
if (hasArchive) return 'settling'
|
||||
// Feed reports no active session but a snapshot is retained. Distinguish a
|
||||
// genuine session end from an upstream feed drop using the *only* trustworthy
|
||||
// signal: whether the retained snapshot carries a terminal SessionStatus.
|
||||
if (hasArchive) {
|
||||
if (sessionEndedCleanly) return 'settling'
|
||||
// Non-terminal retained snapshot means the upstream feed dropped, not that
|
||||
// the session finished. Hold the last frame and warn — never settle/archive.
|
||||
return 'disconnected'
|
||||
}
|
||||
|
||||
// Nothing yet and the feed is still opening.
|
||||
if (transport === 'connecting') return 'connecting'
|
||||
// Cold start only: still opening the feed and we have not yet learned the
|
||||
// current state. Once the initial state query resolves, an idle feed is
|
||||
// `inactive`, not a perpetual "connecting…".
|
||||
if (transport === 'connecting' && !stateLoaded) return 'connecting'
|
||||
|
||||
return 'inactive'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user