Files
transcript/docs/superpowers/plans/2026-07-13-audio-history-streaming-fallback.md
T
evandrodevbr 06aaeeaf79 wip: snapshot das alterações não commitadas (2026-10-20)
Origem: D:/Documents/Github/transcript
Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 2
2026-10-20 00:50:17 -03:00

7.7 KiB
Raw Blame History

Audio History, API Fallback, and Streaming Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Retain the last 10 recordings, export them through a native save dialog, fall back from API to local Whisper PT-BR, and insert streaming API text progressively.

Architecture: Add a small audio_history module that owns safe filename validation, metadata, retention, and export source resolution. Extend the existing OpenAI-compatible client to detect SSE/NDJSON by response Content-Type; the existing UI command pipeline owns partial rendering and global-hotkey insertion so the client remains transport-focused.

Tech Stack: Rust, Tauri 2, tauri-plugin-dialog, reqwest blocking client, TypeScript, Vitest, Cargo tests.


Task 1: Persist a bounded, safe audio history

Files:

  • Create: src-tauri/src/audio_history.rs

  • Modify: src-tauri/src/lib.rs

  • Modify: src-tauri/src/app_state.rs

  • Modify: src-tauri/src/ui_commands.rs

  • Test: src-tauri/src/audio_history.rs

  • Step 1: Write failing history tests

#[test]
fn retain_keeps_the_ten_newest_wavs() { /* create 11 timestamped WAV files; assert 10 remain */ }

#[test]
fn audio_id_cannot_escape_recordings_dir() {
    assert!(resolve_audio_path(Path::new("recordings"), "../secret.wav").is_err());
}
  • Step 2: Run the focused test

Run: cargo test --manifest-path src-tauri/Cargo.toml audio_history::tests --lib Expected: compilation failure because audio_history does not exist.

  • Step 3: Implement a minimal history module
pub const AUDIO_HISTORY_LIMIT: usize = 10;

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AudioHistoryItem { pub id: String, pub size_bytes: u64, pub modified_at_ms: u64 }

pub fn retain_audio_history(dir: &Path) -> AppResult<Vec<AudioHistoryItem>> { /* list WAVs, sort newest-first, remove beyond 10 */ }
pub fn resolve_audio_path(dir: &Path, id: &str) -> AppResult<PathBuf> { /* filename-only + canonicalized containment */ }
  • Step 4: Call retention after both normal and hotkey transcription flows

Replace cleanup_audio_if_needed calls with retain_audio_history(&state.recordings_dir) so captured WAVs are never deleted simply because keep_audio is false. Add audio_history to AppSnapshot from ManagedAppState::snapshot.

  • Step 5: Run the focused test

Run: cargo test --manifest-path src-tauri/Cargo.toml audio_history::tests --lib Expected: PASS.

Task 2: Export recordings safely through a native dialog

Files:

  • Modify: src-tauri/Cargo.toml

  • Modify: src-tauri/src/lib.rs

  • Modify: src-tauri/src/ui_commands.rs

  • Test: src-tauri/src/audio_history.rs

  • Step 1: Add a failing source-resolution test

#[test]
fn resolve_audio_path_accepts_a_listed_wav() { /* assert a known WAV resolves inside recordings */ }
  • Step 2: Add the documented Tauri dialog plugin
tauri-plugin-dialog = "2"
.plugin(tauri_plugin_dialog::init())

Use DialogExt::dialog().file().add_filter("Áudio WAV", &["wav"]).blocking_save_file() and std::fs::copy; return false when the user cancels.

  • Step 3: Register an explicit command
#[tauri::command]
pub(crate) fn download_audio(id: String, app: tauri::AppHandle, state: State<'_, ManagedAppState>) -> Result<bool, String>

Resolve id through audio_history::resolve_audio_path; never accept a caller-provided file path.

  • Step 4: Run Rust tests

Run: cargo test --manifest-path src-tauri/Cargo.toml --lib Expected: PASS.

Task 3: API-to-Whisper PT-BR fallback

Files:

  • Modify: src-tauri/src/ui_commands.rs

  • Test: src-tauri/src/ui_commands.rs

  • Step 1: Write a failing pure routing test

#[test]
fn api_failure_without_partial_uses_local_fallback() {
    assert!(should_fallback_to_whisper(TranscriptionMode::ApiOnly, false));
}

#[test]
fn partial_stream_does_not_fallback_and_duplicate_text() {
    assert!(!should_fallback_to_whisper(TranscriptionMode::ApiOnly, true));
}
  • Step 2: Implement the smallest routing rule
fn should_fallback_to_whisper(mode: TranscriptionMode, partial_received: bool) -> bool {
    matches!(mode, TranscriptionMode::ApiOnly) && !partial_received
}

In run_configured_transcription, use a cloned settings value with transcription.language = Some("pt".into()) to invoke existing Whisper after API failure. Preserve local-first’s current local-then-API behavior.

  • Step 3: Run focused tests

Run: cargo test --manifest-path src-tauri/Cargo.toml ui_commands::tests --lib Expected: PASS.

Task 4: Stream response handling and immediate insertion

Files:

  • Modify: src-tauri/src/transcription/openai.rs

  • Modify: src-tauri/src/ui_commands.rs

  • Test: src-tauri/src/transcription/openai.rs

  • Step 1: Write failing parser tests

#[test]
fn streaming_content_type_is_detected() { /* text/event-stream and x-ndjson true */ }

#[test]
fn cumulative_stream_text_becomes_only_the_new_suffix() {
    assert_eq!(stream_delta("olá", "olá mundo"), " mundo");
}
  • Step 2: Implement response-stream transport

Set Accept: text/event-stream, application/x-ndjson, application/json. For SSE/NDJSON headers, read lines from the blocking response, ignore [DONE], extract delta, text, transcript, result, output, data, and OpenAI choices[0].delta.content, then invoke an optional partial callback. Keep JSON/plain-text behavior unchanged.

  • Step 3: Wire partial callbacks into UI commands

  • Main-window transcriptions append partials to TranscriptDraft.text and emit snapshots.

  • Global-hotkey transcriptions hide the non-focusable island on first partial and use existing FallbackInjector with ClipboardPaste for each new suffix.

  • Only perform the final full-text insertion when no partial was inserted.

  • Suppress API-to-Whisper fallback after a partial to prevent duplicate external text.

  • Step 4: Run focused tests

Run: cargo test --manifest-path src-tauri/Cargo.toml transcription::openai::tests ui_commands::tests --lib Expected: PASS.

Task 5: Minimal audio history UI

Files:

  • Modify: src/app.ts

  • Modify: src/app.test.ts

  • Modify: src/styles.css

  • Step 1: Write failing frontend tests

it("shows the latest audio and requests its download", async () => {
  // Fake snapshot has audioHistory[0]; click the button; expect download_audio with its id.
});
  • Step 2: Implement a compact details list

Add audioHistory to frontend AppSnapshot. Render one collapsed <details> section below diagnostics: “Áudios recentes”, a primary “Baixar último áudio” action, and up to 10 safe metadata rows. Show a useful empty state. Call native download_audio by item ID.

  • Step 3: Add restrained CSS

Use existing tokens and compact buttons. Do not introduce a new modal, gradients, or a card grid. Preserve visible focus styles and reduced-motion behavior.

  • Step 4: Run focused tests

Run: npm test -- --run src/app.test.ts --reporter=verbose Expected: PASS.

Task 6: Full verification and release artifact

Files:

  • Modify only release metadata if a new artifact version is required.

  • Run cargo fmt --manifest-path src-tauri/Cargo.toml -- --check.

  • Run cargo test --manifest-path src-tauri/Cargo.toml --lib.

  • Run npm test -- --run && npm run build.

  • Run cargo check --manifest-path src-tauri/Cargo.toml inside the VS Build Tools environment.

  • Run npm run tauri:build inside the VS Build Tools environment and verify installer timestamps.