Files
gui-video-clipper/docs/superpowers/specs/2026-09-22-v0.1.2-polish-design.md

9.4 KiB

v0.1.2 Polish: Placeholders, Auto-Upgrade, and Analysis Cache

Date: 2026-09-22 Version: 0.1.2 Status: Design

Overview

Three polish features for v0.1.2:

  1. Fix misleading placeholder text shown before any video is loaded
  2. Automatically use cached HQ video files instead of prompting to upgrade
  3. Cache video analysis results (waveforms, keyframes, thumbnails, captions) to avoid reprocessing

1. Idle-State Placeholder Text

Problem

When the app first loads with no video, the timeline shows "Generating Waveform..." and "Generating Thumbnails..." in the placeholder lanes. No work is happening — this is misleading.

Solution

Make drawTimeline context-aware by accepting a hasVideo boolean (derived from session.status === 'ready').

  • No video loaded (hasVideo === false): Draw empty lane backgrounds with no text.
  • Video loaded, data pending (hasVideo === true, data missing): Show "Generating Waveform..." / "Generating Thumbnails..." as today.

Files Changed

  • src/lib/timeline/renderer.ts: Add hasVideo parameter to drawTimeline(). Update drawPlaceholderLane() to accept a boolean controlling whether to show text. When hasVideo is false, render the background rectangle but skip the text label.
  • src/lib/components/Timeline.svelte: Pass session.status === 'ready' as the hasVideo argument.

2. Auto-Use Cached HQ Video

Problem

When a video's HQ (export) file is already cached on disk from a prior session, the app still downloads a low-res preview, then shows an "Upgrade" toast to switch to HQ. The user should get the HQ version immediately.

Solution

At the start of beginDownload(), check the export cache first. If the HQ file exists:

  • Set activeVideoPath to the HQ file immediately
  • Set exportFilePath, exportStatus = 'complete', exportProgress = 1.0
  • Mark preview as complete too (skip the preview download entirely)
  • Run triggerPostDownloadProcessing() against the HQ file
  • No upgrade toast shown

When no cached HQ exists, the existing two-track download flow (preview first, background HQ) continues unchanged. The upgrade toast still appears when HQ completes mid-session — silently swapping the video source during active playback would be jarring.

Behavior Matrix

HQ cached? Preview cached? Behavior
Yes (irrelevant) Use HQ directly. No preview download. No toast.
No Yes Use cached preview. Start background HQ download. Toast on HQ completion.
No No Download preview, start background HQ. Toast on HQ completion.

Files Changed

  • src/lib/stores/videoSession.svelte.ts: Restructure beginDownload() to check export cache before preview cache. Add early-return path that sets HQ as active video.

3. Analysis Cache System

Problem

Every time a video is loaded, waveforms, keyframes, and thumbnails are re-extracted from scratch via ffmpeg. For a 5-minute video this takes 10-30 seconds. Re-opening the same video should be instant.

Architecture

Cache Directory

~/Library/Application Support/gui-video-clipper/cache/
  <url-hash>/
    manifest.json
    thumbnails/
      frame_000001.jpg
      frame_000002.jpg
      ...
    captions/
      en.vtt

The URL hash is the first 16 hex characters of the SHA-256 of the source URL. This provides sufficient collision resistance while keeping paths short.

Manifest Schema

{
  "version": 1,
  "sourceUrl": "https://youtube.com/watch?v=abc123",
  "title": "Example Video",
  "duration": 312.5,
  "fps": 30.0,
  "createdAt": "2026-09-22T18:00:00Z",
  "lastAccessedAt": "2026-09-22T18:05:00Z",
  "waveformTiers": {
    "tier0": [0.1, 0.5, ...],
    "tier1": [0.1, 0.3, 0.5, ...],
    "tier2": [0.05, 0.1, 0.15, ...]
  },
  "keyframePositions": [0.0, 2.5, 5.0, 7.5],
  "thumbnailSpritesheets": [
    {
      "filePath": "thumbnails",
      "startIndex": 0,
      "count": 100,
      "thumbWidth": 160,
      "thumbHeight": 90,
      "columns": 10,
      "intervalSeconds": 1.0
    }
  ],
  "captionFilePath": "captions/en.vtt"
}

Data sizing: Waveform tier2 is at most 200K floats (~1.6 MB as JSON). Tier0 and tier1 add ~100 KB combined. Keyframes are a few hundred entries. This all fits comfortably inline in JSON. Thumbnails are JPEG files on disk, referenced by relative path.

All paths in the manifest are relative to the cache entry directory.

New Rust Service: cache_manager.rs

// Public API
pub fn cache_dir_for_url(url: &str) -> PathBuf
pub fn load_manifest(url: &str) -> Option<CacheManifest>
pub fn save_manifest(url: &str, manifest: &CacheManifest) -> Result<(), String>
pub fn clear_cache_entry(url: &str) -> Result<(), String>
pub fn clear_all_cache() -> Result<(), String>
pub fn get_cache_stats() -> Result<CacheStats, String>
pub fn thumbnail_cache_dir(url: &str) -> PathBuf
pub fn caption_cache_dir(url: &str) -> PathBuf

CacheManifest struct mirrors the JSON schema above. CacheStats contains entry_count: usize and total_bytes: u64.

New IPC Commands

Added to src-tauri/src/commands/ (new file cache.rs or added to existing):

Command Signature Purpose
load_cached_analysis (url: String) -> Option<CachedAnalysis> Load all cached data for a URL
save_analysis_to_cache (url, waveform_tiers, keyframe_positions, thumbnail_spritesheets, caption_file_path) -> () Persist analysis results
clear_video_cache (url: String) -> () Delete one video's cache
clear_all_cache () -> () Delete the entire cache dir
get_cache_stats () -> CacheStats Get entry count and total size
get_thumbnail_cache_dir (url: String) -> String Get the cache directory path for thumbnail output
get_caption_cache_dir (url: String) -> String Get the cache directory path for caption output

CachedAnalysis contains:

  • waveformTiers: WaveformTiers
  • keyframePositions: Vec<f64>
  • thumbnailSpritesheets: Vec<ThumbnailSpritesheet> (with absolute paths resolved from the cache dir)
  • captionFilePath: Option<String> (absolute path)

save_analysis_to_cache accepts the individual analysis fields rather than a wrapper struct, keeping the IPC boundary simple. The Rust side constructs the CacheManifest from these fields plus metadata (source URL, title, timestamps) looked up from the manifest or computed fresh.

Frontend Integration

videoSession.svelte.ts — triggerPostDownloadProcessing():

  1. Call load_cached_analysis(session.url) before running ffmpeg.
  2. On cache hit: populate session state directly, jump to processingStep = 'done'. Skip all ffmpeg extraction.
  3. On cache miss: run extraction as today. After all steps complete (waveform, keyframes, thumbnails, captions), call save_analysis_to_cache(...) to persist.

New TypeScript bindings: src/lib/bindings/cache.ts with wrapper functions for the new IPC commands.

Thumbnail Path Changes

Currently, extract_thumbnails (Rust command) generates a UUID-based temp dir for thumbnails. With caching:

  • Add an optional output_dir: Option<String> parameter to the extract_thumbnails Tauri command. When provided, thumbnails are written there. When None, the existing UUID-based temp dir behavior is preserved as a fallback.
  • The frontend calls get_thumbnail_cache_dir(session.url) to get the cache-managed output path, then passes it to extract_thumbnails.
  • Similarly, get_caption_cache_dir(session.url) provides the path for caption file output.

Cache Invalidation UX

Preferences Panel (PreferencesPanel.svelte):

  • New "Cache" section at the bottom
  • Display total cache size (e.g., "Cache: 450 MB across 12 videos")
  • "Clear All Cache" button with confirmation

Per-Video Reprocess (in StatusBar.svelte or near the transport controls):

  • A small "↻" (reprocess) button visible when a video is loaded and processing is complete (processingStep === 'done')
  • Calls clear_video_cache(session.url) then re-runs triggerPostDownloadProcessing() on the current video file
  • This re-extracts analysis data only — it does not re-download the video

Future: Project Files

The cache manifest structure is designed to be referenced by future project files. A project file would store:

  • The source URL
  • Clip regions (start/end times, labels, colors)
  • Caption settings
  • A reference to the cache entry (by URL hash)

This design means a project file doesn't duplicate analysis data — it points to the cache. If the cache is cleared, the project can re-derive everything from the source URL.

No project file save/load UI is implemented in this version. The manifest structure is the forward-looking scaffold.

Dead Code Annotations

Any types or fields scaffolded for future project file support (e.g., fields in CacheManifest not yet read by the frontend) will be annotated with #[allow(dead_code)] and a // Future: used by project file save/load comment. This satisfies the requirement to handle dead code warnings proactively (item 3b).

Constraints

  • Zero new warnings/errors: All existing warnings and errors were recently resolved. Any new warnings introduced during implementation must be addressed immediately.
  • macOS-only paths: ~/Library/Application Support/ is macOS-specific, which matches the project's macOS-only target.
  • Backward compatibility: Existing temp-dir downloads (preview/export) continue to work as before. The cache is additive — it doesn't replace the download mechanism.