E frontend reconciliation (2/2): align consumers to generated bindings; svelte-check clean
Traceability Validation / Check Requirement Traces (push) Failing after 21s
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 13m21s
🏗️ Build and Test JellyTau / Build Android APK (push) Failing after 2m14s

Reconcile the ~50 frontend files that consumed the old hand-written types against
the stricter generated bindings (141 -> 0 svelte-check errors):

- Nullable strictness: null-coalesce/guard at consumer sites; widen shared helpers
  (CachedImage.tag, ticksToSeconds, formatDuration, getSessionIcon, session store
  send*/transferToRemote) to accept null.
- Field-name fixes exposed by codegen: userData.played -> isPlayed; remote
  NowPlayingItem uses album (not albumName); ArtistItem id/name.
- player.ts: normalize remote NowPlayingItem -> MediaItem in mergedMedia.
- ArtistItem now serializes camelCase (PascalCase alias retained for Jellyfin
  deserialize); update its serialize test.
- Update downloads.test.ts to the bundled { request } shape; complete the
  sessions.test.ts NowPlayingItem mock.

Frontend: svelte-check 0 errors, vitest 448 passing. Backend: 387 passing.
This commit is contained in:
2026-06-20 20:49:20 +02:00
parent 76c78e2edc
commit 14e9d7e03a
21 changed files with 85 additions and 68 deletions
@@ -15,8 +15,8 @@
const nowPlaying = $derived(session.nowPlayingItem);
const supportsSeek = $derived(playState?.canSeek ?? false);
const supportsNextPrevious = $derived(
session.supportedCommands.includes("NextTrack") &&
session.supportedCommands.includes("PreviousTrack")
(session.supportedCommands ?? []).includes("NextTrack") &&
(session.supportedCommands ?? []).includes("PreviousTrack")
);
async function handlePlayPause() {
@@ -107,8 +107,8 @@
<h4 class="text-base font-medium text-white truncate">{nowPlaying.name}</h4>
{#if nowPlaying.artists && nowPlaying.artists.length > 0}
<p class="text-sm text-gray-400 truncate">{nowPlaying.artists.join(", ")}</p>
{:else if nowPlaying.albumName}
<p class="text-sm text-gray-400 truncate">{nowPlaying.albumName}</p>
{:else if nowPlaying?.album}
<p class="text-sm text-gray-400 truncate">{nowPlaying?.album}</p>
{/if}
</div>
@@ -196,9 +196,9 @@
{#if playState.volumeLevel !== undefined}
<div class="flex items-center gap-3">
<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
{#if playState.isMuted || playState.volumeLevel === 0}
{#if playState.isMuted || (playState.volumeLevel ?? 0) === 0}
<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z" />
{:else if playState.volumeLevel < 50}
{:else if (playState.volumeLevel ?? 0) < 50}
<path d="M7 9v6h4l5 5V4l-5 5H7z" />
{:else}
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z" />