Skip to content

feat: playlists, favourites, user profiles, people row, and recently played#83

Merged
JoePittsy merged 11 commits into
mainfrom
develop
May 11, 2026
Merged

feat: playlists, favourites, user profiles, people row, and recently played#83
JoePittsy merged 11 commits into
mainfrom
develop

Conversation

@JoePittsy
Copy link
Copy Markdown
Member

Overview

This release brings the social layer of TrueTunes to life — playlists you can build and share, a personal Favourites playlist, full user profile pages, a People row on the home screen, and the recently played feature that kicked the branch off.


Playlists

Full playlist system backed by Azure Cosmos DB with optimistic concurrency control on all mutations.

What's new:

  • Create private or public playlists from the home page, profile page, or track context menu
  • Add tracks to any playlist via right-click → "Add to playlist" submenu
  • Drag to reorder tracks within a playlist (owner and members)
  • Remove tracks (owner only)
  • Rename a playlist by clicking the title (owner only, Escape correctly cancels without saving)
  • Delete a playlist with a confirmation step (owner only, Favourites protected server-side)
  • Upload a custom cover image (owner only, stored in Azure Blob with 20-year SAS URL)
  • Join / leave public playlists
  • Playlist detail page (/playlist/:id) with full track table, member list, and metadata
  • Playlist cards on home and profile pages sort Favourites first, then by most recently updated
  • Track count on cards updates immediately after adding or removing tracks

Server functions added: playlist-create, playlist-get, playlist-list, playlist-add-track, playlist-remove-track, playlist-reorder, playlist-update, playlist-delete, playlist-join, playlist-upload-image

Infrastructure: new playlists Cosmos container and playlist-images blob container provisioned via ARM template.


Favourites

  • Each user gets a Favourites playlist auto-created on first login (favourites-ensure Azure Function)
  • Heart button in the player bar toggles the currently playing track in/out of Favourites with optimistic UI update and rollback on failure
  • "Add to Favourites" option in the track context menu
  • Favourites playlist is pinned to the top of all playlist lists and cannot be deleted or made public
  • useFavourite hook with isFavourited state derived from the cached playlist, full add/remove with rollback

User Profiles

  • Profile pages at /profile/:userName showing top tracks, top artists, top albums, playlists, and Queuedle rank
  • Click any user name anywhere in the app (track rows, playlist "added by", leaderboard) to go to their profile
  • Profile picture upload (owner only) stored in Azure Blob
  • Own profile shows a Sign Out link and all playlists (owned + joined); other profiles show only public playlists
  • useUserProfile and useUserStats hooks; profile-get and profile-upload-image Azure Functions

People Row (Home Page)

  • New "People" section on the home page showing all users who have queued tracks
  • Circular profile picture avatars (gradient initial fallback), sorted by most recently queued
  • Current user excluded server-side
  • Click through to any user's profile page
  • users-list Azure Function queries the events container with GROUP BY to get last-queued timestamp per user, joins with profiles for images
  • Skeleton loaders while fetching; hover scale animation with correct overflow handling

Recently Played

  • "Recently Played — Last 7 Days" section on the home page with artist and album grids
  • User picker dropdown (gated behind completing today's Queuedle to prevent spoilers)
  • recently-played Azure Function aggregates Cosmos events per user
  • Drag artists/albums from recently played into the queue

Context Menu

  • Right-click any track anywhere in the app to get: Play next, Add to queue, Add to Favourites, Add to playlist (submenu), View [user]'s profile
  • ContextMenuProvider wraps the app shell; useTrackContextMenu hook exposes showTrackMenu to any component
  • CreatePlaylistDialog inline in the context menu flow for creating a new playlist and immediately adding the track to it

Toast Notifications

  • ToastProvider / useToast for non-blocking error feedback on all mutation failures (add track, remove track, rename, delete, join, reorder, upload image)

Code quality & bug fixes

  • IPC error propagation fix: all 7 mutation IPC handlers now use a playlistFetch helper that throws on non-2xx responses, so renderer catch blocks and optimistic rollbacks actually fire on server errors (previously handlers returned { error: '...' } silently)
  • Escape cancels rename correctly: fixed blur-after-Escape race condition in PlaylistPanel rename input using a cancelRename ref
  • Track count invalidation: all add/remove callsites now invalidate the playlist list query so card track counts update immediately
  • Shared utilities: getPlaylistColor, createDragGhost, PlaylistCard component, and @keyframes skelPulse extracted to canonical locations, eliminating duplication across HomePanel, ProfilePanel, and PlaylistPanel
  • useEnsureFavourites retry logic now works correctly (handler throws instead of returning error object)
  • handleJoin in PlaylistPanel had no error handling — fixed with contextual toast
  • useFavourite toggle invalidates the playlist list query after success so Favourites track count stays accurate

Test coverage

New test files:

  • usePlaylists.test.tsuseFavourite add/remove happy paths, rollback paths, useEnsureFavourites stale time and retry config
  • PlaylistPanel.test.tsxhandleRemoveTrack optimistic update and rollback
  • CreatePlaylistDialog.test.tsx — create without track, create with pending track, server error inline display
  • TopNav.test.tsx — updated to match new profile chip navigation behaviour

722 tests passing, full typecheck clean.

Joe Pitts and others added 10 commits May 8, 2026 10:33
Replaces the broken api.content.history (Sonos, album-only) with a new
Azure Function that queries the Cosmos events container for the current
user's queue history from the past 7 days.

- New Azure Function /api/recently-played — deduplicates in-memory,
  returns top 20 tracks / 10 artists / 10 albums sorted by most-recent
- IPC handler history:recent + preload bridge fetchRecentlyPlayed
- useRecentlyPlayed hook — fetches by displayName, converts to SonosItem
  so CardRow/useOpenItem can render and navigate them
- HomePanel: three conditional rows (Artists, Albums, Tracks) replacing
  the single dead Recently Played row

Needs: cd server && npm run deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getName() prefers item.name over item.title — toAlbumItem was setting
name: a.artist which caused the album row to display artist names.
Also wire artist field on both album and track items for subtitle display.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace CardRow with a bespoke side-by-side layout: album list (left)
  and circular artist grid (right), max 9 artists capped at 3×N columns
- Artist images fetched in parallel via useQueries; circular cards with
  fade-in glow on hover; artist names shown below circles
- Album rows: draggable (application/sonos-item-list), add-to-queue
  button visible on hover, grab cursor
- When no albums present, artist grid spans full width with up to 5 cols
- User picker inline in section title; locks with tooltip when Quedle
  not yet completed; "Nothing queued in the last 7 days" empty state
- Loading skeleton mirrors the real layout with staggered pulse animation
- Server: returns availableUsers (last 30 days) alongside recent data
- Artists capped at 9 (3×3 max); CardRow artist cards now circular with
  no add-to-queue button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /profile/:userName route with ProfilePanel showing recently played,
  all-time top tracks/artists/albums, and Queuedle tier + stat grid
- Add useUserStats hook wrapping fetchStats alltime per user
- Add TopNav profile button (Contact icon) for current user
- Make attribution usernames in queue rows clickable to profile
- Make all leaderboard usernames clickable to profile
- Make all Queuedle panel leaderboard names clickable to profile
- Add profile icon button to HomePanel user picker dropdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Implement comprehensive playlist management with dedicated UI panel.
- Allow users to create, view, add tracks to, and manage their own playlists.
- Introduce a global context menu for tracks to play next, queue, or add to playlist.
- Integrate track context menu across album, artist, queue, and search track lists.
- Display user playlists on home and profile panels with a create playlist option.
- Add backend APIs and storage for playlist data and cover images.
- Extract getPlaylistColor, createDragGhost, PlaylistCard into shared modules
- Move @Keyframes skelPulse to global.css (was duplicated in 3 modules)
- Profile chip in TopNav navigates directly to profile page; sign-in shows name-entry popover
- Sign out button moved to profile page, inline with play count (X plays | Sign out)
- Playlists section moved above Recently Played on profile page with + New button
- Playlist cards now show cover art via imageUrl from list endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… grid

- Profile picture upload: Azure Blob + Cosmos profiles container, camera emoji overlay, shown in TopNav chip
- Top tracks: 2-column 5×5 grid with multi-select, drag-to-queue, clickable artist/album links
- Queuedle rank promoted to header hero block with tier icon
- Stats fetch count raised to 25 (server accepts ?count param)
- Top artists/albums displayed at 110px for compact fit
- Playlists section now wraps as CSS grid instead of horizontal scroll

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 72.04% 1850 / 2568
🔵 Statements 68.99% 2123 / 3077
🔵 Functions 62.95% 588 / 934
🔵 Branches 63.19% 1990 / 3149
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
renderer/src/App.tsx 68.94% 54.69% 43.93% 73.21% 65, 66, 72, 85-86, 97, 116-117, 123, 128, 136, 144, 147, 152, 159-163, 210, 231-247, 265-267, 272, 356-368, 393-395, 403, 434-475, 507-515
renderer/src/components/CardRow.tsx 100% 94.11% 100% 100%
renderer/src/components/HomePanel.tsx 58.53% 58.78% 38.46% 62.74% 34-72, 185-189, 204-216, 259-332, 383, 409-439
renderer/src/components/LeaderboardPanel.tsx 86.15% 84.8% 81.48% 88.7% 14, 39-47, 135, 181, 205-206, 348-358, 383
renderer/src/components/PlayerBar.tsx 83.33% 61.03% 80.76% 86.36% 47, 77, 78, 96-101, 128, 212, 322
renderer/src/components/PlaylistPanel.tsx 28.02% 38.88% 19.44% 32.82% 19-25, 88-95, 123-125, 129-132, 136-153, 158-159, 162, 169-201, 206, 222-252, 264-378, 401, 403-425
renderer/src/components/ProfilePanel.tsx 0% 0% 0% 0% 19-295
renderer/src/components/TopNav.tsx 78.04% 72.15% 66.66% 80.55% 76, 86-91, 137-140, 232, 233-264, 274-300
renderer/src/components/album/AlbumPanel.tsx 83.14% 85.59% 77.77% 88.88% 47, 83-88, 99, 131, 188
renderer/src/components/album/AlbumTrackRow.tsx 85% 80% 70% 84.21% 50, 79, 101
renderer/src/components/artist/ArtistHero.tsx 81.63% 78.94% 76.92% 87.17% 64-68
renderer/src/components/artist/HeroTrackRow.tsx 80% 82.35% 60% 77.77% 46-47
renderer/src/components/artist/TopSongRow.tsx 70% 50% 40% 66.66% 41-43
renderer/src/components/common/ContextMenu.tsx 33.62% 31.16% 27.27% 36.63% 41-104, 126, 130-136, 142-202, 213-267, 297, 335, 335-347
renderer/src/components/common/MediaCard.tsx 100% 70% 100% 100%
renderer/src/components/common/MediaRow.tsx 100% 100% 100% 100%
renderer/src/components/common/PlaylistCard.tsx 0% 0% 0% 0% 12-28
renderer/src/components/common/Toast.tsx 46.66% 0% 22.22% 63.63% 30-32, 41-43
renderer/src/components/queue/DraggableQueueRow.tsx 37.93% 75% 27.27% 44% 27-31, 71-73, 77-119
renderer/src/components/queue/QueueSidebar.tsx 52.76% 59.63% 63.04% 55.92% 88-96, 101, 111, 116, 123, 130, 136, 142, 151-174, 206-252, 269, 310-315, 351
renderer/src/components/queuedle/QueuedlePanel.tsx 84.61% 80.08% 83.67% 86.41% 87, 120, 144-151, 157, 164, 203-206, 285, 317, 383, 387, 420, 422-424, 434, 455-458, 463, 475-477
renderer/src/components/search/SearchResults.tsx 83.11% 65.11% 66.66% 81.13% 18-21, 42-57, 82, 113, 142, 161, 163, 184
renderer/src/hooks/useNowPlaying.ts 88.23% 68.18% 50% 88.23% 38-39
renderer/src/hooks/usePlaylists.ts 73.58% 63.63% 62.5% 76.59% 21-22, 28, 34, 42, 51-52, 79, 129-132
renderer/src/hooks/useRecentlyPlayed.ts 0% 0% 0% 0% 7-84
renderer/src/hooks/useUserProfile.ts 0% 0% 0% 0% 4-18
renderer/src/hooks/useUserStats.ts 0% 0% 0% 0% 5-43
renderer/src/hooks/useUsers.ts 100% 100% 100% 100%
renderer/src/lib/dragHelpers.ts 100% 100% 100% 100%
renderer/src/lib/playlistColor.ts 100% 100% 100% 100%
renderer/src/types/globals.d.ts 100% 100% 100% 100%
Generated in workflow #128 for commit 65081db by the Vitest Coverage Report Action

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds the “social layer” to TrueTunes by introducing a Cosmos-backed playlist system (including auto-created Favourites), user profiles (with image upload), a People row, and a “Recently Played (last 7 days)” home section, wiring the new APIs through Electron IPC and exposing them in the renderer with new panels/hooks, context menus, and toast-based error feedback.

Changes:

  • Added new Azure Functions + Cosmos/Blob infra to support playlists, favourites, profiles, people list, and recently played aggregation.
  • Updated Electron preload + main IPC handlers to expose new playlist/profile/history APIs to the renderer (including stats count support).
  • Added new renderer UI (Profile/Playlist panels, People row, context menu, toasts), shared utilities, and accompanying tests/styles.

Minimal code changes to address review findings (recommended):

  • Fix Cosmos “not found” handling where item.read() currently won’t reach if (!resource) branches (notably favourites ensure and playlist get/delete).
    Files: server/src/functions/favourites-ensure.ts, server/src/functions/playlist-get.ts, server/src/functions/playlist-delete.ts
  • Align “owner-only remove” behavior with the PR description by enforcing owner-only removal server-side.
    Files: server/src/functions/playlist-remove-track.ts
  • Make reorder mutation update caches consistently (invalidate playlist + playlist list queries, or apply server response including updatedAt).
    Files: renderer/src/components/PlaylistPanel.tsx, (optionally) renderer/src/hooks/usePlaylists.ts
  • Validate/clamp days in recently played and address the users-list N+1 profile reads if this endpoint is expected to scale.
    Files: server/src/functions/recently-played.ts, server/src/functions/users-list.ts
  • Fix minor tooltip typo (“Quedle” → “Queuedle”).
    Files: renderer/src/components/HomePanel.tsx

Tests to add/update (targeted):

  • Add a PlaylistPanel test covering successful reorder (verifying expected query invalidations or updatedAt update behavior).
    Files: renderer/src/components/__tests__/PlaylistPanel.test.tsx

Tradeoffs (high-level):

  • Batch-fetching profiles in users-list reduces RU/latency vs. simple per-user reads (current approach is simpler but scales poorly).
  • Cache invalidation after reorder is slightly more network traffic but keeps “sorted by recently updated” consistent and avoids UI drift.

Reviewed changes

Copilot reviewed 73 out of 74 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/preload.ts Exposes new IPC APIs (playlists, profiles, recently played, stats count).
src/main.ts Adds IPC handlers for playlist/profile/history endpoints + stats count wiring.
server/src/lib/withOCC.ts Introduces optimistic concurrency read-modify-write helper for Cosmos mutations.
server/src/lib/getContainer.ts Adds cached Cosmos container accessor for playlists.
server/src/functions/users-list.ts Adds People-row backing API (queued users + profile images).
server/src/functions/stats.ts Adds count query param for variable-length stats results.
server/src/functions/recently-played.ts Adds recently played aggregation + available users for picker.
server/src/functions/profile-upload-image.ts Adds profile image upload to Blob + profile doc upsert.
server/src/functions/profile-get.ts Adds profile fetch endpoint.
server/src/functions/playlist-upload-image.ts Adds playlist cover upload to Blob + Cosmos patch.
server/src/functions/playlist-update.ts Adds playlist rename/publicity update with OCC.
server/src/functions/playlist-reorder.ts Adds track reorder mutation with OCC.
server/src/functions/playlist-remove-track.ts Adds track removal mutation with OCC.
server/src/functions/playlist-list.ts Adds playlist listing (owned/joined) endpoint for UI lists.
server/src/functions/playlist-join.ts Adds join/leave mutation for public playlists with OCC.
server/src/functions/playlist-get.ts Adds playlist detail fetch endpoint.
server/src/functions/playlist-delete.ts Adds playlist deletion endpoint with favourites protection.
server/src/functions/playlist-create.ts Adds playlist creation endpoint.
server/src/functions/playlist-add-track.ts Adds track add mutation with OCC + addedBy/addedAt stamping.
server/src/functions/favourites-ensure.ts Adds first-login favourites auto-creation endpoint.
server/azuredeploy.json Provisions new Cosmos containers + playlist-images blob container.
renderer/src/types/globals.d.ts Adds TS types for playlists/profiles/recently-played + preload API surface.
renderer/src/test/setup.ts Extends window.sonos mocks for new preload APIs.
renderer/src/styles/TopNav.module.css Adds profile chip/popover styles.
renderer/src/styles/Toast.module.css Adds toast notification styles.
renderer/src/styles/QueueSidebar.module.css Adds clickable attribution username styling.
renderer/src/styles/Queuedle.module.css Adds clickable username button styling in Queuedle leaderboard.
renderer/src/styles/ProfilePanel.module.css New profile page styling.
renderer/src/styles/PlaylistPanel.module.css New playlist detail page styling (drag reorder, members, actions).
renderer/src/styles/PlayerBar.module.css Adds favourited heart button styling.
renderer/src/styles/LeaderboardPanel.module.css Adds clickable username button styling.
renderer/src/styles/HomePanel.module.css Adds People row, playlists row, and recently played layouts/styles.
renderer/src/styles/global.css Adds shared @keyframes skelPulse.
renderer/src/styles/ContextMenu.module.css Adds context menu + submenu styling.
renderer/src/styles/CardRow.module.css Adjusts CardRow padding/margins for updated layouts.
renderer/src/lib/playlistColor.ts Adds deterministic playlist gradient helper.
renderer/src/lib/dragHelpers.ts Extracts reusable drag ghost helper.
renderer/src/hooks/useUserStats.ts Adds user stats hook (count-aware) for profile pages.
renderer/src/hooks/useUsers.ts Adds users hook for People row.
renderer/src/hooks/useUserProfile.ts Adds user profile hook + invalidation helper.
renderer/src/hooks/useRecentlyPlayed.ts Adds recently played hook + artist image enrichment queries.
renderer/src/hooks/usePlaylists.ts Adds playlists/favourites hooks incl. optimistic favourite toggle + invalidations.
renderer/src/hooks/useNowPlaying.ts Exposes current track identity needed for favouriting.
renderer/src/hooks/tests/usePlaylists.test.ts Adds tests for favourites toggle + ensure favourites query config.
renderer/src/components/TopNav.tsx Replaces name popover with profile chip + sign-in flow and profile nav.
renderer/src/components/search/SearchResults.tsx Adds context menu integration + shared drag ghost helper.
renderer/src/components/queuedle/QueuedlePanel.tsx Makes leaderboard usernames link to profiles.
renderer/src/components/queue/QueueSidebar.tsx Uses shared drag ghost helper.
renderer/src/components/queue/DraggableQueueRow.tsx Adds track context menu + clickable attribution user → profile.
renderer/src/components/queue/tests/DraggableQueueRow.test.tsx Updates attribution test for new clickable username element.
renderer/src/components/ProfilePanel.tsx Adds new profile page UI (stats, playlists, avatar upload).
renderer/src/components/PlaylistPanel.tsx Adds new playlist detail UI (join/leave, rename, delete, upload, reorder).
renderer/src/components/PlayerBar.tsx Adds Favourites heart toggle (optimistic) tied to now playing track.
renderer/src/components/LeaderboardPanel.tsx Makes usernames link to profiles while preserving drill-down behavior.
renderer/src/components/HomePanel.tsx Adds People row, playlists row, and “Recently Played — Last 7 Days” section with picker gating.
renderer/src/components/common/Toast.tsx Adds ToastProvider/useToast for mutation failure feedback.
renderer/src/components/common/PlaylistCard.tsx Adds shared playlist card component for home/profile lists.
renderer/src/components/common/PlaylistCard.module.css Adds playlist card styling.
renderer/src/components/common/MediaRow.tsx Adds optional onContextMenu plumbing to row component.
renderer/src/components/common/MediaCard.tsx Adds circular option (used for artists).
renderer/src/components/common/MediaCard.module.css Adds circular art styling.
renderer/src/components/common/ContextMenu.tsx Adds global track context menu + playlist submenu + create playlist dialog.
renderer/src/components/common/tests/CreatePlaylistDialog.test.tsx Adds tests for create playlist dialog (with/without pending track, error).
renderer/src/components/CardRow.tsx Adds artist circular cards + configurable card size.
renderer/src/components/artist/TopSongRow.tsx Adds track context menu on artist top song rows.
renderer/src/components/artist/HeroTrackRow.tsx Adds track context menu on artist hero rows.
renderer/src/components/artist/ArtistHero.tsx Uses shared drag ghost helper.
renderer/src/components/album/AlbumTrackRow.tsx Adds track context menu on album track rows.
renderer/src/components/album/AlbumPanel.tsx Uses shared drag ghost helper.
renderer/src/components/tests/TopNav.test.tsx Updates tests for profile chip / sign-in popover behavior.
renderer/src/components/tests/PlaylistPanel.test.tsx Adds tests for optimistic remove track + rollback in playlist panel.
renderer/src/components/tests/LeaderboardPanel.test.tsx Updates drill-down tests to account for username button navigation.
renderer/src/components/tests/HomePanel.test.tsx Updates home tests for recently played hook integration.
renderer/src/App.tsx Adds routes for profile/playlist pages + wraps app in Toast/ContextMenu providers; ensures favourites on login; updates Home/PlayerBar props.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +41
export async function profileUploadImageHandler(
request: HttpRequest,
context: InvocationContext,
): Promise<HttpResponseInit> {
const userName = request.params['userName'];
if (!userName) return { status: 400, jsonBody: { error: 'userName required' } };

const storageConn = process.env['STORAGE_CONNECTION_STRING'];
const cosmosConn = process.env['COSMOS_CONNECTION_STRING'];
const dbName = process.env['COSMOS_DATABASE'] ?? 'truetunes';

if (!storageConn || !cosmosConn) {
return { status: 500, jsonBody: { error: 'Storage or Cosmos not configured' } };
}
Comment on lines +31 to +34
(doc) => {
if (doc.owner !== userName && !doc.members.includes(userName)) {
throw Object.assign(new Error('Not a member of this playlist'), { statusCode: 403 });
}
Comment on lines +15 to +18
const { name, isPublic = false, owner } = body;
if (!name || !owner) {
return { status: 400, jsonBody: { error: 'name and owner required' } };
}
Comment on lines +46 to +50
// Verify ownership before uploading to storage
const cosmos = new CosmosClient(cosmosConn);
const { resource: playlistDoc } = await cosmos.database(dbName).container('playlists').item(id, id).read();
if (!playlistDoc) return { status: 404, jsonBody: { error: 'Playlist not found' } };
if (playlistDoc.owner !== userName) return { status: 403, jsonBody: { error: 'Only the owner can change the playlist image' } };
Comment thread server/src/functions/recently-played.ts Outdated
Comment on lines +64 to +66
const days = parseInt(request.query.get('days') ?? '7', 10);
const startMs = Date.now() - days * 24 * 60 * 60 * 1000;

Comment on lines +33 to +36
if (owner) {
query = {
query: 'SELECT c.id, c.name, c.owner, c.isPublic, c.isFavourites, c.members, c.imageUrl, c.createdAt, c.updatedAt, ARRAY_LENGTH(c.tracks) AS trackCount FROM c WHERE c.owner = @owner ORDER BY c.updatedAt DESC',
parameters: [{ name: '@owner', value: owner }],
Comment on lines +13 to +20
try {
const container = getPlaylistContainer();

const { resource } = await container.item(id, id).read();

if (!resource) {
return { status: 404, jsonBody: { error: 'Playlist not found' } };
}
Comment on lines +18 to +22
// Return existing
const { resource } = await container.item(id, id).read();
if (resource) {
return { jsonBody: resource, headers: { 'Access-Control-Allow-Origin': '*' } };
}
Comment on lines +28 to +31
const { resource } = await container.item(id, id).read();
if (!resource) {
return { status: 404, jsonBody: { error: 'Playlist not found' } };
}
Comment on lines +14 to +20
const container = getPlaylistContainer();

const { resource } = await container.item(id, id).read();

if (!resource) {
return { status: 404, jsonBody: { error: 'Playlist not found' } };
}
- playlist-create: add name trim/empty/max-length validation consistent with playlist-update
- playlist-remove-track: restrict to owner only (was allowing any member)
- recently-played: clamp and validate days param (1–30, return 400 on invalid)
- playlist-upload-image: move Cosmos ownership check inside try/catch
- PlaylistPanel: invalidate playlist detail and list queries after successful reorder
- HomePanel: fix typo Quedle → Queuedle in picker tooltip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@JoePittsy JoePittsy merged commit d879e07 into main May 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants