diff --git a/crates/audio/src/capture.rs b/crates/audio/src/capture.rs index 3d85c12..75a69ad 100644 --- a/crates/audio/src/capture.rs +++ b/crates/audio/src/capture.rs @@ -370,7 +370,11 @@ fn open_and_validate( device: cpal::Device, name: &str, require_audio: bool, -) -> Result<(MicrophoneCapture, VecDeque, mpsc::Receiver)> { +) -> Result<( + MicrophoneCapture, + VecDeque, + mpsc::Receiver, +)> { let config = device .default_input_config() .map_err(|e| Error::AudioCaptureFailed(format!("default_input_config: {e}")))?; diff --git a/crates/audio/src/concurrency.rs b/crates/audio/src/concurrency.rs index 85fb004..c74a7c3 100644 --- a/crates/audio/src/concurrency.rs +++ b/crates/audio/src/concurrency.rs @@ -15,7 +15,5 @@ pub async fn decode_and_resample(path: &Path) -> Result { resample_to_16khz(&audio) }) .await - .map_err(|e| { - lumotia_core::error::Error::AudioDecodeFailed(format!("Task join error: {e}")) - })? + .map_err(|e| lumotia_core::error::Error::AudioDecodeFailed(format!("Task join error: {e}")))? } diff --git a/crates/audio/src/decode.rs b/crates/audio/src/decode.rs index e1f62ff..8427123 100644 --- a/crates/audio/src/decode.rs +++ b/crates/audio/src/decode.rs @@ -28,8 +28,8 @@ pub fn decode_audio_file_limited( path: &Path, max_duration_secs: Option, ) -> Result { - let file = File::open(path) - .map_err(|e| Error::AudioDecodeFailed(format!("Cannot open file: {e}")))?; + let file = + File::open(path).map_err(|e| Error::AudioDecodeFailed(format!("Cannot open file: {e}")))?; let mss = MediaSourceStream::new(Box::new(file), Default::default()); let mut hint = Hint::new(); @@ -41,8 +41,8 @@ pub fn decode_audio_file_limited( } pub fn probe_audio_duration_secs(path: &Path) -> Result> { - let file = File::open(path) - .map_err(|e| Error::AudioDecodeFailed(format!("Cannot open file: {e}")))?; + let file = + File::open(path).map_err(|e| Error::AudioDecodeFailed(format!("Cannot open file: {e}")))?; let mss = MediaSourceStream::new(Box::new(file), Default::default()); let mut hint = Hint::new(); if let Some(ext) = path.extension().and_then(|e| e.to_str()) { @@ -99,9 +99,7 @@ fn decode_media_stream( .ok_or_else(|| Error::AudioDecodeFailed("Unknown sample rate".into()))?; if sample_rate == 0 { - return Err(Error::AudioDecodeFailed( - "Invalid sample rate: 0".into(), - )); + return Err(Error::AudioDecodeFailed("Invalid sample rate: 0".into())); } let track_id = track.id; @@ -128,9 +126,7 @@ fn decode_media_stream( )); } Err(e) => { - return Err(Error::AudioDecodeFailed(format!( - "packet read failed: {e}" - ))); + return Err(Error::AudioDecodeFailed(format!("packet read failed: {e}"))); } }; @@ -168,9 +164,7 @@ fn decode_media_stream( } if samples.is_empty() { - return Err(Error::AudioDecodeFailed( - "No audio data decoded".into(), - )); + return Err(Error::AudioDecodeFailed("No audio data decoded".into())); } Ok(AudioSamples::new(samples, sample_rate, 1)) diff --git a/crates/audio/src/streaming_resample.rs b/crates/audio/src/streaming_resample.rs index 482dbb4..6cb1ea9 100644 --- a/crates/audio/src/streaming_resample.rs +++ b/crates/audio/src/streaming_resample.rs @@ -77,9 +77,7 @@ impl StreamingResampler { INPUT_CHUNK, 1, // mono ) - .map_err(|e| { - Error::AudioDecodeFailed(format!("StreamingResampler init failed: {e}")) - })?; + .map_err(|e| Error::AudioDecodeFailed(format!("StreamingResampler init failed: {e}")))?; Ok(Self::Sinc { resampler, @@ -110,9 +108,7 @@ impl StreamingResampler { let chunk: Vec = residual.drain(..INPUT_CHUNK).collect(); let input = vec![chunk]; let result = resampler.process(&input, None).map_err(|e| { - Error::AudioDecodeFailed(format!( - "StreamingResampler process failed: {e}" - )) + Error::AudioDecodeFailed(format!("StreamingResampler process failed: {e}")) })?; if let Some(channel) = result.into_iter().next() { out.extend_from_slice(&channel); @@ -144,9 +140,7 @@ impl StreamingResampler { let input = vec![chunk]; let result = resampler.process(&input, None).map_err(|e| { - Error::AudioDecodeFailed(format!( - "StreamingResampler flush failed: {e}" - )) + Error::AudioDecodeFailed(format!("StreamingResampler flush failed: {e}")) })?; let Some(mut out) = result.into_iter().next() else { diff --git a/crates/audio/src/wav.rs b/crates/audio/src/wav.rs index a71aab3..8cab9d7 100644 --- a/crates/audio/src/wav.rs +++ b/crates/audio/src/wav.rs @@ -42,9 +42,8 @@ impl WavWriter { }; let file = std::fs::File::create(path).map_err(Error::from)?; let buffered = BufWriter::new(file); - let inner = hound::WavWriter::new(buffered, spec).map_err(|e| { - Error::from(std::io::Error::other(format!("WAV create failed: {e}"))) - })?; + let inner = hound::WavWriter::new(buffered, spec) + .map_err(|e| Error::from(std::io::Error::other(format!("WAV create failed: {e}"))))?; Ok(Self { inner, samples_since_flush: 0, @@ -77,9 +76,9 @@ impl WavWriter { /// `Self::DEFAULT_FLUSH_EVERY_SAMPLES` — but may do so at natural /// boundaries (end-of-utterance, UI events) for tighter recovery. pub fn flush(&mut self) -> Result<()> { - self.inner.flush().map_err(|e| { - Error::from(std::io::Error::other(format!("WAV flush failed: {e}"))) - })?; + self.inner + .flush() + .map_err(|e| Error::from(std::io::Error::other(format!("WAV flush failed: {e}"))))?; self.samples_since_flush = 0; Ok(()) } @@ -89,9 +88,9 @@ impl WavWriter { /// writer leaves a playable file up to the last flush; callers /// that care about the unflushed tail should always finalise. pub fn finalize(self) -> Result<()> { - self.inner.finalize().map_err(|e| { - Error::from(std::io::Error::other(format!("WAV finalize failed: {e}"))) - })?; + self.inner + .finalize() + .map_err(|e| Error::from(std::io::Error::other(format!("WAV finalize failed: {e}"))))?; Ok(()) } } @@ -105,21 +104,20 @@ pub fn write_wav(path: &Path, audio: &AudioSamples) -> Result<()> { sample_format: hound::SampleFormat::Int, }; - let mut writer = hound::WavWriter::create(path, spec).map_err(|e| { - Error::from(std::io::Error::other(format!("WAV create failed: {e}"))) - })?; + let mut writer = hound::WavWriter::create(path, spec) + .map_err(|e| Error::from(std::io::Error::other(format!("WAV create failed: {e}"))))?; for &sample in audio.samples() { let clamped = sample.clamp(-1.0, 1.0); let int_sample = (clamped * i16::MAX as f32) as i16; - writer.write_sample(int_sample).map_err(|e| { - Error::from(std::io::Error::other(format!("WAV write failed: {e}"))) - })?; + writer + .write_sample(int_sample) + .map_err(|e| Error::from(std::io::Error::other(format!("WAV write failed: {e}"))))?; } - writer.finalize().map_err(|e| { - Error::from(std::io::Error::other(format!("WAV finalize failed: {e}"))) - })?; + writer + .finalize() + .map_err(|e| Error::from(std::io::Error::other(format!("WAV finalize failed: {e}"))))?; Ok(()) } @@ -146,17 +144,14 @@ pub fn read_wav(path: &Path) -> Result { .map(|sample| { sample .map(|s| s as f32 / (1 << (bits_per_sample - 1)) as f32) - .map_err(|e| { - Error::AudioDecodeFailed(format!("WAV sample decode failed: {e}")) - }) + .map_err(|e| Error::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))) }) .collect::>>()?, hound::SampleFormat::Float => reader .into_samples::() .map(|sample| { - sample.map_err(|e| { - Error::AudioDecodeFailed(format!("WAV sample decode failed: {e}")) - }) + sample + .map_err(|e| Error::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))) }) .collect::>>()?, }; diff --git a/crates/hotkey/src/linux.rs b/crates/hotkey/src/linux.rs index c37e2f5..28fb3da 100644 --- a/crates/hotkey/src/linux.rs +++ b/crates/hotkey/src/linux.rs @@ -411,7 +411,9 @@ async fn try_attach_device( // Register with the supervisor. This await is brief — it just locks // the supervisor inner Vec and pushes — and happens outside the // tracked-map lock. - supervisor.register("device-listener", listener_handle).await; + supervisor + .register("device-listener", listener_handle) + .await; true } diff --git a/crates/hotkey/tests/listener_lifecycle.rs b/crates/hotkey/tests/listener_lifecycle.rs index 6a4aa24..4d63500 100644 --- a/crates/hotkey/tests/listener_lifecycle.rs +++ b/crates/hotkey/tests/listener_lifecycle.rs @@ -143,8 +143,7 @@ async fn reconfigure_does_not_leak_forwarder() { // (orphaned listener tasks still holding sender clones), the // forwarder would never see `None` from recv() and this timeout // would fire. - let join_result = - tokio::time::timeout(Duration::from_secs(5), forwarder_1).await; + let join_result = tokio::time::timeout(Duration::from_secs(5), forwarder_1).await; assert!( join_result.is_ok(), "old forwarder did not join after listener.stop() — Race-2 regressed: \ @@ -176,8 +175,7 @@ async fn reconfigure_does_not_leak_forwarder() { // ---- Cleanup ---- listener_2.stop().await; - let cleanup_join = - tokio::time::timeout(Duration::from_secs(5), forwarder_2).await; + let cleanup_join = tokio::time::timeout(Duration::from_secs(5), forwarder_2).await; assert!( cleanup_join.is_ok(), "second forwarder also failed to drain after stop()" @@ -189,5 +187,8 @@ async fn reconfigure_does_not_leak_forwarder() { // count. The counters exist so the test compiles as a real // forwarder pattern matching what commands::hotkey does in // production. - let _ = (received_first.load(Ordering::SeqCst), received_second.load(Ordering::SeqCst)); + let _ = ( + received_first.load(Ordering::SeqCst), + received_second.load(Ordering::SeqCst), + ); } diff --git a/crates/llm/src/lib.rs b/crates/llm/src/lib.rs index 8ee6051..f215f58 100644 --- a/crates/llm/src/lib.rs +++ b/crates/llm/src/lib.rs @@ -256,7 +256,7 @@ impl LlmEngine { } /// True iff a model load is currently in flight. Exposed for tests - /// + frontends that want to render a "loading…" state without + /// and frontends that want to render a "loading…" state without /// polling `is_loaded()` (which returns false during a swap). pub fn is_loading(&self) -> bool { self.loading.load(Ordering::Acquire) @@ -630,7 +630,7 @@ fn extract_json_envelope(text: &str) -> Option<&str> { let mut in_string = false; let mut escaped = false; - while let Some((offset, ch)) = chars.next() { + for (offset, ch) in chars { if in_string { if escaped { escaped = false; @@ -933,8 +933,6 @@ mod tests { loader_handle.join().unwrap(); // After the first load completes, a fresh attempt is allowed. - assert!(engine - .__test_run_with_lock_discipline(|| {}) - .is_ok()); + assert!(engine.__test_run_with_lock_discipline(|| {}).is_ok()); } } diff --git a/crates/storage/src/database.rs b/crates/storage/src/database.rs index bfd5c8f..e156242 100644 --- a/crates/storage/src/database.rs +++ b/crates/storage/src/database.rs @@ -181,15 +181,13 @@ pub async fn list_transcripts_paged( /// Excludes soft-deleted (deleted_at IS NOT NULL) rows — they only count /// toward the trash view. pub async fn count_transcripts(pool: &SqlitePool) -> Result { - let n: i64 = sqlx::query_scalar( - "SELECT COUNT(*) FROM transcripts WHERE deleted_at IS NULL", - ) - .fetch_one(pool) - .await - .map_err(|source| Error::Query { - operation: "count_transcripts".into(), - source, - })?; + let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM transcripts WHERE deleted_at IS NULL") + .fetch_one(pool) + .await + .map_err(|source| Error::Query { + operation: "count_transcripts".into(), + source, + })?; Ok(n) } @@ -337,17 +335,16 @@ pub async fn delete_transcript(pool: &SqlitePool, id: &str) -> Result<()> { // (which filters deleted_at IS NULL) so a double-delete still finds // the row and the second call is a no-op cleanup rather than an // error. - let audio_path: Option = sqlx::query_scalar( - "SELECT audio_path FROM transcripts WHERE id = ?", - ) - .bind(id) - .fetch_optional(pool) - .await - .map_err(|source| Error::Query { - operation: "delete_transcript".into(), - source, - })? - .flatten(); + let audio_path: Option = + sqlx::query_scalar("SELECT audio_path FROM transcripts WHERE id = ?") + .bind(id) + .fetch_optional(pool) + .await + .map_err(|source| Error::Query { + operation: "delete_transcript".into(), + source, + })? + .flatten(); let res = sqlx::query( "UPDATE transcripts SET deleted_at = datetime('now') \ @@ -390,10 +387,7 @@ pub async fn delete_transcript(pool: &SqlitePool, id: &str) -> Result<()> { /// /// Audio files are also best-effort removed here in case the original /// soft-delete failed at the filesystem layer (Rev-3 belt-and-braces). -pub async fn purge_deleted_transcripts( - pool: &SqlitePool, - older_than_days: i64, -) -> Result { +pub async fn purge_deleted_transcripts(pool: &SqlitePool, older_than_days: i64) -> Result { // Collect (id, audio_path) BEFORE the DELETE so we still have the // paths to clean up after the row is gone. let rows = sqlx::query( @@ -429,8 +423,7 @@ pub async fn purge_deleted_transcripts( // SQLITE_MAX_VARIABLE_NUMBER (default 999). let mut total: u64 = 0; for chunk in ids.chunks(200) { - let placeholders = std::iter::repeat("?") - .take(chunk.len()) + let placeholders = std::iter::repeat_n("?", chunk.len()) .collect::>() .join(","); let sql = format!("DELETE FROM transcripts WHERE id IN ({placeholders})"); @@ -3067,7 +3060,10 @@ mod tests { assert_eq!(second, (0, 0)); } - fn minimal_transcript(id: &'static str, audio_path: Option<&'static str>) -> InsertTranscriptParams<'static> { + fn minimal_transcript( + id: &'static str, + audio_path: Option<&'static str>, + ) -> InsertTranscriptParams<'static> { InsertTranscriptParams { id, text: "soft-delete fixture", @@ -3100,14 +3096,16 @@ mod tests { delete_transcript(&pool, "t-soft").await.unwrap(); - let deleted_at: Option = sqlx::query_scalar( - "SELECT deleted_at FROM transcripts WHERE id = ?", - ) - .bind("t-soft") - .fetch_one(&pool) - .await - .unwrap(); - assert!(deleted_at.is_some(), "row should still exist with deleted_at set"); + let deleted_at: Option = + sqlx::query_scalar("SELECT deleted_at FROM transcripts WHERE id = ?") + .bind("t-soft") + .fetch_one(&pool) + .await + .unwrap(); + assert!( + deleted_at.is_some(), + "row should still exist with deleted_at set" + ); } #[tokio::test] @@ -3116,10 +3114,7 @@ mod tests { // soft-delete actually flipped a row. A repeat delete is a // no-op and does NOT fail when the file is already gone. let pool = test_pool().await; - let tmp = std::env::temp_dir().join(format!( - "lumotia-test-{}.wav", - std::process::id() - )); + let tmp = std::env::temp_dir().join(format!("lumotia-test-{}.wav", std::process::id())); std::fs::write(&tmp, b"fake wav").unwrap(); let path_owned = tmp.to_string_lossy().to_string(); let path_static: &'static str = Box::leak(path_owned.into_boxed_str()); @@ -3130,7 +3125,10 @@ mod tests { assert!(tmp.exists(), "fixture file should exist pre-delete"); delete_transcript(&pool, "t-audio").await.unwrap(); - assert!(!tmp.exists(), "audio file should be removed by delete_transcript"); + assert!( + !tmp.exists(), + "audio file should be removed by delete_transcript" + ); // Repeat delete must not surface an error even though both the // soft-delete UPDATE is a no-op AND the audio file is already gone. @@ -3184,33 +3182,32 @@ mod tests { delete_transcript(&pool, "t-new").await.unwrap(); // Backdate t-old past the 30-day window. t-new keeps "now". - sqlx::query( - "UPDATE transcripts SET deleted_at = datetime('now', '-60 days') WHERE id = ?", - ) - .bind("t-old") - .execute(&pool) - .await - .unwrap(); + sqlx::query("UPDATE transcripts SET deleted_at = datetime('now', '-60 days') WHERE id = ?") + .bind("t-old") + .execute(&pool) + .await + .unwrap(); let purged = purge_deleted_transcripts(&pool, 30).await.unwrap(); assert_eq!(purged, 1, "only t-old should be hard-deleted"); - let old_exists: Option = sqlx::query_scalar( - "SELECT id FROM transcripts WHERE id = ?", - ) - .bind("t-old") - .fetch_optional(&pool) - .await - .unwrap(); + let old_exists: Option = + sqlx::query_scalar("SELECT id FROM transcripts WHERE id = ?") + .bind("t-old") + .fetch_optional(&pool) + .await + .unwrap(); assert!(old_exists.is_none(), "t-old should be hard-gone"); - let new_exists: Option = sqlx::query_scalar( - "SELECT id FROM transcripts WHERE id = ?", - ) - .bind("t-new") - .fetch_optional(&pool) - .await - .unwrap(); - assert!(new_exists.is_some(), "t-new still inside the retention window"); + let new_exists: Option = + sqlx::query_scalar("SELECT id FROM transcripts WHERE id = ?") + .bind("t-new") + .fetch_optional(&pool) + .await + .unwrap(); + assert!( + new_exists.is_some(), + "t-new still inside the retention window" + ); } } diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 3b4aad1..6d931a5 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -19,9 +19,9 @@ pub use database::{ list_transcripts, list_transcripts_paged, list_trashed_transcripts, log_error, mark_implementation_rule_fired, migrate_legacy_setting_keys, prune_error_log, purge_deleted_transcripts, record_feedback, restore_transcript, search_transcripts, - set_implementation_rule_enabled, set_setting, set_task_energy, uncomplete_task, - update_profile, update_task, update_transcript, update_transcript_meta, DailyCompletionCount, ErrorLogRow, FeedbackRow, FeedbackTargetType, - ImplementationRuleRow, InsertTranscriptParams, ProfileRow, ProfileTermRow, - RecordFeedbackParams, TaskRow, TranscriptRow, + set_implementation_rule_enabled, set_setting, set_task_energy, uncomplete_task, update_profile, + update_task, update_transcript, update_transcript_meta, DailyCompletionCount, ErrorLogRow, + FeedbackRow, FeedbackTargetType, ImplementationRuleRow, InsertTranscriptParams, ProfileRow, + ProfileTermRow, RecordFeedbackParams, TaskRow, TranscriptRow, }; pub use file_storage::{app_data_dir, crashes_dir, database_path, logs_dir, recordings_dir}; diff --git a/crates/storage/src/migrations.rs b/crates/storage/src/migrations.rs index fa6d884..34c9d68 100644 --- a/crates/storage/src/migrations.rs +++ b/crates/storage/src/migrations.rs @@ -1237,7 +1237,9 @@ mod tests { // rows so the migration doesn't accidentally mark old transcripts // as deleted. let pool = fk_test_pool().await; - run_migrations_up_to(&pool, 15).await.expect("migrate to v15"); + run_migrations_up_to(&pool, 15) + .await + .expect("migrate to v15"); // Seed a pre-v16 row to verify backfill preserves NULL. sqlx::query( @@ -1290,7 +1292,9 @@ mod tests { .await .expect("read indexes"); assert!( - index_names.iter().any(|n| n == "idx_transcripts_deleted_at"), + index_names + .iter() + .any(|n| n == "idx_transcripts_deleted_at"), "expected idx_transcripts_deleted_at, got {index_names:?}", ); } diff --git a/crates/transcription/src/local_engine.rs b/crates/transcription/src/local_engine.rs index dac5f9c..a1d486d 100644 --- a/crates/transcription/src/local_engine.rs +++ b/crates/transcription/src/local_engine.rs @@ -199,8 +199,7 @@ impl LocalEngine { let backend = guard.as_mut().ok_or(Error::EngineNotLoaded)?; let start = Instant::now(); - let segments = - backend.transcribe_sync_with_abort(audio.samples(), options, abort_flag)?; + let segments = backend.transcribe_sync_with_abort(audio.samples(), options, abort_flag)?; let inference_ms = start.elapsed().as_millis() as u64; Ok(TimedTranscript { @@ -272,7 +271,9 @@ pub fn load_whisper(model_path: &Path) -> Result> { mod tests { use super::*; use std::sync::atomic::Ordering; - use transcribe_rs::{ModelCapabilities, TranscribeError, TranscribeOptions, TranscriptionResult}; + use transcribe_rs::{ + ModelCapabilities, TranscribeError, TranscribeOptions, TranscriptionResult, + }; #[test] fn engine_reports_not_available_before_loading() { diff --git a/crates/transcription/src/model_manager.rs b/crates/transcription/src/model_manager.rs index e8f7e4c..a42bbbd 100644 --- a/crates/transcription/src/model_manager.rs +++ b/crates/transcription/src/model_manager.rs @@ -130,10 +130,7 @@ fn verified_manifest_path(dir: &Path) -> PathBuf { dir.join(".lumotia-verified") } -fn verified_manifest_matches( - entry: &lumotia_core::model_registry::ModelEntry, - dir: &Path, -) -> bool { +fn verified_manifest_matches(entry: &lumotia_core::model_registry::ModelEntry, dir: &Path) -> bool { let manifest = match std::fs::read_to_string(verified_manifest_path(dir)) { Ok(contents) => contents, Err(_) => return false, @@ -760,10 +757,7 @@ mod tests { manifest_path.exists(), "final manifest must exist after atomic write" ); - assert!( - !tmp_path.exists(), - "stale .tmp must be removed by rename" - ); + assert!(!tmp_path.exists(), "stale .tmp must be removed by rename"); let body = std::fs::read_to_string(&manifest_path).unwrap(); assert!(body.starts_with("version\t1")); diff --git a/crates/transcription/src/whisper_rs_backend.rs b/crates/transcription/src/whisper_rs_backend.rs index 2bbb0fb..1926792 100644 --- a/crates/transcription/src/whisper_rs_backend.rs +++ b/crates/transcription/src/whisper_rs_backend.rs @@ -92,9 +92,7 @@ impl WhisperRsBackend { ); let mut state = self.ctx.create_state().map_err(|e| { - Error::TranscriptionFailed( - WhisperBackendError::State(e.to_string()).to_string(), - ) + Error::TranscriptionFailed(WhisperBackendError::State(e.to_string()).to_string()) })?; let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 }); @@ -124,9 +122,7 @@ impl WhisperRsBackend { } state.full(params, samples).map_err(|e| { - Error::TranscriptionFailed( - WhisperBackendError::Transcribe(e.to_string()).to_string(), - ) + Error::TranscriptionFailed(WhisperBackendError::Transcribe(e.to_string()).to_string()) })?; let n = state.full_n_segments(); diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..addcd49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,10 @@ +[toolchain] +# Pinned to stop rustc / rustfmt / clippy drift across contributor machines +# and CI runners. Bumping this version requires: +# - cargo fmt --check clean on the new toolchain +# - cargo clippy --workspace --all-targets -- -D warnings clean +# - cargo test --workspace green +# in that order, committed as a hygiene sweep separate from feature work. +channel = "1.94.1" +components = ["rustfmt", "clippy"] +profile = "minimal" diff --git a/src-tauri/src/commands/clipboard.rs b/src-tauri/src/commands/clipboard.rs index 7094338..94fe172 100644 --- a/src-tauri/src/commands/clipboard.rs +++ b/src-tauri/src/commands/clipboard.rs @@ -16,8 +16,7 @@ pub(crate) const MAX_CLIPBOARD_BYTES: usize = 1024 * 1024; /// buttons; mirror the secondary-windows capability grant in /// `src-tauri/capabilities/secondary-windows.json` so the IPC trust /// boundary and the permission set stay in lock-step. -const CLIPBOARD_ALLOWED_WINDOWS: &[&str] = - &["main", "transcript-viewer", "transcription-preview"]; +const CLIPBOARD_ALLOWED_WINDOWS: &[&str] = &["main", "transcript-viewer", "transcription-preview"]; /// Copy text to the system clipboard via arboard. Restricted to the /// documented "clipboard-capable" windows (main + transcript-viewer + diff --git a/src-tauri/src/commands/fs.rs b/src-tauri/src/commands/fs.rs index cd19cbd..7651e30 100644 --- a/src-tauri/src/commands/fs.rs +++ b/src-tauri/src/commands/fs.rs @@ -88,9 +88,12 @@ pub(crate) fn resolve_export_path(path: &Path, bases: &[PathBuf]) -> Result Result<(), String> { /// `&[&'static str]` and should mirror an entry in /// `src-tauri/capabilities/*.json` — keeping the IPC trust boundary and the /// permission grant in lock-step. -pub fn ensure_window_in_set( - window: &tauri::WebviewWindow, - allowed: &[&str], -) -> Result<(), String> { +pub fn ensure_window_in_set(window: &tauri::WebviewWindow, allowed: &[&str]) -> Result<(), String> { ensure_window_in_set_label(window.label(), allowed) } pub fn ensure_window_in_set_label(label: &str, allowed: &[&str]) -> Result<(), String> { - if allowed.iter().any(|a| *a == label) { + if allowed.contains(&label) { Ok(()) } else { Err(format!( diff --git a/src-tauri/src/commands/transcription.rs b/src-tauri/src/commands/transcription.rs index b73918d..97fa744 100644 --- a/src-tauri/src/commands/transcription.rs +++ b/src-tauri/src/commands/transcription.rs @@ -50,10 +50,7 @@ const MAX_TRANSCRIBE_BYTES: u64 = 1024 * 1024 * 1024; /// rejects most obvious attacks), then size (one stat call), then /// path canonicalisation (slowest, only meaningful once the cheaper /// gates pass). -pub(crate) fn validate_transcribe_input( - path: &Path, - metadata_len: u64, -) -> Result<(), String> { +pub(crate) fn validate_transcribe_input(path: &Path, metadata_len: u64) -> Result<(), String> { let ext = path .extension() .and_then(|s| s.to_str()) @@ -67,7 +64,10 @@ pub(crate) fn validate_transcribe_input( ) })?; - if !ALLOWED_AUDIO_EXTENSIONS.iter().any(|allowed| *allowed == ext) { + if !ALLOWED_AUDIO_EXTENSIONS + .iter() + .any(|allowed| *allowed == ext) + { return Err(format!( "Refusing to transcribe {}: extension '.{}' is not in the \ allowlist. Supported: {}.", @@ -237,8 +237,7 @@ pub async fn transcribe_file( // Trust-5: extension + size gate before we hand the path to the // audio decoder. `std::fs::metadata` resolves symlinks so the size // check sees the actual blob, not a symlink-target lie. - let metadata = std::fs::metadata(&path) - .map_err(|e| format!("Cannot stat {path}: {e}"))?; + let metadata = std::fs::metadata(&path).map_err(|e| format!("Cannot stat {path}: {e}"))?; validate_transcribe_input(Path::new(&path), metadata.len())?; let resolved_profile_id = @@ -347,7 +346,9 @@ mod tests_trust5 { #[test] fn accepts_each_allowed_extension() { - for ext in ["wav", "mp3", "m4a", "mp4", "flac", "ogg", "opus", "webm", "aac"] { + for ext in [ + "wav", "mp3", "m4a", "mp4", "flac", "ogg", "opus", "webm", "aac", + ] { let path_string = format!("/tmp/clip.{ext}"); let path = Path::new(&path_string); assert!( @@ -376,10 +377,8 @@ mod tests_trust5 { #[test] fn rejects_oversize_file() { - let result = validate_transcribe_input( - Path::new("/tmp/huge.wav"), - MAX_TRANSCRIBE_BYTES + 1, - ); + let result = + validate_transcribe_input(Path::new("/tmp/huge.wav"), MAX_TRANSCRIBE_BYTES + 1); let err = result.expect_err("must reject oversize file"); assert!(err.contains("1 GiB"), "unexpected error: {err}"); } diff --git a/src-tauri/src/commands/transcripts.rs b/src-tauri/src/commands/transcripts.rs index 94844c3..70cdaea 100644 --- a/src-tauri/src/commands/transcripts.rs +++ b/src-tauri/src/commands/transcripts.rs @@ -17,9 +17,9 @@ use lumotia_storage::{ delete_transcript as db_delete_transcript, get_transcript as db_get_transcript, insert_transcript as db_insert_transcript, list_transcripts_paged, list_trashed_transcripts as db_list_trashed_transcripts, - restore_transcript as db_restore_transcript, - search_transcripts as db_search_transcripts, update_transcript as db_update_transcript, - update_transcript_meta as db_update_transcript_meta, InsertTranscriptParams, TranscriptRow, + restore_transcript as db_restore_transcript, search_transcripts as db_search_transcripts, + update_transcript as db_update_transcript, update_transcript_meta as db_update_transcript_meta, + InsertTranscriptParams, TranscriptRow, }; use crate::AppState; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 21f3b4a..e42d5b5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -18,9 +18,7 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{EnvFilter, Layer}; -use lumotia_core::paths::{ - check_target_ambiguity, migrate_legacy_data_dir, MigrationStatus, -}; +use lumotia_core::paths::{check_target_ambiguity, migrate_legacy_data_dir, MigrationStatus}; use lumotia_core::types::EngineName; use lumotia_llm::LlmEngine; use lumotia_storage::{ @@ -239,7 +237,7 @@ fn build_rolling_appender(logs_dir: &Path) -> std::io::Result Option { // Best-effort: keep stderr logging even if the file path is // unwritable, and surface the failure to stderr so dogfooders // notice the missing forensic stream. - let _ = tracing_subscriber::registry() - .with(stderr_layer) - .try_init(); + let _ = tracing_subscriber::registry().with(stderr_layer).try_init(); eprintln!( "lumotia: failed to install rolling file log appender at {}: {e}", logs_dir.display() diff --git a/src-tauri/src/tauri_app_data_migration.rs b/src-tauri/src/tauri_app_data_migration.rs index f80ac33..12f9ecb 100644 --- a/src-tauri/src/tauri_app_data_migration.rs +++ b/src-tauri/src/tauri_app_data_migration.rs @@ -41,16 +41,10 @@ pub enum AppDataMigrationStatus { /// after the first successful migration (we preserve legacy as a /// backup), so the warning is informational rather than an /// indication of trouble. - BothExistLegacyPreserved { - old: PathBuf, - new: PathBuf, - }, + BothExistLegacyPreserved { old: PathBuf, new: PathBuf }, /// Migration succeeded: legacy copied to new path via atomic /// staging rename, legacy preserved as a backup. - Migrated { - old: PathBuf, - new: PathBuf, - }, + Migrated { old: PathBuf, new: PathBuf }, } /// Resolve the OLD Tauri `app_data_dir` from platform conventions. The @@ -67,6 +61,10 @@ pub fn legacy_tauri_app_data_dir() -> Option { } fn legacy_tauri_app_data_dir_for(identifier: &str) -> Option { + // Exactly one of the four cfg blocks below is present per target + // compile. Each is a tail expression that becomes the function's + // return value. Avoiding explicit `return` keeps clippy's + // needless_return lint happy on every platform. #[cfg(target_os = "linux")] { // XDG_DATA_HOME wins when set and non-empty, per the XDG Base @@ -79,29 +77,29 @@ fn legacy_tauri_app_data_dir_for(identifier: &str) -> Option { } } let home = std::env::var("HOME").ok().filter(|s| !s.is_empty())?; - return Some( + Some( PathBuf::from(home) .join(".local") .join("share") .join(identifier), - ); + ) } #[cfg(target_os = "macos")] { let home = std::env::var("HOME").ok().filter(|s| !s.is_empty())?; - return Some( + Some( PathBuf::from(home) .join("Library") .join("Application Support") .join(identifier), - ); + ) } #[cfg(target_os = "windows")] { let appdata = std::env::var("APPDATA").ok().filter(|s| !s.is_empty())?; - return Some(PathBuf::from(appdata).join(identifier)); + Some(PathBuf::from(appdata).join(identifier)) } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] @@ -123,10 +121,7 @@ fn legacy_tauri_app_data_dir_for(identifier: &str) -> Option { /// `paths.rs` migration already covers the user's transcripts and /// models, so even a total-failure here only loses webview-keyed state /// (preferences, session storage, plugin geometry). -pub fn migrate_tauri_app_data_dir_with_paths( - old: &Path, - new: &Path, -) -> AppDataMigrationStatus { +pub fn migrate_tauri_app_data_dir_with_paths(old: &Path, new: &Path) -> AppDataMigrationStatus { let old_exists = old.exists(); let new_exists = new.exists(); @@ -265,7 +260,10 @@ mod tests { assert!(leveldb.exists()); // Staging directory is cleaned up. - assert!(!tmp.path().join("consulting.corbel.lumotia.migrating").exists()); + assert!(!tmp + .path() + .join("consulting.corbel.lumotia.migrating") + .exists()); } #[test] @@ -329,6 +327,9 @@ mod tests { AppDataMigrationStatus::BothExistLegacyPreserved { .. } )); - assert_eq!(fs::read(new.join("file.txt")).unwrap(), b"v2-edited-by-user"); + assert_eq!( + fs::read(new.join("file.txt")).unwrap(), + b"v2-edited-by-user" + ); } } diff --git a/src-tauri/tests/tracing_appender_smoke.rs b/src-tauri/tests/tracing_appender_smoke.rs index d3ce7ff..8e2437b 100644 --- a/src-tauri/tests/tracing_appender_smoke.rs +++ b/src-tauri/tests/tracing_appender_smoke.rs @@ -56,11 +56,7 @@ fn init_tracing_creates_log_file() { let entries: Vec<_> = fs::read_dir(&logs_dir) .expect("read tempdir") .filter_map(Result::ok) - .filter(|e| { - e.file_name() - .to_string_lossy() - .starts_with("lumotia") - }) + .filter(|e| e.file_name().to_string_lossy().starts_with("lumotia")) .collect(); assert!(