crabidy/plan/queue-persistence.md

69 lines
3.9 KiB
Markdown

# 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<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".
- [x] **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.
- [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.