player bar: the album line survives library plays, and lands with the title

Two halves of one defect family. The meta line preferred the queue item's
artist/album fields as a PAIR whenever either existed — but a library play
(playAlbum) writes only `artist`, with the record's name living inside
`sub` ("artist — album"), so `album: null` was taken at face value and
every library album lost its name from the bar (and the morning comment
claiming local rows carry neither field was simply wrong). Each half now
falls back to the `sub` split on its own, here and in the rail's byline —
which also restores the album for continuation items whose `sub` is a
bare artist.

And the line no longer trickles in: the buffering branch used to render
the artist alone, so the album flashed in a beat after the title when
playback started. The status gate is replaced by a REAL distinction — a
resolver-phase track has no `path` (playQueueIndex always writes one), so
"finding best source…" still shows as a bare label, and the moment a real
queue item owns the transport the full clickable line renders, buffering
or not. Measured: the first frame carrying the new title already carries
artist · album; no artist-only frame exists.
This commit is contained in:
KeelrLeek committed 2026-08-03 17:18:25 -05:00
1 parent e2cfed081c
commit 8cd56a433e
2 files changed
+30 -14

No files matched your search

+9 -1
View File
@@ -104,11 +104,19 @@
return sub.includes(" — ") ? sub.split(" — ")[0].trim() : sub; return sub.includes(" — ") ? sub.split(" — ")[0].trim() : sub;
}); });
// Artist / album split for the separate, clickable identity lines. // Artist / album split for the separate, clickable identity lines.
// Per field, like the player bar's: the item's own artist/album win when
// present (the continuation fill writes them beside a bare-artist `sub`),
// and each half falls back to the `sub` split on its own — a library play
// carries only `artist`, with the record's name living inside `sub`.
const metaParts = $derived.by(() => { const metaParts = $derived.by(() => {
const sub = player.track?.sub ?? ""; const sub = player.track?.sub ?? "";
return sub.includes(" — ") const split = sub.includes(" — ")
? sub.split(" — ").map((s) => s.trim()) ? sub.split(" — ").map((s) => s.trim())
: [sub.trim(), ""]; : [sub.trim(), ""];
return [
currentItem?.artist ?? split[0] ?? "",
currentItem?.album ?? split[1] ?? "",
];
}); });
const LOSSLESS = new Set(["FLAC", "WAV", "ALAC", "AIFF", "APE"]); const LOSSLESS = new Set(["FLAC", "WAV", "ALAC", "AIFF", "APE"]);
+21 -13
View File
@@ -43,21 +43,23 @@
const canScratch = $derived(!currentItem?.stream); const canScratch = $derived(!currentItem?.stream);
/** Artist and album for the separate, clickable meta lines. /** Artist and album for the separate, clickable meta lines.
* *
* The queue item's OWN `artist` / `album` fields come first, and splitting * Per FIELD, not all-or-nothing. The queue paths disagree about what an
* `sub` on the em dash is only the fallback. `sub` is a display convenience * item carries: the album-continuation fill writes artist AND album
* that not every path fills the same way: the album-continuation fill * fields beside a bare-artist `sub`; a library play (`playAlbum`) writes
* (`maybeExtendAlbumQueue`) writes a bare artist into it while carrying the * only `artist`, with the record's name living inside `sub`
* album right alongside, so a record played straight through lost its album * ("artist — album"); old restored items may carry neither. Preferring
* name from the second track on — which is exactly what it looked like. * the item's fields as a PAIR whenever either existed took `album: null`
* Local library rows carry neither field, and those still fall back. */ * at face value and erased the record from every library play — each
* half now falls back to the `sub` split on its own. */
const metaParts = $derived.by(() => { const metaParts = $derived.by(() => {
if (currentItem?.artist || currentItem?.album) {
return [currentItem.artist ?? "", currentItem.album ?? ""];
}
const sub = player.track?.sub ?? ""; const sub = player.track?.sub ?? "";
return sub.includes(" — ") const split = sub.includes(" — ")
? sub.split(" — ").map((s) => s.trim()) ? sub.split(" — ").map((s) => s.trim())
: [sub.trim(), ""]; : [sub.trim(), ""];
return [
currentItem?.artist ?? split[0] ?? "",
currentItem?.album ?? split[1] ?? "",
];
}); });
const volumeIcon = $derived( const volumeIcon = $derived(
player.muted || player.volume === 0 player.muted || player.volume === 0
@@ -791,8 +793,14 @@
<!-- Artist and album on ONE line, which is what a 62px bar can pay <!-- Artist and album on ONE line, which is what a 62px bar can pay
for. The `.who` idiom from the album page: a 50%-opacity middot for. The `.who` idiom from the album page: a 50%-opacity middot
between them, and no underline until hover. --> between them, and no underline until hover. -->
{#if player.status === "buffering"} <!-- A resolver-phase track has no `path` (playQueueIndex always
<span class="who"><span class="sub">{metaParts[0] || "buffering…"}</span></span> writes one), and its `sub` IS the status line — show it bare.
The moment a real queue item owns the transport, the full
clickable line renders, buffering or not: gating it on
status=playing made the album flash in a beat after the
title and artist. -->
{#if player.track && !player.track.path}
<span class="who"><span class="sub">{player.track.sub || "buffering…"}</span></span>
{:else if player.track && (metaParts[0] || metaParts[1])} {:else if player.track && (metaParts[0] || metaParts[1])}
<span class="who"> <span class="who">
{#if metaParts[0]} {#if metaParts[0]}