# 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/ / 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 ```json { "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` ```rust // Public API pub fn cache_dir_for_url(url: &str) -> PathBuf pub fn load_manifest(url: &str) -> Option 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 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` | 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` - `thumbnailSpritesheets: Vec` (with absolute paths resolved from the cache dir) - `captionFilePath: Option` (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` 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.