Files
TalkEdit/polish_plan.md
2026-05-06 13:18:53 -06:00

17 KiB

TalkEdit — Testing & Robustness Plan

Tests are critical before launch to prevent regressions and ensure the app is stable. Below is every function in the codebase that needs test coverage, organized by module.


1. Rust backend (src-tauri/src/)

licensing.rs — already has partial coverage (4 tests)

  • verify_license_key — valid key, invalid format, invalid signature, expired key (exists)
  • load_cached_license — file exists, file missing, malformed file
  • cache_license — write and read back
  • remove_license — removes file, no-ops if missing
  • get_or_start_trial — creates new trial file, loads existing, handles corrupt file
  • get_trial_info — active trial (29 days), expired trial (0 days), exactly at boundary
  • get_trial_days_remaining — active returns Some, expired returns None,
  • clear_trial — removes file, no-ops if missing
  • get_app_status — licensed (cached license), trial active, expired (no license, no trial)
  • has_feature — feature exists, feature missing, empty features list

models.rs — no existing tests

  • list_models — whisper models found, llm models found, mixed, empty dirs
  • delete_model — deletes file, deletes directory, path doesn't exist
  • huggingface_cache_dir — HF_HOME set, XDG_CACHE_HOME set, defaults to ~/.cache

transcription.rs — no existing tests

  • ensure_model_downloaded — returns success (stub function)

paths.rs — no existing tests

  • project_root — dev layout, packaged (TAURI_RESOURCE_DIR), fallback
  • python_exe — bundled path, venv paths, fallback to .venv312
  • backend_script — joins project_root/backend
  • root_script — joins project_root

2. Frontend store (frontend/src/store/)

editorStore.ts (Zustand) — partially covered (2 tests)

  • reset (in beforeEach)
  • setGlobalGainDb — clamps to -24/+24 (exists)
  • addGainRange — adds with correct start/end/gain (exists)
  • addCutRange — adds with correct start/end, handles overlapping ranges
  • addMuteRange — adds with correct start/end
  • addSpeedRange — adds with correct start/end/speed
  • removeCutRange — removes existing, no-ops on missing
  • removeMuteRange — removes existing, no-ops on missing
  • removeGainRange — removes existing, no-ops on missing
  • removeSpeedRange — removes existing, no-ops on missing
  • updateCutRange — updates bounds, prevents negative duration
  • updateMuteRange — updates bounds
  • updateGainRangeBounds — updates bounds
  • updateSpeedRangeBounds — updates bounds
  • updateGainRange — updates gain value
  • updateSpeedRange — updates speed value
  • setSelectedWordIndices — single, multiple, empty, out of range handled
  • replaceWordRange — replaces words in middle, replaces at start, handles invalid indices
  • updateWordText — updates word, preserves timing, no-ops on bad index
  • getWordAtTime — exact match, between words, before first word, after last word, no words
  • loadVideo — sets videoUrl, resets state, handles missing file
  • setCurrentTime — sets time, clamps to 0-duration
  • setTranscribing — toggles flag, sets status
  • setTranscription — sets words and segments, handles empty arrays
  • setMarkInTime / setMarkOutTime — sets and clears
  • clearMarkRange — clears both marks
  • addTimelineMarker — adds with label/color/time
  • removeTimelineMarker — removes by id
  • updateTimelineMarker — updates label/color
  • setZonePreviewPaddingSeconds — sets and clamps
  • setBackgroundMusic / updateBackgroundMusic — sets and updates
  • setAdditionalClips — adds, removes, reorders
  • setSilenceTrimGroups — sets groups

licenseStore.ts — no existing tests

  • canEdit — true for Licensed, true for Trial, false for Expired, false for null
  • checkStatus — calls getAppStatus, sets correct state, handles error (falls to Expired)
  • activateLicense — valid key sets Licensed, invalid key returns false
  • deactivateLicense — reverts to trial if valid, falls to Expired otherwise
  • hasFeature — returns true for matching, false for missing
  • setShowDialog — toggles dialog visibility
  • Persist middleware — Licensed status persists, Trial/Expired does not

aiStore.ts — no existing tests

  • setProviderConfig — updates provider, encrypts API keys
  • setDefaultProvider — changes default
  • setCustomFillerWords — sets and clears
  • setFillerResult — sets and clears
  • setProcessing — toggles with message

3. Frontend hooks (frontend/src/hooks/)

useKeyboardShortcuts.ts — no existing tests

  • Keyboard event dispatch — space plays/pauses, J/K/L speed controls, I/O marks, Delete cuts, Ctrl+Z undo
  • toggleCheatsheet — creates overlay with correct content, toggles off
  • Skip logic — skips correctly forward and back from current playhead

useVideoSync.ts — no existing tests

  • Synchronization of store isPlaying with video element, audio element
  • togglePlay — starts playing, pauses
  • seekTo — seeks video to correct time, seeks audio to correct time
  • Handles video element ref being null (doesn't crash)

4. Frontend lib (frontend/src/lib/)

keybindings.ts — no existing tests

  • loadBindings — loads from localStorage, falls back to standard preset when missing
  • saveBindings — persists and reloads correctly
  • applyPreset — 'standard' and 'left-hand' both apply all required bindings
  • detectConflicts — detects duplicate keys, returns empty when no conflicts

5. Backend services (backend/services/)

video_editor.py — no existing tests

  • apply_cut_segments — keeps correct segments from transcript word list, FFmpeg concat cmd
  • apply_mute_ranges — cuts audio for muted ranges
  • apply_gain_ranges — adjusts volume (positive and negative) for FFmpeg filter chains
  • apply_speed_ranges — time-stretches or compresses segments
  • mix_background_music — mixes with ducking enabled, mixes without ducking, handles no music
  • build_export_filters — stitches together all zone types into correct filter order

audio_cleaner.py — no existing tests

  • detect_silence — detects pauses above threshold, returns correct time ranges
  • remove_silence — splits by silence, re-concatenates keep segments

ai_provider.py — no existing tests

  • complete — Ollama completion succeeds, OpenAI completion succeeds, Claude completion succeeds
  • complete — handles missing provider, timeout, bad JSON response
  • list_ollama_models — returns models list, handles connection error

transcription.py — no existing tests

  • transcribe_file — returns words with correct format (word, start, end, confidence)
  • transcribe_segment — re-transcribes a range with offset-adjusted timestamps
  • _load_model — caches model, returns existing from cache, handles GPU/CPU

caption_generator.py — no existing tests

  • generate_srt — correct SRT format, sequential numbering, proper timestamps
  • generate_vtt — correct VTT format with header, chroma key tags
  • generate_ass — correct ASS subtitle format

gpu_utils.py — no existing tests

  • get_optimal_device — returns CUDA when available, returns CPU otherwise

audio_processing.py — no existing tests

  • extract_audio — extracts wav from video, handles audio-only input, temp file cleanup

6. Frontend components — integration tests

TranscriptEditor.tsx

  • Word selection: click, shift+click range, drag select
  • Ctrl+click seeks video to correct time
  • Double-click enters edit mode, Enter commits, Escape cancels
  • Zone mode drag creates correct zone type
  • Search finds matches, navigates with Enter/Shift+Enter
  • "Restore" button appears on hover over words in a zone, removes the zone
  • Re-transcribe calls backend and updates words
  • Selection toolbar buttons create correct zone types
  • When canEdit is false, buttons are disabled and zone creation is blocked

WaveformTimeline.tsx

  • Canvas renders waveform when audio data loads
  • Click seeks to correct time
  • Zone drag creates zone on mouse up
  • Zone selection and resize with handles
  • Delete key removes selected zone
  • Zoom and scroll work correctly
  • Zone toggle buttons show/hide overlay layers
  • Loading spinner shows when no waveform data
  • Error message with retry button on load failure

ExportDialog.tsx

  • Fast mode card and re-encode card toggle correctly
  • Resolution selector only visible in re-encode mode
  • Format selector disables WAV for video files
  • Export button triggers export with correct parameters
  • Progress bar updates during export
  • Loudness normalization checkbox shows LUFS target selector

AIPanel.tsx

  • Filler words tab: detect button sends request, displays results
  • Apply All creates cut ranges for all fillers
  • Clips tab: find clips shows suggestions
  • Reprocess tab: model selector + reprocess button
  • Error state with retry on API failure

App.tsx — layout and toolbar

  • Welcome screen shows when no video loaded
  • Trial bar shows on welcome screen for Trial/Expired states
  • Toolbar buttons toggle modes correctly (Cut, Mute, Gain, Speed)
  • Toolbar buttons open correct panels (Zones, Silence, Markers, Music, Append, AI, Export, Settings, Help)
  • File menu opens/closes, items work
  • Split pane dividers are draggable and keyboard-accessible
  • First-run welcome overlay shows once
  • Hotkeys work: Escape clears modes, ? opens cheatsheet

7. Error handling regression tests

  • Backend crash: show reconnect banner, not broken UI
  • Transcription failure: show error, allow retry with different model
  • Export failure: show FFmpeg stderr, allow copy
  • Model download timeout: show error, allow retry
  • File not found: handled gracefully on open/load
  • Permission denied: handled gracefully on save/export
  • Concurrent operations: block export during transcription, block transcription during export

8. Licensing & trial flow tests

  • Fresh install: shows 30-day trial
  • Day 29: still allows editing
  • Day 31: shows expired, editing locked, export still works
  • Activate valid license: switches to Licensed, clears trial
  • Activate invalid license: shows error, stays on trial
  • Deactivate license: returns to trial if valid, expires if trial over
  • Expired banner shows correct message and activate link
  • canEdit prop correctly gates all editing controls across all components

Test infrastructure

Layer Framework Run command
Rust cargo test (built-in) cd src-tauri && cargo test
Frontend (Vitest) Vitest + jsdom cd frontend && npx vitest run
Frontend (components) Playwright or Vitest + testing-library cd frontend && npx vitest run
Python backend pytest cd backend && python -m pytest

Setup needed

  • Frontend: npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom (vitest likely already installed)
  • Frontend: add "test": "vitest run" to package.json if not present
  • Python: ensure pytest is installed (pip install pytest pytest-asyncio)
  • CI: add GitHub Actions workflow for cargo test && vitest run && pytest

Priority order

  1. store tests (editorStore, licenseStore, aiStore) — core data integrity
  2. Rust licensing tests — payment/trial logic must never break
  3. Rust models tests — filesystem operations must be safe
  4. Backend service unit tests — export pipeline, transcription, AI
  5. Component integration tests — user-facing behavior
  6. Error handling regression tests — robustness

Robustness beyond tests

React Error Boundary

The app has no error boundary — a single JS error in any component crashes the entire UI to a white screen. Wrap the app in a <ErrorBoundary> that catches render errors and shows a fallback with "Something went wrong" + a reload button.

  • Create ErrorBoundary.tsx component (componentDidCatch pattern)
  • Wrap the entire <App /> in main.tsx
  • Fallback shows: error message, stack trace (collapsed), "Reload" button, "Reset & Clear State" button

Global JS error logging

Uncaught errors in async code and event handlers silently break the app. Add a window.onerror and window.onunhandledrejection handler that logs to the Tauri backend and shows a toast notification.

  • Add global error handler in main.tsx that intercepts all uncaught errors
  • Log to Rust backend via invoke('log_error', { message, stack })
  • Show a non-blocking toast notification (bottom-right) for non-fatal errors
  • Fatal errors still go to the ErrorBoundary

Input validation layer

The app trusts user input too much. Invalid values in number inputs, empty file paths, or negative durations can cause crashes or silent failures. Add validation at the store level.

  • editorStore.ts — validate all setters: clamp numbers, reject empty strings for paths, enforce min/max on dB and speed values
  • licenseStore.ts — validate license key format before sending to Rust (prefix check, base64 pattern)
  • aiStore.ts — validate API key formatting, model name not empty
  • Export options — validate resolution, format, loudness target against allowed values before sending to backend

Frontend runtime assertions

The app makes assumptions about data shapes (e.g. words[sorted[0]].start assumes the index exists). Add assertion checks in critical paths that log a clear error instead of silently producing NaN or undefined.

  • Add an assert utility function: assert(condition, message) that throws a descriptive error in dev, warns in prod
  • Guard all array index access in TranscriptEditor, WaveformTimeline, ExportDialog
  • Guard null/undefined checks on store actions that expect existing data

Auto-save crash recovery

If the app crashes or the system loses power during editing, current work is lost. Add periodic auto-save to a temp file that gets restored on next launch.

  • Every 60 seconds, save the full editor state to app_data_dir/autosave.json
  • On launch, check if autosave.json exists and is newer than the last manual save
  • Show a "Recover unsaved work?" prompt with date/time of autosave
  • Clean up autosave after a manual save or after recovery is accepted/dismissed

Backend health check & self-diagnostics

When the Python backend dies mid-session, the app doesn't know until a request fails. Add periodic health checks and a diagnostics panel.

  • Poll GET /health every 15 seconds from the frontend
  • If backend is unreachable: show a non-blocking banner "Backend disconnected — retrying..."
  • When backend comes back online, dismiss the banner automatically
  • Add a /diagnostics endpoint that reports: Python version, available FFmpeg, GPU detection, model cache sizes, disk space
  • Wire to a "System Info" section in Settings or DevPanel

CI pipeline with automated checks

Currently no CI exists. A GitHub Actions workflow would catch regressions on every push.

  • Add .github/workflows/ci.yml:
    • cargo test — all Rust tests
    • cargo clippy -- -D warnings — enforce Rust lint rules
    • npx vitest run — all frontend tests
    • npx tsc --noEmit — TypeScript type check
    • python -m pytest backend/tests/ — Python backend tests
    • cargo build --release — verify release build succeeds
  • Run on push to main and on PRs
  • Add Rust #[deny(clippy::all)] to catch common mistakes at build time

Fuzz testing for Rust deserialization

The app deserializes user-provided JSON (project files, API responses) — any malformed input could crash the Rust backend. Add fuzz tests for the critical deserialization paths.

  • Fuzz TranscriptionResult deserialization — malformed word/segment JSON
  • Fuzz .aive project file loading — corrupted JSON, missing fields, wrong types
  • Fuzz LicensePayload deserialization — tampered license payloads
  • Use cargo-fuzz or proptest crate for property-based testing

Performance telemetry (opt-in)

Without real-world data, you can't know where the app is slow. Add lightweight timing around operations that users complain about.

  • Log timing for: transcription time, export time, AI completion time, model download time
  • Store in localStorage (not sent anywhere — privacy-first)
  • Show in DevPanel: last N operation timings
  • Use this data to identify slow paths before users report them

Recovery from bad project state

A corrupted .aive file or a partially-loaded project can leave the app in an unusable state. Add recovery paths.

  • On project load failure: show error, offer "Load anyway with partial data" or "Cancel"
  • Add "Reset Editor State" button in DevPanel (clears all state back to empty)
  • Store action: validate all zone ranges are within video duration, auto-remove invalid ones on save