feat(onboarding): guided first-run tour with background FFmpeg provisioning

Phase 5 of the 0.1.0 release prep:
- FFmpeg no longer blocks startup (or crashes the app offline): provisioning
  runs in a background thread emitting throttled ffmpeg-progress events,
  with installed/downloading/failed state queryable via get_ffmpeg_status
  and a retry command
- video jobs are invisible to claiming and tier-priority checks until FFmpeg
  is ready, so they wait as pending without failing — and without starving
  image embeddings/tagging (include_videos gating in db.rs)
- 7-step show-don't-tell onboarding wizard: welcome + live FFmpeg progress,
  real first-folder picker, faked animating pipeline bar, placeholder
  gallery tiles, cycling search-syntax demo, views overview, and an AI
  features step with a real opt-in tagger download
- skippable at every point (Escape included), persisted via
  settings/onboarding_completed.txt, re-runnable from Settings > General,
  which also gains an FFmpeg status/retry row
This commit is contained in:
2026-06-12 23:09:05 +01:00
parent 1640e30330
commit 7403f0cfeb
17 changed files with 1139 additions and 30 deletions
+12 -2
View File
@@ -142,14 +142,18 @@ fn higher_priority_work_pending(pool: &DbPool, own_tier: WorkerTier) -> Result<b
let conn = pool.get()?;
let active_folders = active_indexing_folders();
// Video jobs are unclaimable until FFmpeg is provisioned; they must not
// count as pending higher-priority work or they'd stall lower tiers.
let ffmpeg_ready = crate::media::ffmpeg_ready();
if own_tier > WorkerTier::Thumbnail {
let mut excluded = paused_folder_ids("thumbnail");
excluded.extend(active_folders.iter().copied());
if db::has_claimable_thumbnail_jobs(&conn, &excluded)? {
if db::has_claimable_thumbnail_jobs(&conn, &excluded, ffmpeg_ready)? {
return Ok(true);
}
}
if own_tier > WorkerTier::Metadata {
if own_tier > WorkerTier::Metadata && ffmpeg_ready {
let mut excluded = paused_folder_ids("metadata");
excluded.extend(active_folders.iter().copied());
if db::has_claimable_metadata_jobs(&conn, &excluded)? {
@@ -641,6 +645,7 @@ fn process_thumbnail_batch(
&mut conn,
&active_folders,
&paused_folders,
crate::media::ffmpeg_ready(),
worker_fetch_size,
worker_batch_size,
)
@@ -748,6 +753,11 @@ fn process_metadata_batch(
pool: &DbPool,
media_tools: &MediaTools,
) -> Result<bool> {
// Metadata jobs are video-only and need ffprobe; leave them pending until
// FFmpeg is provisioned — the worker poll loop drains them once ready.
if !crate::media::ffmpeg_ready() {
return Ok(false);
}
if higher_priority_work_pending(pool, WorkerTier::Metadata)? {
return Ok(false);
}