88 lines
4.5 KiB
Markdown
88 lines
4.5 KiB
Markdown
# 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 — `Ctrl-b` and `Ctrl-f` are global in both the TUI and the
|
||
web client** and collide with nothing in any scope — in particular plain
|
||
`f` still toggles the spectrum, and the TUI's binding-table uniqueness
|
||
invariant still holds. In the browser the chord must reach the client
|
||
rather than the find bar. *(tests: the existing `bindings`/`keymap`
|
||
tables and their uniqueness tests, extended.)*
|
||
- [ ] **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.
|