From 2d33d7cb42010bd76efc5e9ea99016297a0760da Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sat, 9 May 2026 09:24:37 -0600 Subject: [PATCH] feat(player): disable metadata actions for ephemeral tracks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Library-bound actions (favorite, add-to-playlist, track info) require a DB row to operate; ephemeral tracks live only in memory and would 404 on use. Surface them disabled rather than removed so the affordance stays discoverable in the chrome — users still see the icon, the tooltip explains why it's inert. Surfaces wired - NowPlayingBar: + and ♥ disabled when active track id >= 2^48. - QueuePanel: now-playing ♥ + per-track Add to Playlist + Track Info in both queue and history menus disabled per-row by id range. - ImmersiveControls + PlayerControlsCompact: ♥ disabled. - ImmersivePlayer: passthrough prop to PlayerControlsCompact. - MiniPlayerFooter / MiniPlayerWindowControls: no metadata actions in these surfaces, nothing to gate. Detection is by id range (>= EPHEMERAL_ID_FLOOR), not by source string. The setQueueForLocalTracks mapping rewrites `source: 'ephemeral'` to `'local'` before the track reaches the queue, so a string-equality check would never fire — id range matches the rest of the ephemeral codebase (gapless carve-out, CUE boundary, seek offset, queue/now- playing wipe). Tooltip on the disabled state uses the new `actions.unavailableForEphemeral` key, translated for en, es, de, fr, pt. cc --- src/lib/components/NowPlayingBar.svelte | 31 ++++++---- src/lib/components/QueuePanel.svelte | 56 ++++++++++++++++--- .../immersive/ImmersiveControls.svelte | 20 +++++-- .../immersive/ImmersivePlayer.svelte | 8 ++- .../immersive/PlayerControlsCompact.svelte | 19 +++++-- src/lib/i18n/locales/de.json | 1 + src/lib/i18n/locales/en.json | 1 + src/lib/i18n/locales/es.json | 1 + src/lib/i18n/locales/fr.json | 1 + src/lib/i18n/locales/pt.json | 1 + src/routes/+page.svelte | 2 + 11 files changed, 112 insertions(+), 29 deletions(-) diff --git a/src/lib/components/NowPlayingBar.svelte b/src/lib/components/NowPlayingBar.svelte index d3a03ee57..e3e033b9d 100644 --- a/src/lib/components/NowPlayingBar.svelte +++ b/src/lib/components/NowPlayingBar.svelte @@ -95,6 +95,14 @@ normalizationGain?: number | null; onToggleNormalization?: () => void; controlsDisabled?: boolean; + /** + * Hide the favorite + add-to-playlist buttons when the active track + * can't be persisted to library structures (today: ephemeral folder + * playback). Playback controls stay enabled — the user can still + * play / pause / skip / seek / volume; only the metadata-bound + * actions disappear. + */ + metadataActionsDisabled?: boolean; explicit?: boolean; qconnectSessionSnapshot?: QconnectSessionSnapshot | null; onToggleQconnectConnection?: () => void | Promise; @@ -150,6 +158,7 @@ normalizationGain = null, onToggleNormalization, controlsDisabled = false, + metadataActionsDisabled = false, explicit = false, qconnectSessionSnapshot = null, onToggleQconnectConnection, @@ -491,23 +500,25 @@ diff --git a/src/lib/components/QueuePanel.svelte b/src/lib/components/QueuePanel.svelte index 4e0262378..6364e8378 100644 --- a/src/lib/components/QueuePanel.svelte +++ b/src/lib/components/QueuePanel.svelte @@ -29,6 +29,18 @@ parental_warning?: boolean; } + // Mirrors EPHEMERAL_ID_FLOOR (1 << 48) in src-tauri/src/ephemeral_library. + // Track ids at or above this threshold come from the in-memory ephemeral + // cache, not the library DB. Favorites / playlists / track-info actions + // require a DB row and don't make sense for ephemeral tracks; the + // helper below gates the corresponding UI off so users don't get + // dead-end clicks. + const EPHEMERAL_ID_FLOOR = 1 << 48; + function isEphemeralQueueTrack(id: string | number | null | undefined): boolean { + if (id == null) return false; + return Number(id) >= EPHEMERAL_ID_FLOOR; + } + interface IndexedQueueTrack { track: QueueTrack; originalIndex: number; @@ -349,11 +361,15 @@ @@ -438,11 +454,23 @@ {/if} - - @@ -497,11 +525,23 @@ {#if openHistoryMenuId === track.id}
- - diff --git a/src/lib/components/immersive/ImmersiveControls.svelte b/src/lib/components/immersive/ImmersiveControls.svelte index 613e6c5ac..41a5bd521 100644 --- a/src/lib/components/immersive/ImmersiveControls.svelte +++ b/src/lib/components/immersive/ImmersiveControls.svelte @@ -30,6 +30,11 @@ onToggleFavorite: () => void; onVolumeChange: (volume: number) => void; volumeLocked?: boolean; + /** Disable the favorite button when the active track lives outside + * the library (today: ephemeral folder playback). Heart stays visible + * but inert so the user understands the action exists; it just isn't + * meaningful for transient tracks. */ + metadataActionsDisabled?: boolean; } let { @@ -52,7 +57,8 @@ onToggleRepeat, onToggleFavorite, onVolumeChange, - volumeLocked = false + volumeLocked = false, + metadataActionsDisabled = false }: Props = $props(); @@ -121,13 +127,17 @@
diff --git a/src/lib/components/immersive/ImmersivePlayer.svelte b/src/lib/components/immersive/ImmersivePlayer.svelte index ac3b9d901..48166d252 100644 --- a/src/lib/components/immersive/ImmersivePlayer.svelte +++ b/src/lib/components/immersive/ImmersivePlayer.svelte @@ -100,6 +100,10 @@ onPlayHistoryTrack?: (trackId: string) => void; // Content flags explicit?: boolean; + /** Disable metadata-bound actions (favorite, etc) when the active + * track lives outside the library. Passed down to ImmersiveControls + * and PlayerControlsCompact. */ + metadataActionsDisabled?: boolean; } let { @@ -150,7 +154,8 @@ onQueueClear, historyTracks = [], onPlayHistoryTrack, - explicit = false + explicit = false, + metadataActionsDisabled = false }: Props = $props(); // UI State @@ -778,6 +783,7 @@ {onToggleShuffle} {onToggleRepeat} {onToggleFavorite} + {metadataActionsDisabled} {isInfinitePlay} {onToggleInfinitePlay} {onVolumeChange} diff --git a/src/lib/components/immersive/PlayerControlsCompact.svelte b/src/lib/components/immersive/PlayerControlsCompact.svelte index 8bd6ffca0..01a9b9c06 100644 --- a/src/lib/components/immersive/PlayerControlsCompact.svelte +++ b/src/lib/components/immersive/PlayerControlsCompact.svelte @@ -48,6 +48,10 @@ onMinimize?: () => void; onToggleFullscreen?: () => void; onToggleMaximize?: () => void; + /** Disable favorite when active track lives outside the library + * (today: ephemeral folder playback). Heart stays visible but + * inert. */ + metadataActionsDisabled?: boolean; } let { @@ -76,7 +80,8 @@ onClose, onMinimize, onToggleFullscreen, - onToggleMaximize + onToggleMaximize, + metadataActionsDisabled = false }: Props = $props(); let progressRef: HTMLDivElement | null = $state(null); @@ -220,11 +225,15 @@