crabidy/plan/progressive-queueing.md

5.1 KiB
Raw Permalink Blame History

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)

  • 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)

  • Implement new/cancel_flag/cancel/applied/apply_chunk. apply_chunk: Replacereplace_with_tracks for the first chunk (returns current track), then mutate kind to Append; Appendappend_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)

  • 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".
  • 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.
  • 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

  • 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.
  • 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)

  • 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

  • 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.
  • 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.