crabidy/quality/captures.md

4.0 KiB

Quality gates: captures

Criteria the implementation must satisfy beyond the automatic tests (crabidy-server/src/capture.rs, crabidy-server/src/capture_store.rs, fsdy/src/lib.rs, plus the TUI tests added during implementation). Each gate is pass/fail by reading the code.

Shared walk (refactor)

  • Bookmarks and download captures run through one walk (capture::capture_into/write_tree); bookmark_store keeps no copy of the worklist, caps, tmp-and-swap, or cleanup logic.
  • The refactor is behavior-preserving for bookmarks: every existing bookmark_store test passes unchanged (module path of CaptureError aside).
  • The walk stays iterative (worklist), all-or-nothing, and removes the temp folder on every failure path — including failed downloads and the byte budget.

Download sink

  • Every external call is bounded: connect timeout on the shared HTTP client and one per-track deadline covering URL fetch, request, and the whole body stream. No retries.
  • Bodies are streamed to disk (never buffered whole) and counted against the capture's byte budget while streaming; exceeding it is TooLarge, not partial data left behind.
  • Non-2xx responses and transport errors are typed (CaptureError::Download) — no panic on any network condition.
  • Download log lines carry paths, names, and counts — never stream URLs (they embed tokens) and never file contents.
  • The toml is written only after its audio file succeeded, with a relative file playable naming the sibling (TrackFile::from_track_with_file); the audio name shares the toml's order prefix and sanitizer (capture::audio_file_name).
  • Downloads run sequentially inside the one spawned capture task; the orchestrator loop keeps serving commands during a capture.

Opt-in (is_downloadable)

  • Tidal sets the flag centrally: nodes are downloadable when queueable or when they list tracks; children mirror is_queable. No per-arm copies to drift.
  • Every other provider (fs, queues, bookmarks, captures, orchestrator root) leaves the flag false — a capture can never be built from another capture's or bookmark's links masquerading as sources.
  • The server enforces the blessing at the capture root (directory source: its own node; track source: its parent node) with CaptureError::Unsupported; an unreadable root is BadSource.

RPC and orchestrator

  • CaptureLibraryNodeRequest.download is additive: old clients (no flag) keep getting bookmarks, byte-for-byte.
  • Error mapping: InvalidName/BadSourceinvalid_argument; TooLarge/Disabled/Unsupportedfailed_precondition; download/walk/write failures → internal.
  • The orchestrator routes /captures in every ProviderClient method (same completeness as /bookmarks), and get_lib_root lists the captures child only when the instance exists.
  • Captures init is non-fatal: no config dir or an unopenable store disables download captures and the /captures mount, never the server (and never bookmarks).
  • /captures mounts with an editable top level (no reserved names): captures are renamable (e) and deletable (d) like bookmarks.

TUI

  • W (shift) is bound in Scope::Library with a help description and passes the bindings-table invariant tests unchanged; w behavior is untouched.
  • The capture overlay opens for W only when the bare selection is queueable and downloadable; tracks inherit their node's flag; marks are ignored. Label: capture (vs bookmark).
  • MessageFromUi::CaptureNode carries download; a failed capture is logged and never tears down the poll loop.

Hygiene

  • New public items are documented; docs state error/edge behavior.
  • clippy -D warnings, fmt, taplo, markdownlint clean on the whole workspace; all tests green.
  • architecture/captures.md reconciled where the implementation diverged (e.g. the exact tidal flag rule).