69 lines
3.3 KiB
Markdown
69 lines
3.3 KiB
Markdown
# Plan — seek
|
||
|
||
Executes `architecture/seek.md` against `quality/seek.md`. One commit:
|
||
the change is a single thin path from key to sink, and half-wiring it
|
||
would leave a dead RPC.
|
||
|
||
## A — The engine (where the only real logic is)
|
||
|
||
- [x] **A1 — `PlayerEngine::seek_target`** — a pure function of
|
||
(current position, delta millis, known duration) returning the absolute
|
||
target: saturating on both ends, 0 as the floor, `duration - 1 s` as
|
||
the ceiling when the duration is known, no ceiling when it is 0.
|
||
Extracted as a free function so the arithmetic is testable without an
|
||
audio device. *Verifies:* G1, G5, G6, G7.
|
||
- [x] **A2 — Rewrite `PlayerEngine::seek_to`** to route through the same
|
||
helper, dropping the `clamp(1s, duration)` that panics whenever the
|
||
duration is unknown or under a second. *Verifies:* G1.
|
||
- [x] **A3 — `PlayerEngine::seek_by`** — reads `sink.get_pos()`, applies
|
||
A1, calls `try_seek`, and on success notifies
|
||
`PlayerMessage::Elapsed` with the new position so a paused seek is
|
||
visible. Refuses when stopped. *Verifies:* G4, G8, G9.
|
||
- [x] **A4 — `PlayerEngineCommand::SeekBy` + `Player::seek_by`** on the
|
||
existing bounded reply pattern. *Verifies:* G3.
|
||
- [x] **A5 — Engine tests**: extremes (`i64::MIN`/`MAX`), zero duration,
|
||
short track, composition, near-end saturation. *Verifies:* G1, G4–G7.
|
||
|
||
## B — Wire and server
|
||
|
||
- [x] **B1 — Proto**: `rpc Seek(SeekRequest) returns (SeekResponse)` with
|
||
`sint32 delta_millis`, documented with the clamping contract.
|
||
*Verifies:* G10.
|
||
- [x] **B2 — `PlaybackCommand::Seek { delta_millis }`** + its `name()`
|
||
arm, and the playback handler that forwards to `player.seek_by` and
|
||
warns on failure. No state. *Verifies:* G2, G12.
|
||
- [x] **B3 — `RpcService::seek`** following the `change_volume` shape
|
||
(record the field, send, return empty). *Verifies:* G10.
|
||
- [x] **B4 — Server test** that the command reaches the player with the
|
||
delta unchanged. *Verifies:* G11.
|
||
|
||
## C — Clients
|
||
|
||
- [x] **C1 — TUI**: `Action::SeekForward`/`SeekBackward`, `Ctrl-f`/`Ctrl-b`
|
||
bindings in `Scope::Global` with help text, `MessageFromUi::Seek(i64)`,
|
||
the `lib.rs` arm, `rpc.rs` wrapper, `SEEK_STEP` constant, dispatch
|
||
tests. *Verifies:* G13, G14, G15.
|
||
- [x] **C2 — Web**: the same two actions in `keymap.rs` (global, with
|
||
`HELP` rows), `Rpc::seek`, the `app.rs` dispatch arms, and `⏪`/`⏩`
|
||
buttons in the transport bar. *Verifies:* G13, G14, G15.
|
||
- [x] **C3 — CLI**: `GlobalCmd::Seek { seconds: i32 }` with
|
||
`allow_negative_numbers`, and its executor arm. *Verifies:* G14.
|
||
|
||
## D — Docs and gates
|
||
|
||
- [x] **D1 — Docs**: key tables (`docs/src/clients/tui.md`,
|
||
`docs/src/clients/web.md`), the CLI page's `global` list, and the
|
||
within-track/end-of-track/HLS sentences in `docs/src/queue.md`.
|
||
*Verifies:* G16.
|
||
- [x] **D2 — Gates**: workspace tests, `check-features`, `cargo fmt`, a
|
||
wasm build of `cbd-web`, and the book build. *Verifies:* G17.
|
||
- [x] **D3 — `plan/summary.md`** entry, then commit.
|
||
|
||
## Deferred (recorded, not dropped)
|
||
|
||
- Absolute seek + click-to-seek on the web progress gauge (the gauge is
|
||
already rendered; needs one proto field and a click handler).
|
||
- Configurable step size, and a larger step on `<`/`>`.
|
||
- Chapter-aware seek for podcasts/audiobooks — no provider exposes
|
||
chapter marks through the library model.
|