crabidy/quality/seek.md

97 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 **paused**`tick()` 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.