This commit introduces a new `Manual` variant to the `OnelinerState`
enum, allowing for manual overrides of the oneliner text. This change
affects the client and server components to handle and display this new
state.
A `OnelinerOverrideRequest` struct is also added for API requests to set
manual oneliners. New tests are included to ensure the `Manual` variant
serializes and deserializes correctly.
The `compute_oneliner_display` function in
`server/src/routes/user_web.rs` is updated to treat `Manual` states the
same as `Ready` states for display purposes. The
`cases_needing_oneliner_in` function in
`server/src/transcribe/recovery.rs` is updated to not retry cases that
are in a `Manual` state.
Pulls three pattern groups into shared helpers so a rename or wire-format
tweak edits one file instead of 10+:
- BulkAction enum in doctate-common replaces the "close"/"analyze"/"reset"
string literals that were duplicated between the bulk handler and 3
attack/CSRF test files. FromStr preserves the exact "Unbekannte Aktion"
error shape; the handler match is now exhaustive over the enum.
- join_url helper in doctate-common absorbs 7 identical
trim_end_matches+format! call sites across client-core, client-desktop,
server/transcribe (ollama, whisper), and server/analyze (llm).
- server/tests/common/artefacts.rs re-exports ONELINER_FILENAME
(doctate-common), DOCUMENT_FILE + ANALYSIS_INPUT_FILE
(doctate-server::analyze), and CLOSE_MARKER (doctate-server::paths) so
test files reference the canonical name instead of inlining literals.
Also lifts client-desktop local duplication:
- paths.rs: project_path helper collapses 4 identical ProjectDirs chains
- main.rs: or_die helper replaces 4 eprintln!+exit(1) blocks
- app.rs: named RecordingContext struct replaces (Uuid, String) tuple
at 3 sites around the ffmpeg-flush finalization path
Verification: 397 tests pass (baseline was 389; +8 for new unit tests on
BulkAction and join_url), 0 failed, 4 ignored (unchanged). clippy clean.
The /api/oneliners time window is now read per-user from users.toml
(window_hours, default 72h). Clients no longer carry a window:
client.toml oneliner_window_hours, SyncConfig.window_hours, the
?hours=N query param, and the render_case_list cutoff filter are
gone. ETag suffix keeps the effective hours so an admin edit to
users.toml invalidates client caches on the next request.
OnelinersResponse.window_hours stays in the wire format, but now
exists solely to anchor client reconciliation.
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 …"
The oneliner is now stored as `OnelinerState` enum which can represent
three states: `Ready`, `Empty` or `Error`. This allows the client to
differentiate between a case with no medical content and a case where
the oneliner generation failed.
The `OnelinerState` enum is serialized to JSON and stored in
`oneliner.json` file. The `OnelinerEntry` struct has been updated to
reflect this change.
The client-desktop application has been updated to handle the new
`OnelinerState` enum and display appropriate UI elements for each state.
The server-side code has also been updated to read and write the
`OnelinerState` enum, and to handle the new file format.
The `reset_case_artefacts` function in
`server/src/routes/case_actions.rs` has been updated to remove
`oneliner.txt` and create `oneliner.json` instead.
The tests have been updated to reflect these changes.
The TOML dependency is added to `Cargo.lock` to support configuration
file parsing.
New modules `config` and `startup` are introduced in
`doctate-client-core`:
- `config`: Handles client configuration loading and saving, including
defaults for polling intervals and window hours.
- `startup`: Contains routines for initial cleanup tasks, such as
removing stale markers and orphan audio files based on defined
retention periods.
Introduce `last_recording_at` to `OnelinerEntry` and use it as the
primary sort key for cases. This ensures that cases with recent
dictation activity are prioritized, even if their initial creation date
is older.
The logic for determining a case's activity has been updated to consider
the most recent `.m4a` file's modification time (`last_recording_at`),
falling back to `updated_at` or `created_at` if necessary.
This change also refactors the timestamp formatting and handling within
the web interface to correctly display and sort cases based on their
actual last activity time, improving user experience and data relevance.
The `now_rfc3339` function is updated to strip sub-second precision for
consistent filename generation.
Introduces a new crate `doctate-client-core` to house shared client-side
logic. This includes:
- `case_store`: Manages local case marker files and merging with server
snapshots.
- `snapshot_cache`: Placeholder for caching server responses.
- `oneliner_poller`: Placeholder for the background polling task.
The workspace configuration and `Cargo.lock` have been updated to
include the new crate.
This commit introduces a new `doctate-common` crate to the workspace.
This crate will house shared data structures and constants used by both
the `doctate-server` and any future clients.
This refactoring helps to:
- Reduce code duplication.
- Improve maintainability by centralizing common logic.
- Define a clear API contract for server-client communication.
The following modules have been added:
- `ack`: Contains `AckResponse` and `AckStatus` for server responses.
- `constants`: Defines shared constants like API endpoints and multipart
field names.
- `timestamp`: Provides utilities for handling RFC3339 timestamps,
including conversions to and from filesystem-safe strings.