agent: code-atomiser-fix — soft-delete transcripts with audio cleanup (Rev-2, Rev-3)
Two interlocking reversibility kills, fixed as one bundle:
Rev-2 (hard-DELETE transcripts, no trash) — delete_transcript previously
issued DELETE FROM transcripts WHERE id = ?, so a single click on the
History "Clear All" / "Confirm" button erased months of dictation with no
trash, no export, no undo. The new contract:
* delete_transcript UPDATEs deleted_at = datetime('now') and best-effort
removes the audio file. Idempotent on repeat call.
* Migration v16 adds `deleted_at TEXT` plus a partial index over the trash
rows so the purge query stays cheap on long-running databases.
* get_transcript, list_transcripts_paged, count_transcripts, and
search_transcripts all filter `deleted_at IS NULL` so trash rows are
invisible to the regular history view but kept for restore.
* New list_trashed_transcripts and restore_transcript power the inverse
trash view; row order is most-recently-deleted first.
* New purge_deleted_transcripts(older_than_days) hard-removes trash
older than the retention window. Wired into the Tauri setup hook at
30-day retention; best-effort, never blocks startup.
Rev-3 (orphan WAV files on transcript delete) — same delete_transcript
function. Audio file at audio_path is now best-effort removed when the
soft-delete actually flips a row; NotFound is the expected case for
repeat deletes and is not logged. purge_deleted_transcripts also retries
audio removal as belt-and-braces.
Adds 4 regression tests: delete_transcript_soft_deletes,
delete_transcript_removes_audio_file, list_transcripts_excludes_soft_deleted
(also covers restore_transcript), and purge_deleted_transcripts_hard_deletes_old.
Plus migration_v16_adds_deleted_at_column_and_index already in place.
Frontend UI (HistoryPage clearAll type-the-word modal + View trash path)
deferred to a follow-up commit; the backend contract is complete and the
existing arm-confirm flow now soft-deletes instead of hard-deleting, so
the user-data-loss class is closed for the live release path. Rev-4
(SettingsPage deleteProfile routing) also deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,13 +25,19 @@ use lumotia_core::types::EngineName;
|
||||
use lumotia_llm::LlmEngine;
|
||||
use lumotia_storage::{
|
||||
database_path, get_setting, init as init_db, migrate_legacy_setting_keys, prune_error_log,
|
||||
set_setting,
|
||||
purge_deleted_transcripts, set_setting,
|
||||
};
|
||||
|
||||
/// How long to retain `error_log` rows. Pruned once on startup.
|
||||
/// 90 days is long enough to triage "this happened a few weeks ago"
|
||||
/// reports and short enough that the table stays kilobyte-scale.
|
||||
const ERROR_LOG_RETENTION_DAYS: i64 = 90;
|
||||
|
||||
/// How long soft-deleted transcripts live in the trash before
|
||||
/// `purge_deleted_transcripts` hard-removes them on startup. 30 days
|
||||
/// gives the user a comfortable undo window without keeping months of
|
||||
/// abandoned audio on disk.
|
||||
const TRANSCRIPT_TRASH_RETENTION_DAYS: i64 = 30;
|
||||
use lumotia_transcription::LocalEngine;
|
||||
|
||||
static TRACING_INIT: Once = Once::new();
|
||||
@@ -491,6 +497,26 @@ pub fn run() {
|
||||
Err(e) => tracing::warn!(target: "lumotia_startup", error = %e, "error log prune failed"),
|
||||
}
|
||||
|
||||
// Hard-delete soft-deleted transcripts past the retention
|
||||
// window (Rev-2 atomiser fix). Best-effort — a purge failure
|
||||
// means trash rows stick around an extra day, not a blocker.
|
||||
let t_purge_trash = Instant::now();
|
||||
match purge_deleted_transcripts(&db, TRANSCRIPT_TRASH_RETENTION_DAYS).await {
|
||||
Ok(n) if n > 0 => tracing::info!(
|
||||
target: "lumotia_startup",
|
||||
rows_removed = n,
|
||||
retention_days = TRANSCRIPT_TRASH_RETENTION_DAYS,
|
||||
elapsed_ms = t_purge_trash.elapsed().as_millis(),
|
||||
"transcript trash purge complete"
|
||||
),
|
||||
Ok(_) => {}
|
||||
Err(e) => tracing::warn!(
|
||||
target: "lumotia_startup",
|
||||
error = %e,
|
||||
"transcript trash purge failed — trash rows remain for next startup"
|
||||
),
|
||||
}
|
||||
|
||||
// Load saved preferences for webview injection.
|
||||
let t1 = Instant::now();
|
||||
let prefs_json = get_setting(&db, "lumotia_preferences").await.unwrap_or(None);
|
||||
|
||||
Reference in New Issue
Block a user