Dropped the border from both menu panels (shadow alone separates them from the page), and gave 'Añadir a una playlist' a leading ListMusic icon so it matches the icon+text layout of 'Añadir a la cola'.
The redesign added overflow-hidden to the outer menu panel to round its corners, but the playlist submenu is absolutely positioned outside that panel's box (left-full/right-full), so it got clipped invisible. Restored the previous approach: rows get their own rounded-lg inside a padded (not overflow-hidden) panel.
Replaced the ad-hoc neutral-900/white-10 Tailwind palette with the same --mf-elevated/--mf-border/--mf-text-2 tokens and rounded-xl/shadow recipe the sidebar's own dropdown already uses, so the menu no longer looks foreign to the rest of the app.
It previously appended to the end of the queue; 'Añadir a la cola' is expected to mean the track plays right after the current one, so it now splices in right after the currently playing track's index.
The right-click menu now has a plain 'Añadir a la cola' item (appends the track to player.tracks via the new player.addToQueue) alongside the existing playlist submenu, and its first-level text is text-base to match the rest of the menu.
The submenu was positioned with a margin gap after the trigger row, so moving the mouse toward it crossed a dead zone with no hovered element and the CSS :hover state dropped, hiding the submenu before the cursor reached it. The gap is now padding on the submenu's own wrapper, which keeps it part of the hoverable box and bridges the trigger to the submenu seamlessly.
Right-clicking a local track in the Musify section now opens the same nested add-to-playlist menu as YouTube results. Also switched the playlist thumbnail corners to rounded-lg.
The right-click menu now shows a single 'Añadir a una playlist' item; hovering it reveals a submenu listing every playlist with a small cover thumbnail next to its name, instead of dumping the whole playlist list into the first-level menu.
- Rename the local results heading from 'Mi música' to 'Musify'.
- Replace the per-card add-to-playlist icon on YouTube results with a right-click context menu listing all playlists.
- Stop the search box from clearing what the user is typing when a debounced navigation resolves after they've kept typing, by decoupling the input's value from the server-driven query prop.
The player never registered Media Session action handlers, so the OS/keyboard next-track and previous-track keys had nothing to call — only play/pause worked via the browser's built-in audio-element heuristic. Now previoustrack/nexttrack are wired to the player, and metadata/playback state are kept in sync for the lock-screen/OS media widget.
The queue/history identity for a song originating on YouTube now always uses its videoId, instead of switching to the local track id once it finishes downloading. Previously that switch made the same song produce two separate entries when merging session and server listening history.
Explore now has Todo/Mi musica/YouTube Music filters (searching both by default), YouTube results share the local grid card style, and cards show the artist instead of the upload date.
Playing a YouTube song now creates a lightweight track row (without queueing a download) and writes listening history; saving to a playlist queues the download via the new DownloadRequested flag.
Rows now span the full page width and show track duration plus
listens count, matching the columns already used elsewhere in the
app. Also trims the trailing bottom padding to match other pages.
GetListeningHistory used AsEnumerable() before DistinctBy/Take, which
forced EF Core to materialize every history row for the user just to
keep the last 15 distinct tracks. Now groups by track and takes the
15 most recently played track ids in SQL, then fetches only those.
Home and explore had an oversized trailing bottom padding compared to
the rest of the app; brought both down to match. Also matches "Mis
listas" grid/limit on home to "Novedades", and sizes the library page
header the same as the playlists page header.
Create and edit playlist modals now upload/preview a cover image
(reusing the presigned-upload flow already used for tracks) and share
identical layout and coloring. Adds a cover proxy route and wires
PlaylistArt to render a playlist's real cover before falling back to
the track mosaic/gradient.
Also reworks the playlist detail page: full-width borderless "Añadir
de tu biblioteca" table with listens count and click-to-play, drops
the empty-playlist placeholder message, and restyles the playlists
grid to match the rest of the app.
TrackApplicationResponse.FromEntity counted track.ListeningHistories,
but none of the queries that built a response ever Included it, so
the collection was always empty and listensCount was always 0. Each
query now projects the count as a correlated subquery before mapping
to the response.
Adds GET /playlists/{id}/cover (mirrors the track cover endpoint) so
uploaded playlist pictures can actually be served. Also drops the
NotEmpty rule on playlist description, which rejected the blank
description the frontend treats as optional; CreatePlayList now falls
back to the domain's default text instead.