S4 of the lyrics epic: ms-precision position source, 30Hz sync engine,
word-anchored karaoke fill, auto-centered ladder scroll, QConnect peer
following.
- qbz-player: read-only SharedState::current_position_ms() derived from
the existing playback_start_millis/position_at_start anchors (Q1; no
stream/seek/format path touched) + unit test
- qbz-lyrics: new sync module porting the Tauri active-line math (binary
search active index, per-line progress with the 0.99 snap) plus the
word-anchored clip fraction for Qobuz wsync lines (char-proportional
word width approximation, Q2); unit tests
- qbz-slint: lyrics_sync engine - adaptive slint::Timer (33ms while
panel open + synced + playing, 250ms gate polling otherwise),
equality-guarded pushes, fill animation zeroed across discontinuities
(line change / backward seek); kick() on doc commit and panel open
- QConnect (Q7): the poll loop controller branch publishes the raw
renderer anchor; the engine extrapolates it per tick so lyrics follow
the peer (Tauri freezes); local fallthrough clears the anchor
- LyricsLinesView: karaoke fill via clipped bright overlay text over the
dim base (rect-clip approximation, D5); auto-center scroll from
layout-relative active-row geometry, smooth for <=2-line moves and
instant for jumps/initial sync, animated via a separate scroll-target
property so user wheel input is never animated
- LyricsState: line-progress + fill-anim-ms properties
cc
S2 of the Lyrics epic: new shared crate crates/qbz-lyrics (headless,
frontend-agnostic per ADR-006), the whole fetch/parse/cache engine in one
testable home for Slint, Tauri and the TUI.
- Domain model: LyricsDoc/LyricsLine/Word (native Qobuz wsync word stamps
preserved) + the Tauri-wire-compatible LyricsPayload (camelCase serde);
LyricsProvider gains the qobuz variant and round-trips it.
- LRC parser ported from the Tauri TS semantics (gap markers as end-of-vocal,
8s MAX_SUNG cap, median last-line end) with the F5 defects fixed forward:
multi-stamp lines split per stamp, [offset:] honored. Emitter renders wsync
line bounds as LRC-with-gap-markers (Q5 persistence form, lossy at word
level only).
- Providers ported verbatim from src-tauri/src/lyrics/providers.rs: LRCLIB
search-first + scorer and lyrics.ovh, request shapes byte-identical.
- Cache: same per-user SQLite schema/path Tauri uses (WAL per ADR-002,
no TTL) plus the additive qobuz_wsync_json column (amended Q5) with an
idempotent in-place migration for Tauri-era DBs; readers prefer wsync.
Stats read entries AND size from the per-user path (F1 fix).
- Orchestrator LyricsService: cache probe with uniform plain-only soft miss
(Q4) -> Qobuz primary (wsync authoritative; plain held while LRCLIB is
probed for synced, the no-sync-regression rule) -> LRCLIB with one retry
on transport error -> lyrics.ovh; offline = cache-only with typed
NotAvailableOffline (F3); in-flight dedupe via shared futures (F6);
request-key echo on every response for the caller stale guard (F2).
- 55 unit tests: parser/emitter round-trips, scorer matrix, cache lifecycle
+ schema migration, chain priority/offline/dedupe with mocked providers.
cc