/** Setup and search readiness are independent: media downloads need not block text. */ export type SemanticCoverage = "text_only" | "full_media"; export interface SemanticSetupStatus { ready: boolean; generation: number | null; pending: number; downloading: boolean; warming: boolean; embedding: boolean; error: string | null; indexing_ready?: boolean; text_model_ready?: boolean; media_model_ready?: boolean; media_model_error?: string | null; requested_coverage?: SemanticCoverage; } export function semanticIndexReady(status: SemanticSetupStatus) { return Boolean( status.ready && !status.warming && !status.embedding && (status.indexing_ready ?? !status.downloading), ); } /** Promotion must receive its own automatic attempt even with unchanged counts. */ export function semanticIndexKey( status: SemanticSetupStatus, folderRevision: number, ) { return `${status.generation}:${status.pending}:${folderRevision}:${status.requested_coverage ?? "full_media"}`; } export function semanticRetryVisible(status: SemanticSetupStatus) { return Boolean(status.media_model_error || (status.error && !status.ready)); } export function semanticMediaReady(status: SemanticSetupStatus) { // Legacy capture fixtures and older full-pair status did not expose this field. return status.media_model_ready ?? (status.ready && !status.downloading); }