docs(traces): tag the twelve "Done but untraced" requirements, and stop the matrix over-reporting

Twelve requirements were marked Done in docs/requirements.md with zero TRACES
anywhere in the tree. The features work — the tags were simply never written —
so the matrix over-reported on exactly the requirements a reviewer would most
want to verify. Each is now tagged at the code that actually implements it:

- JA-006 / JA-009 / JA-013 / JA-014 / JA-015 / JA-018 and IR-022 / IR-024 at
  their Jellyfin call sites in repository/online.rs (search, get_item's
  MediaStreams/People fields, Items/Resume, Shows/NextUp, FavoriteItems DELETE,
  get_person/get_items_by_person), plus the commands that expose them.
- UR-006 / IR-006 across the lockscreen spine: JellyTauPlaybackService (the
  MediaSessionCompat owner), the nativeOnMediaCommand JNI intake, and
  LockscreenMetadata / update_lockscreen_metadata.
- IR-008 at both audio-focus mechanisms — ExoPlayer-managed for audio, the
  manual AudioFocusRequest listener for video — and at the media-type string
  that chooses between them.
- UR-037 (with DR-042, also untraced) on the video-library poster grid:
  LibraryGrid, MediaCard, and the tv/movies routes.

Resolve contradictory statuses across layers, evidence first:

- IR-018/IR-019 were Planned under Done URs because they were scoped to libmpv.
  MpvBackend is the audio-only backend and overrides neither
  set_subtitle_track nor set_audio_track — the trait's not_implemented()
  default still stands — so UR-020/UR-021 are met by ExoPlayer and by the
  HTML5 <video> path instead. Both IRs are re-scoped to those backends and
  marked Done; IT-008/IT-009 and the stale @req-planned markers in backend.rs
  follow.
- IR-005 (MPRIS) stays Planned: there is no MPRIS/D-Bus code or dependency in
  the project and update_lockscreen_metadata is a no-op off Android. UR-006 is
  corrected to Done (Android) rather than the IR being marked Done.
- A note under the IR table records where a UR is met by a different mechanism
  than its IR anticipated.

Define the two dangling IDs the source already referenced: DR-189 (the control
bar never auto-hid on a touchscreen, because its timer was armed only from
onmousemove) and UT-188 (its rule test). The live-denominator assertion in
extract-traces.test.ts moves 187/330 to 188/331 accordingly.

Traced requirements 444 to 459; IR coverage 19/32 to 25/32.
This commit is contained in:
2026-08-16 22:58:55 +02:00
parent 73641e192c
commit ebf9a99b80
16 changed files with 5059 additions and 4512 deletions
@@ -27,6 +27,17 @@ import com.google.common.util.concurrent.ListenableFuture
*
* Media commands are routed back to Rust via JNI to ensure proper
* queue management for next/previous track operations.
*
* This class owns both sessions: the media3 [MediaSession] the service contract
* requires, and the legacy [MediaSessionCompat] that actually carries the
* lockscreen transport. The compat session is flagged
* FLAG_HANDLES_MEDIA_BUTTONS or FLAG_HANDLES_TRANSPORT_CONTROLS, which is what
* makes a Bluetooth headset's AVRCP play/pause/skip arrive as a transport
* callback; every one of those callbacks is forwarded to Rust through
* nativeOnMediaCommand rather than acted on locally, so the player stays the
* single source of truth and the session remains a consumer of its state.
*
* TRACES: UR-006 | IR-006
*/
@OptIn(UnstableApi::class)
class JellyTauPlaybackService : MediaSessionService() {
@@ -263,7 +263,15 @@ class JellyTauPlayer(private val appContext: Context) {
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build()
// Create ExoPlayer with audio focus handling
// Create ExoPlayer with audio focus handling.
//
// For audio playback ExoPlayer manages focus itself: handleAudioFocus=true
// makes it request AUDIOFOCUS_GAIN on play, duck on a transient loss, and
// pause on a call or another app taking focus. Video re-applies this per
// load with handleAudioFocus=false and drives focus manually instead (see
// requestAudioFocus), because a video needs delayed-focus handling.
//
// TRACES: UR-004, UR-006 | IR-008
exoPlayer = ExoPlayer.Builder(appContext)
.setAudioAttributes(audioAttributes, /* handleAudioFocus= */ true)
// Pause when the audio output is removed (wired headphones unplugged or
@@ -1328,7 +1336,12 @@ class JellyTauPlayer(private val appContext: Context) {
* Request audio focus for video playback.
* This is critical for video to have audio on Android.
*
* TRACES: UR-004 | DR-145
* The listener installed here is the pause-on-call path: AUDIOFOCUS_LOSS and
* AUDIOFOCUS_LOSS_TRANSIENT (an incoming call is the latter) both pause,
* LOSS_TRANSIENT_CAN_DUCK lowers the volume instead, and GAIN restores —
* resuming only what we paused, via pendingPlayOnFocusGain.
*
* TRACES: UR-004, UR-006 | IR-008, DR-145
*
* @return true if focus was granted outright and playback may start now.
* false for a DELAYED or refused request — the caller must hold playback