# 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. - [x] **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). - [x] **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). - [x] **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". - [x] **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). - [x] **T5 — playback loop wiring.** `Playback::new` takes `Option>`; 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". - [x] **T6 — orchestrator + server startup.** `queues_client: Option>` 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. - [x] **T7 — rpc `save_queue`.** Send `SaveQueue` to the playback loop, map `SaveQueueError` → `Status` (invalid_argument / failed_precondition / internal). Verifies: gate "RPC and orchestrator" (error mapping). - [x] **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". - [x] **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. - [x] **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. - [x] **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.