95 lines
5.1 KiB
Markdown
95 lines
5.1 KiB
Markdown
# Plan — progressive queueing
|
||
|
||
Ordered tasks for the `implement` stage. Inputs:
|
||
`architecture/progressive-queueing.md`, the workspace stubs, gates in
|
||
`quality/progressive-queueing.md`. Tests:
|
||
`devenv shell -- cargo test --workspace` (13 failing at plan time = the
|
||
target: 5 in crabidy-core, 5 in crabidy-server, 2 in cbd-tui, 1 in tidaldy).
|
||
Use the session-local `CARGO_TARGET_DIR` if `target/` contains artifacts
|
||
owned by the repo owner.
|
||
|
||
## 1. Default chunked resolve (crabidy-core)
|
||
|
||
- [x] Implement the `resolve_tracks_into` default body: track path →
|
||
single chunk from `get_metadata_for_track` (failure → warn, `Ok`, no
|
||
chunk — matches today's skip); node path → depth-first **pre-order**
|
||
walk (stack of paths, children pushed in reverse so siblings pop in
|
||
listing order), per node: skip unreadable with a warning (root
|
||
failure → `Err`), skip non-queueable, send `tracks` as one chunk when
|
||
non-empty; a failed send (receiver gone) → return `Ok` immediately.
|
||
**Verify**: all 5 `default_resolve_*` tests pass.
|
||
|
||
## 2. PendingResolve state machine (crabidy-server lib)
|
||
|
||
- [x] Implement `new`/`cancel_flag`/`cancel`/`applied`/`apply_chunk`.
|
||
`apply_chunk`: `Replace` → `replace_with_tracks` for the first chunk
|
||
(returns current track), then mutate kind to `Append`; `Append` →
|
||
`append_tracks`; `InsertAfter(pos)` → `insert_tracks(pos)` then
|
||
advance the cursor by `tracks.len()` (clamping is `insert_tracks`'
|
||
job); count `applied`. **Verify**: the 5 op tests pass
|
||
(`replace_op_*`, `insert_after_op_*` ×2, `append_op_*`,
|
||
`cancel_flag_*`).
|
||
|
||
## 3. Playback loop wiring (crabidy-server)
|
||
|
||
- [x] Implement `broadcast_queue` (snapshot → `Queue`, set `resolving`
|
||
from the pending map, broadcast) and route the remaining direct
|
||
`StreamUpdate::Queue(...)` broadcasts in `handle_command` through it.
|
||
**Verify**: gate "resolving is true iff at least one op is pending".
|
||
- [x] Implement `start_resolve`: allocate op id, insert `PendingResolve`,
|
||
immediate `broadcast_queue`, spawn the forwarder
|
||
(`tokio::spawn` + `in_current_span`): per path in order — check the
|
||
cancel flag (set → stop), open `flume::bounded(4)` chunk channel,
|
||
send `ProviderCommand::ResolveTracks`, forward each chunk as
|
||
`ApplyResolvedChunk` (checking the cancel flag between chunks, drop
|
||
the receiver on cancel); after all paths (or on any exit path) send
|
||
`ResolveFinished`. Track the op's paths for the zero-track warning.
|
||
**Verify**: gates "immediate feedback", "cancellation propagates",
|
||
"no panics"; `cargo check`.
|
||
- [x] Implement `apply_resolved_chunk` (look up op — unknown id: drop
|
||
silently; lock queue, `apply_chunk`, `broadcast_queue`, then
|
||
`play_if_some` outside the locks), `finish_resolve` (remove op, warn
|
||
when `applied() == 0` with the op's paths, `broadcast_queue`), and
|
||
`cancel_pending_resolves` (cancel + clear map; no broadcast needed —
|
||
the caller mutates and broadcasts next). **Verify**: gates "playback
|
||
starts with the first chunk", "late chunks dropped"; existing
|
||
playback tests still green.
|
||
|
||
## 4. tidaldy page-streamed resolve
|
||
|
||
- [x] Implement `make_paginated_request_into` (same loop as
|
||
`make_paginated_request`, `sink(page.items)` per page, stop on
|
||
`false`). **Verify**: gate "token/refresh path reused"; `cargo check`.
|
||
- [x] Implement the `resolve_tracks_into` override: `parse_path` first
|
||
(foreign → `MalformedPath` before any I/O); track paths → one
|
||
metadata chunk; pre-order worklist walk where `Playlist` and `Album`
|
||
nodes stream `tracks/items` pages as chunks (`Track::to_proto` per
|
||
page, no playlist-metadata fetch) and all other node kinds fall back
|
||
to `get_lib_node` (tracks as one chunk when queueable, children onto
|
||
the worklist in listing order). Failed sends → stop, `Ok`.
|
||
**Verify**: `resolve_rejects_foreign_paths_before_any_network_call`
|
||
passes; gate "order preserved end-to-end".
|
||
|
||
## 5. TUI indicator (cbd-tui)
|
||
|
||
- [x] Implement `Queue::loading_dots` (1 + (elapsed_ms / 400) % 3 dots)
|
||
and render the pseudo-item: when `resolving`, push one extra
|
||
`ListItem` (dots, `COLOR_SECONDARY`) after the track rows, computed
|
||
from a monotonic clock at render time; `self.list` stays untouched.
|
||
**Verify**: `loading_dots_cycle_one_to_three`,
|
||
`resolving_queue_renders_trailing_dots_item` pass; the three guard
|
||
tests stay green.
|
||
|
||
## 6. End-to-end + gates sweep
|
||
|
||
- [x] Exercise the full path against the live API if the local tidal
|
||
config is available (temporary ignored probe: resolve a multi-album
|
||
artist through the orchestrator, assert multiple chunks arrive, in
|
||
listing order, first chunk before the walk completes; drop the
|
||
receiver mid-stream and confirm fetching stops). Note the outcome in
|
||
the summary; if unavailable, rely on the offline tests and say so.
|
||
- [x] Sweep: no `todo!()` left (workspace grep), fmt + clippy + tests
|
||
green, every `quality/progressive-queueing.md` box checked, docs
|
||
updated where behavior shifted. Append the outcome + deviations to
|
||
`plan/summary.md`.
|