// Ordering rules remain plugin-private because revisions are vendor runtime state. import type { XgridsCameraPreviewState, XgridsK1State } from "./api"; function monotonicInteger(value: number | null | undefined): number | null { return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null; } function deviceSessionScope(state: XgridsK1State): string | null { const sessionId = state.device_session?.device_session_id; return typeof sessionId === "string" && sessionId.trim() ? sessionId : null; } function cameraSnapshotIsAtLeastAsNew( current: XgridsCameraPreviewState, incoming: XgridsCameraPreviewState, ): boolean { const currentRevision = monotonicInteger(current.revision); const incomingRevision = monotonicInteger(incoming.revision); if (currentRevision !== null || incomingRevision !== null) { if (incomingRevision === null) return false; if (currentRevision === null) return true; if (incomingRevision !== currentRevision) return incomingRevision > currentRevision; } const currentGeneration = monotonicInteger(current.generation); const incomingGeneration = monotonicInteger(incoming.generation); if (currentGeneration === null) return true; if (incomingGeneration === null) return false; return incomingGeneration >= currentGeneration; } /** * Select an authoritative runtime snapshot without allowing asynchronous REST, * mutation, or event responses to roll camera preview state backwards. * * Revision is the primary ordering key. In particular, a stop snapshot with a * newer revision and a null generation is valid and must replace the active * generation. Generation is only a tie-breaker when revisions are equal or * absent. */ export function selectMonotonicXgridsState( current: XgridsK1State | null, incoming: XgridsK1State, ): XgridsK1State { if (current && deviceSessionScope(current) !== deviceSessionScope(incoming)) { return incoming; } if (!current?.camera_preview) return incoming; if (!incoming.camera_preview) return current; // Backend snapshots are atomic. Reject the whole stale response instead of // combining camera/catalog/metrics fields captured at different moments. return cameraSnapshotIsAtLeastAsNew(current.camera_preview, incoming.camera_preview) ? incoming : current; }