crabidy/plan/queue-persistence.md

3.9 KiB

Plan: queue-persistence

Ordered tasks; each names its verification (tests in fsdy/src/lib.rs / crabidy-server/src/queue_store.rs and/or gates in quality/queue-persistence.md). Stubs and workspace wiring exist; the new fsdy/queue-store tests fail on todo!() at plan time.

  • T1 — fsdy instance parameterization. Client::new(provider_root, disk_root) with validation; replace every hardcoded "/fs"/"/fs/" inside Client methods with instance state (disk_path, is_track_path, list_dir title/paths, get_lib_root); init builds the /fs instance via new. Verifies: instances_serve_their_own_provider_root, instance_paths_cannot_escape_their_root_either, new_rejects_malformed_roots; gates "fsdy instances" (prefix derivation, single traversal site).
  • T2 — fsdy serialization. TrackFile::from_track (uniform link playable), to_toml, track_file_name (zero-padded prefix + sanitized title). Verifies: from_track_round_trips_through_a_link_file, from_track_serializes_sparse_metadata, track_file_names_sort_in_queue_order_and_stay_plain, links_into_fs_instances_are_legal_and_one_hop (rule relaxation, done at stub time).
  • T3 — QueueStore. open (create_dir_all), validate_name, save/persist_current via one tmp-and-swap writer (entries + .queue-state.toml sidecar), load_current (sorted listing, skip broken, default state on sidecar defects). Verifies: all queue_store::tests except persister_writes_the_latest_snapshot; gates "Queue store".
  • T4 — persister task. spawn_persister: watch changes → debounce → skip-if-equal → persist_current, warn on failure, exit on sender drop. Verifies: persister_writes_the_latest_snapshot; gates "playback wiring" (debounce/skip, warnings only).
  • T5 — playback loop wiring. Playback::new takes Option<Arc<QueueStore>>; watch sender + persist sends from broadcast_queue, play, and the shuffle/repeat toggles; run spawns the persister; PlaybackCommand::SaveQueue { name, result_tx } handled on the loop (snapshot, spawn write, reply); restore_current applies tracks + position + mods before run, no autoplay. Playback-level tests (restore → Init snapshot; SaveQueue ok/empty; a queue mutation reaches the store). Verifies: new playback::tests; gates "Playback wiring".
  • T6 — orchestrator + server startup. queues_client: Option<Arc<fsdy::Client>> mounted over QueueStore::dir at /queues; routing arms in every trait method; root child when present; main.rs builds the store (non-fatal), restores, spawns everything in order. Verifies: gates "RPC and orchestrator"; workspace build.
  • T7 — rpc save_queue. Send SaveQueue to the playback loop, map SaveQueueErrorStatus (invalid_argument / failed_precondition / internal). Verifies: gate "RPC and orchestrator" (error mapping).
  • T8 — TUI save flow. Action::QueueSaveAs (w, Scope::Queue), InputPurpose::SaveQueue (+ overlay label), MessageFromUi::SaveQueue, orchestrator arm → rpc::save_queue. TUI tests: binding lookup, overlay open only with a non-empty queue, submit sends the trimmed name. Verifies: new cbd-tui tests; gates "TUI".
  • T9 — full verification. Whole workspace suite green; clippy/fmt/taplo/markdownlint clean; walk every gate in quality/queue-persistence.md and tick it; no todo!() left.
  • T10 — live smoke test. Temp store + real tree: persist a mixed queue (tidal + fs tracks), reload it, browse /queues through the provider instance, queue a saved folder via the resolve walk — remove any temporary probe afterwards. Verifies: end-to-end D2/D5 behavior outside unit scope.
  • T11 — docs. plan/summary.md section incl. deviations; reconcile architecture/fs-provider.md (one-hop links) and architecture/queue-persistence.md if the implementation diverged.