fix: settle silent-only cases to Empty oneliner state
Introduce `TranscriptState { Pending, Silent, Content }` in
doctate-common as the canonical three-way state of a recording's
`.transcript.txt` sidecar. Previously each call-site projected the
raw `Option<String>` / `.exists()` onto its own 2-state view and the
projections disagreed: the UI treated a 0-byte silent transcript like
`Content` while the oneliner worker treated it like `Pending`,
leaving silent-only cases stuck on "generiere Titel …" forever with
no persisted oneliner.json.
`update_oneliner` now settles a silent-only case to
`OnelinerState::Empty` when no `Content` transcript exists and no
recording is still `Pending`, so the UI resolves to "unbenannt" and
recovery treats it as terminal.
Single reader: `paths::read_transcript_state`. All call-sites
(scan_recordings, compute_oneliner_display, has_pending_recordings,
all_transcripts_joined, read_recordings, enqueue_pending_for_user,
scan_m4as, cases_needing_oneliner_in, case_recordings.html) go
through the same typed abstraction and must handle `Silent` via
exhaustive match.
Regression tests:
- silent_case_empty_test: heal path settles silent-only case to
Empty without calling Ollama
- case_page_silent_only_shows_empty_not_generating: UI renders
"unbenannt", not "generiere Titel …"
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use doctate_common::TranscriptState;
|
||||
use doctate_common::oneliners::{ONELINER_FILENAME, OnelinerState};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
@@ -81,3 +82,20 @@ pub async fn read_oneliner_state(
|
||||
.map(OffsetDateTime::from);
|
||||
(Some(state), mtime)
|
||||
}
|
||||
|
||||
/// Load the `.transcript.txt` sidecar for a recording as a
|
||||
/// [`TranscriptState`].
|
||||
///
|
||||
/// `audio_stem_path` is the non-`.failed` stem path of the recording
|
||||
/// (e.g. `case_dir/2026-04-16T16-23-38Z.m4a`) — the sidecar sits next
|
||||
/// to it with extension `.transcript.txt`.
|
||||
///
|
||||
/// This is the single reader for that sidecar. Every call-site that
|
||||
/// used to do `tokio::fs::read_to_string(...).ok()` or
|
||||
/// `with_extension("transcript.txt").exists()` should go through here
|
||||
/// so the three-state semantics stay consistent across the codebase.
|
||||
pub async fn read_transcript_state(audio_stem_path: &Path) -> TranscriptState {
|
||||
let transcript_path = audio_stem_path.with_extension("transcript.txt");
|
||||
let raw = tokio::fs::read_to_string(&transcript_path).await.ok();
|
||||
TranscriptState::from_raw(raw.as_deref())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user