crabidy/quality/seek.md

5.0 KiB
Raw Permalink Blame History

Quality gates — seek

Criteria an implementation of architecture/seek.md must satisfy. Automated coverage lives in audio-player/src/player_engine.rs (the clamping arithmetic, which is where the interesting behaviour is), crabidy-server/src/playback.rs, and the two clients' binding tests.

Hard rules (highest priority)

  • G1 — No panic on any delta, from any state. The clamp is saturating arithmetic with no assert/clamp(min, max) where min > max is reachable. Specifically: an unknown duration (reported as 0), a track shorter than the step, a delta larger than the track, i64::MIN/i64::MAX as the delta, and a stopped or empty sink must all be handled without panicking. This closes the existing bug in seek_to (D5). (tests: seek_target_never_panics_on_extremes, seek_target_saturates_at_the_start, an_unknown_duration_does_not_clamp_forward.)
  • G2 — A failed seek stays server-side. Seek never returns a color-eyre/anyhow report to a client; an unseekable source produces a warn! and an unchanged position, not an RPC error (D7).
  • G3 — No new unbounded channel and no new blocking call on an async task. The seek reply rides the existing bounded reply pattern; try_seek is called only from the engine thread.

Behaviour

  • G4 — Deltas compose. Two successive backward seeks from the same starting position land at start - 2 × step, not start - step — the engine reads the live position each time (D1). (test: successive_seeks_compose.)
  • G5 — Backward saturates at zero, never below, and never into the previous track (A1).
  • G6 — Forward past the end lands one second before the end when the duration is known, so the track finishes through the normal end-of-stream path and the queue advances — the engine never seeks to the exact end (D4). (test: seek_target_saturates_near_the_end.)
  • G7 — An unknown duration does not clamp forward. A length-less stream reports duration 0; that must not turn every forward seek into "seek to 0" (the naive clamp) — it must pass the target through and let the decoder refuse it.
  • G8 — The new position is broadcast immediately, including while pausedtick() returns early for a paused sink, so the engine emits Elapsed from the seek path itself (D6).
  • G9 — Seeking while stopped does nothing and does not start playback (A4).

Wire and plumbing

  • G10 — One RPC, relative only: Seek(SeekRequest{sint32 delta_millis}) -> SeekResponse, fire-and-forget like the other playback RPCs (D2, A3). No absolute field, no oneof — the deferred extension stays compatible.
  • G11 — The delta is never re-derived client-side. No client computes a target position from a broadcast TrackPosition; grep for arithmetic on position in the clients and find none (D1, A2). (test: seek_command_forwards_the_delta_unchanged.)
  • G12 — The playback loop holds no seek state. PlaybackCommand::Seek forwards to the player and does nothing else — no position cache to go stale.

Clients

  • G13 — ,/. seek and </> skip tracks, global in both clients, colliding with nothing in any scope, with the TUI's binding-table uniqueness invariant intact and the shifted forms resolving whether or not the terminal reports SHIFT. Being plain printable characters, none of them can be swallowed by a browser — which Ctrl-n is, and which is why </> exist. (tests: the bindings/keymap tables and their uniqueness tests, extended.)
  • G18 — A progress-bar click becomes an offset inside the track. The mapping is a pure function tested on the native target: a click behind the playhead seeks back, the edges are exactly the track's ends, a fraction outside [0, 1] is clamped rather than extrapolated, and a duration of 0 (or a non-finite fraction, meaning a zero-width element) declines instead of seeking somewhere arbitrary. (tests: a_progress_bar_click_becomes_an_offset, a_click_without_a_scale_is_declined.)
  • G14 — Every client reaches it: TUI binding → MessageFromUi → RPC; web binding + / buttons → RPC; cbd global seek <SECONDS> accepting a negative value (D8).
  • G15 — Both help surfaces list it — the TUI help modal (from BINDINGS) and the web help overlay (HELP) — so the two tables stay in lockstep with dispatch, which their tests enforce.

Documentation

  • G16 — Documented where a user looks: the key tables in docs/src/clients/tui.md and the web page, the CLI page's global list, and a sentence in docs/src/queue.md saying seek is within-track, that a forward seek past the end advances, and that SoundCloud's HLS streams cannot seek.
  • G17 — Clippy clean under -D warnings across the feature matrix (check-features), fmt clean, and the wasm target builds — the web client's mod app is #[cfg(target_arch = "wasm32")] and native clippy does not see it.