Skip to content

feat: sidebar navigation, video library, and UI improvements - #18

Merged
davidbzyk merged 9 commits into
mainfrom
ui-bugs
Apr 3, 2026
Merged

davidbzyk merged 9 commits into
mainfrom
ui-bugs

Conversation

@davidbzyk

Copy link
Copy Markdown
Owner

Summary

  • Sidebar navigation — Record, Library, Settings with active state highlighting. Widget window excluded via SvelteKit route groups.
  • Video library — Browse past recordings with lazy-loaded thumbnails, sort by date/size/name, play, delete, upload to Drive, reveal in Finder.
  • Settings tabs — Voice, Recording, Cloud tabs. All inputs persist on blur.
  • Recording metadata — .meta.json sidecar written alongside each MP4 with voice profile name, provider, and Drive URL.
  • TTS/VC mode toggle on main recording page when Local provider selected.
  • Preview page shows correct local voice name instead of ElevenLabs voice.
  • Recording persistence — unsaved recordings survive sidebar navigation.

Security & reliability

  • Path validation on all file commands (open, reveal, delete, thumbnail)
  • Pipeline and library share resolve_output_dir() — empty/missing config always falls back to ~/Movies/VoiceOver, never CWD
  • resolve_output_dir never panics, never returns empty or relative paths
  • get_config resets empty output_dir to default before reaching frontend
  • Removed Linux fallback code (macOS-only app)
  • All error handlers log via logger.ts with [VO:tag] prefix
  • $derived.by() used correctly throughout (not $derived(() => ...))

Test plan

  • 120 Rust tests pass (21 new)
  • 90 frontend tests pass (21 new — library state, sorting, deletion, meta updates, error states)
  • Coverage: 59% statements, 52% branches, 70% functions (all thresholds met)
  • pnpm check — 0 errors
  • cargo clippy — 0 warnings
  • Manual: record → navigate to Library → recording appears
  • Manual: upload to Drive from library card
  • Manual: settings persist across app restart
  • Manual: widget window has no sidebar

Expand the UI from a compact 480px recorder into a 900px workspace with
sidebar navigation, a video library page for browsing past recordings,
and tabbed settings.

Layout: routes moved into (app) group so the sidebar renders on all pages
except the floating widget window. StatusBar shows sidecar health, active
voice, and output directory.

Library: new Rust module (library.rs) with list_recordings, generate_thumbnail,
delete_recording, open_in_system, reveal_in_finder commands. Frontend lazy-loads
thumbnails via IntersectionObserver. Drive upload per recording. Output dir
resolves with fallback to system default (mirrors pipeline behavior).

Meta tracking: pipeline.rs writes .meta.json sidecar alongside each MP4 on
completion. google_drive.rs updates it with drive_url after upload. Library
reads meta for voice profile and Drive status display.

Settings: reorganized into Voice / Recording / Cloud tabs.
Shows the Text-to-Speech / Voice Conversion selector directly below
the provider toggle when Local is selected, before the voice dropdown.
The preview page was always displaying the ElevenLabs selectedVoice name
in the Replace Voice hint, even when using the local TTS provider. Now
fetches local voice profiles on mount and shows the correct name.
If the user has an unsaved recording and navigates to Library or Settings
via the sidebar, the record page now redirects back to /preview on mount
instead of showing a fresh recording screen. The recording is preserved
until explicitly saved or discarded.
The API key section was conditionally hidden when provider was set to
'local', making it impossible to view or manage the key. Now always
visible on the Cloud tab regardless of active provider.
Both voice collections were hidden behind the provider conditional —
only the active provider's voices were visible. Now both sections
always render with clear "Local Voices" and "ElevenLabs Voices"
headings. Local voices load on mount regardless of active provider.
Security:
- Path validation on open_in_system and reveal_in_finder (match delete pattern)
- Remove Linux fallback code and /tmp paths (macOS-only app)

Data integrity:
- Pipeline resolves local voice name from sidecar before writing meta.json
  (stores "MJ" not "4ad88b19-3bfe-493d-8xxx")
- Settings saves config on addVoice/removeVoice/setDefault (lost with back
  button removal)
- google_drive.rs meta update uses proper match/log instead of .ok()

Reactivity:
- $derived(() => ...) → $derived.by() in 4 components (RecordingCard,
  StatusBar, library page)
- RecordingCard updates meta through libraryState.updateRecordingMeta()
  instead of mutating props
- Library page awaits load before clearing stale flag

Error handling:
- RecordingCard shows error messages on delete/upload failure
- All onclick handlers wrapped in try/catch (open, reveal, delete, upload)
- Empty catch in preview voice name resolution now logs via logger
- StatusBar sidecar poll logs failures via logger.debug
- All console.* switched to logger with [VO:tag] prefix

Tests:
- 21 new frontend tests for library.svelte.ts (sorting, loading, deletion,
  meta updates, error states)
- New Rust test for local provider meta (verifies name not UUID)
- Coverage: 45% → 59% statements, 38% → 52% branches (all thresholds met)
…safety

Settings persistence (#1):
- Add onblur save to output_dir, API key, Drive client_id, and client_secret
  inputs. These lost their save trigger when saveAndBack() was removed.

Pipeline output dir:
- Pipeline now uses shared resolve_output_dir() instead of its own fallback
  that wrote to CWD when config was empty. resolve_output_dir replaced
  .expect() with graceful fallback to avoid panics.

StatusBar voice name (#2):
- Resolves local voice profile ID to human-readable name on mount.

Meta.json type consistency (#3):
- createdAt now written as u64 number, not string. Matches uploadedAt.

Sidebar active state (#4):
- /create-voice now highlights Settings in sidebar.

Error feedback (#5, #7):
- handleOpen/handleReveal set errorMessage on failure.
- Library load shows user-friendly error with logger call.
serde's default only applies to missing fields, not empty strings.
get_config now resets empty output_dir to ~/Movies/VoiceOver so the
settings UI and pipeline always have a valid path.
@coderabbitai

coderabbitai Bot commented Apr 3, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4dbaed48-d8ee-47cf-8351-8eded8ce4aa9

📥 Commits

Reviewing files that changed from the base of the PR and between 9f1704f and dee1b02.

📒 Files selected for processing (20)
  • src-tauri/src/config.rs
  • src-tauri/src/ffmpeg.rs
  • src-tauri/src/google_drive.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/library.rs
  • src-tauri/src/pipeline.rs
  • src-tauri/tauri.conf.json
  • src/lib/RecordingCard.svelte
  • src/lib/Sidebar.svelte
  • src/lib/StatusBar.svelte
  • src/lib/library.svelte.test.ts
  • src/lib/library.svelte.ts
  • src/lib/state.svelte.ts
  • src/routes/(app)/+layout.svelte
  • src/routes/(app)/+layout.ts
  • src/routes/(app)/+page.svelte
  • src/routes/(app)/create-voice/+page.svelte
  • src/routes/(app)/library/+page.svelte
  • src/routes/(app)/preview/+page.svelte
  • src/routes/(app)/settings/+page.svelte

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Recording Library with ability to browse, delete, and manage saved recordings
    • Added thumbnail generation and caching for recordings
    • Enhanced sidebar navigation for easier app traversal
    • Added status bar displaying system health, configured voice, and output directory
    • Added local voice mode toggle (TTS/Voice Conversion) when using local voice profiles
    • Reorganized Settings into tabbed interface (Voice, Recording, Cloud)
    • Enhanced Google Drive integration with automatic metadata tracking (upload dates, URLs)
  • Improvements

    • Expanded main window width for better layout
    • Added output directory validation with automatic fallback
    • Recording metadata now persists with drive uploads

Walkthrough

This PR adds a comprehensive recording library system to the application. A new Rust library module provides Tauri commands for managing local recordings—listing, generating thumbnails, deleting, and opening/revealing files—with filesystem validation, optional metadata sidecars, and FFmpeg-based thumbnail extraction. The frontend introduces corresponding UI components (sidebar, status bar, recording card) and a dedicated library page with sorting and filtering, alongside state management for recording lists and metadata updates.

Changes

Cohort / File(s) Summary
Recording Management Backend
src-tauri/src/library.rs
New module implementing filesystem-backed recording management with models (RecordingInfo, RecordingMeta), path validation, directory scanning, and Tauri command exports for listing, thumbnail generation, deletion, and file system operations.
Metadata & Thumbnails
src-tauri/src/ffmpeg.rs, src-tauri/src/pipeline.rs, src-tauri/src/google_drive.rs
Added FFmpeg thumbnail extraction (thumbnail_args, extract_thumbnail); integrated .meta.json sidecar generation in pipeline with voice-name resolution; Drive upload now updates metadata with driveUrl and uploadedAt fields.
Configuration & Handler Registration
src-tauri/src/config.rs, src-tauri/src/lib.rs, src-tauri/tauri.conf.json
Output directory validation in get_config; registered five new library command handlers; increased main window width from 480 to 900px.
Client-side State Management
src/lib/library.svelte.ts, src/lib/state.svelte.ts, src/lib/library.svelte.test.ts
New LibraryState class and reactive types (RecordingInfo, RecordingMeta) for managing recordings with sorting, filtering, and CRUD operations; comprehensive test suite covering load/delete/sort/meta-update flows; added libraryStale flag to app state.
Navigation & Layout Components
src/lib/Sidebar.svelte, src/lib/StatusBar.svelte, src/routes/(app)/+layout.svelte, src/routes/(app)/+layout.ts
New sidebar with route navigation, status bar showing TTS health and configuration, and grid-based layout wrapper; disabled SSR and enabled prerendering for app layout.
Recording Display & Interactions
src/lib/RecordingCard.svelte, src/routes/(app)/library/+page.svelte
New card component for individual recordings with lazy-loaded thumbnails, metadata display, delete/upload/open actions, and Drive integration; library page with refresh, sorting toolbar, and error/empty/loading states.
Page Updates
src/routes/(app)/+page.svelte, src/routes/(app)/preview/+page.svelte, src/routes/(app)/settings/+page.svelte
Home page guard for pending recordings and voice-mode toggle UI; preview page marks library stale after processing; settings page restructured with tabbed UI (voice/recording/cloud) and save-on-blur for sensitive inputs.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant Backend as Backend<br/>(Tauri)
    participant FS as File System
    participant FFmpeg
    participant Drive as Google Drive

    User->>Frontend: Open Library Page
    Frontend->>Backend: list_recordings()
    Backend->>FS: Scan output_dir for .mp4 files
    Backend->>FS: Read .meta.json companion files
    Backend->>FS: Check .thumbnails/ cache
    Backend-->>Frontend: RecordingInfo[] with metadata
    Frontend->>User: Display recordings list

    User->>Frontend: Scroll to recording
    Frontend->>Frontend: IntersectionObserver triggers
    alt Thumbnail not cached
        Frontend->>Backend: generate_thumbnail(file_path)
        Backend->>FFmpeg: Extract frame at 00:00:01
        FFmpeg->>FS: Write .jpg to .thumbnails/
        Backend-->>Frontend: Thumbnail path
    else Thumbnail cached
        Frontend->>Frontend: Use cached path
    end
    Frontend->>User: Display thumbnail

    User->>Frontend: Upload to Drive
    Frontend->>Drive: OAuth token refresh
    Frontend->>Backend: Invoke Drive upload pipeline
    Backend->>Drive: POST file
    Drive-->>Backend: share_link
    Backend->>FS: Write/update .meta.json with driveUrl
    Backend-->>Frontend: Upload complete
    Frontend->>User: Show Drive link
Loading
sequenceDiagram
    participant Startup
    participant Config as Config<br/>Module
    participant FS as File System
    participant Keychain
    participant Backend as Lib.rs<br/>(Handler Reg.)

    Startup->>Config: get_config()
    Config->>FS: Load AppConfig from JSON
    Config->>Keychain: Overlay keychain secrets
    Config->>Config: Validate output_dir
    alt output_dir empty or missing
        Config->>Config: Use default_output_dir()
        Config->>Config: Emit info log
    end
    Config-->>Backend: AppConfig (validated)
    Backend->>Backend: Register library handlers<br/>(list_recordings, etc.)
    Backend-->>Startup: Application ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #17 — Modifies src-tauri/src/pipeline.rs to pass shared HttpClient to local-TTS calls; related because this PR also modifies pipeline.rs for metadata generation and makes chrono_timestamp public for the same module.
  • PR #11 — Modifies src-tauri/src/config.rs and get_config flow with keychain secret migration; related because this PR adds output-directory validation in the same function post-overlay.
  • PR #12 — Converts get_config to async with spawn_blocking; related because this PR also modifies get_config logic and depends on its post-overlay validation.

Poem

🐰 A library of voices, now neatly arranged,
With thumbnails cached and metadata changed,
Sidebars guide, status bars glow,
Recording cards dance in a UI show,
The vault grows tidy—no files left stray! 🎬✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ui-bugs
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch ui-bugs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@davidbzyk
davidbzyk merged commit b747316 into main Apr 3, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant