crabidy/quality/progressive-queueing.md

4.9 KiB

Quality gates — progressive queueing

Criteria for architecture/progressive-queueing.md. Automatic tests live in crabidy-core/src/lib.rs (default resolve_tracks_into walk, fake provider), crabidy-server/src/lib.rs (PendingResolve chunk application), tidaldy/src/lib.rs (offline path validation), and cbd-tui/src/app/queue.rs (indicator rendering). They fail while the stubs are unimplemented — that is the target state for implement.

Run: devenv shell -- cargo test --workspace (session-local CARGO_TARGET_DIR when target/ is owner-built).

Channel and concurrency gates (verify by reading)

  • All new channels are bounded. The chunk channel is small (≤ 8 chunks); forwarder → playback reuses the existing bounded(64). No unbounded() anywhere in the feature.
  • No lock is held across an await. The pending map and queue mutexes are locked, used, and released inside synchronous blocks only — same discipline as the existing playback handlers.
  • The playback loop stays the single writer of queue state. Neither the forwarder task nor the provider resolve task touches QueueManager or broadcasts; they only send commands.
  • The provider loop never blocks on a resolve. The ResolveTracks arm spawns; GetTrackUrls for the first chunk's track must be servable while a resolve is still streaming (deadlock check: playback awaiting GetTrackUrls + provider awaiting a chunk send must be impossible).
  • Channel semantics are documented where they are the contract: sender-drop = finished, receiver-drop = cancel, on both ProviderClient::resolve_tracks_into and ProviderCommand::ResolveTracks.

Cancellation gates

  • Replace and Clear cancel every pending op before mutating the queue; Queue/Append/Insert cancel nothing.
  • Cancellation propagates to the network fetch: the forwarder drops the chunk receiver, and both resolve implementations (default walk and tidaldy override) treat a failed chunk send as "stop fetching, return Ok" — verified by test for the default, by reading for tidaldy's page loop.
  • Late chunks for finished/cancelled ops are dropped silently (no error, no queue mutation).

Behavior gates

  • Immediate feedback: accepting a resolve op broadcasts a Queue snapshot with resolving = true before the first chunk arrives.
  • resolving is true iff at least one op is pending, and every queue broadcast from the playback loop goes through the one helper that sets it (grep: no direct StreamUpdate::Queue( construction in the op/chunk paths besides broadcast_queue).
  • Playback starts with the first chunk that makes a track current (replace, or landing in an empty queue) and never restarts for later chunks of the same op — covered by the PendingResolve tests, plus reading the apply_resolved_chunkplay_if_some wiring.
  • Order preserved end-to-end: pages arrive in collection order, nodes in listing order (pre-order walk — the old LIFO reversal must not reappear), multi-path requests resolve sequentially in request order, and InsertAfter keeps the whole op contiguous.
  • An op that finishes with zero tracks logs the existing "resolved to no playable tracks" warning with enough context (paths).

Error and robustness gates

  • No panics on provider failure, in any spawned task. Unreadable nodes are skipped with a warning; a dead provider channel ends the op (with ResolveFinished still sent, so resolving clears) instead of leaving a stuck indicator.
  • Tracing spans survive the spawns: forwarder and provider resolve tasks attribute their events to the originating request's span (ProviderMessage::new capture + in_current_span/explicit parent).
  • tidaldy page loop honors the existing token/refresh path (reuses make_request) and adds no new retry logic.

Wire and UI gates

  • Proto change is additive only: Queue.resolving = 4, no rpc shape changes; field documented in the proto.
  • The TUI pseudo-item is render-only: never in the list model, unreachable by selection/removal/navigation (get_size unchanged) — covered by tests.
  • Indicator style matches the pane (COLOR_SECONDARY, inside the queue block, after the last track).
  • Old client / new server and new client / old server both degrade cleanly (flag ignored / never set — reasoning check against the generated proto defaults).

Documentation gates

  • ProviderClient::resolve_tracks_into docs state the full channel contract (order, completion, cancellation, error policy).
  • Deviations from the architecture are recorded in plan/summary.md under this feature.