Skip to content

feat: create playlists from the sidebar - #370

Merged
sozercan merged 5 commits into
sozercan:mainfrom
tsibog:pr/create-playlist-sidebar
Jul 15, 2026
Merged

feat: create playlists from the sidebar#370
sozercan merged 5 commits into
sozercan:mainfrom
tsibog:pr/create-playlist-sidebar

Conversation

@tsibog

@tsibog tsibog commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Description

Playlists can now be created from the sidebar: a "+" button reveals on hover over the Playlists section header, opens the existing create-playlist dialog, and on success pins the new playlist to the sidebar and navigates straight to it. Previously the only way to create a playlist was buried in a song's "Add to Playlist" context menu, so an empty account had no visible entry point at all. While creation is in flight the "+" is replaced by a small inline spinner.

The second commit fixes a bug this feature made prominent: deleting a playlist left its sidebar pin behind forever (deletePlaylist never touched SidebarPinnedItemsManager), and the deleted playlist's page stayed open when it had been opened from the sidebar (dismiss() is a no-op there). Deletion is now optimistic — the pin and library entry disappear and navigation happens immediately on confirm; on API failure the pin is restored at its original position, the library entry comes back, and the existing error alert is shown.

AI Prompt (Optional)

🤖 AI Prompt Used
I'd like a little PLUS (+) next to the Playlists label in the left sidebar,
because how do I create a playlist in this app otherwise. Show it only on
hover, with a spinner while the playlist is created. Also deleting a playlist
does nothing visible for seconds and the playlist is stuck in the sidebar —
investigate and lean into optimistic updates.

AI Tool: Claude Code

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)
  • 🐛 Bug fix (non-breaking change that fixes an issue)

Related Issues

None found.

Changes Made

  • Sidebar — custom Playlists section header with a hover-revealed "+" (spinner while creating); the section now renders even with no pinned items so the entry point always exists
  • SongActionsHelper.presentCreatePlaylistDialog — the NSAlert create dialog previously inlined in AddToPlaylistContextMenu is extracted so both call sites share one create/broadcast/reconcile path (PlaylistCreationRequest + Result completion)
  • LibraryMutationActions.deletePlaylist — optimistic: unpins from the sidebar and broadcasts the library removal before the API call, rolls both back on failure; reconciliation unchanged
  • SidebarPinnedItemsManager.insert(_:at:) — position-preserving restore for the rollback path
  • onPlaylistDeleted @Entry environment key — the pinned-playlist detail page navigates home after deletion instead of calling dismiss() (a no-op outside a navigation stack); nav-stack contexts keep the dismiss() fallback

Testing

  • Unit tests pass (swift test --skip KasetUITests)
  • Manual testing performed — created playlists from the sidebar (spinner, pin, navigation), deleted pinned and unpinned playlists, verified instant UI response and Library consistency after reload
  • UI tested on macOS 26+

Checklist

  • My code follows the project's style guidelines
  • I have run swiftlint --strict && swiftformat .
  • I have added tests that prove my fix/feature works (deletion unpins from sidebar; failed deletion restores pin + library entry)
  • New and existing unit tests pass locally
  • I have updated documentation if needed
  • I have checked for any performance implications
  • My changes generate no new warnings

Screenshots

kaset-pr370-demo.mp4

Additional Notes

Includes the same test: deflake commit as #363/#364 (the NotificationServiceTests fixed-wait flake lives on main and hits fresh branches); it disappears on rebase once any of them merges.

Agent Transcript

Redacted agent session transcript (scoped to this PR)
source: local Claude Code session, trimmed to this PR's scope
omitted: raw tool outputs, system prompts, local paths, unrelated session turns

[user]
i'd like a little PLUS (+) next to the playlists label in the left sidebar,
because how do I create a playlist in this app otherwise.

[assistant]
Scouted first: createPlaylist already exists in the API layer (posts
playlist/create) and an NSAlert create dialog already exists inside the song
context menu — the feature was just undiscoverable. The sidebar Playlists
section is driven by SidebarPinnedItemsManager, which does NOT observe
LibraryMutationBroadcaster, so a created playlist must be explicitly pinned.
Implemented: custom section header with a "+" button, extracted the dialog
into a shared SongActionsHelper.presentCreatePlaylistDialog so both call
sites share one create/broadcast path; on success the playlist is pinned and
opened. Section now renders even with zero pinned items.

[user]
Show the + only when hovering the Playlists header; align its right padding
with the header's left margin.

[user]
Delete playlist looks broken — I confirm the dialog and nothing happens.
...actually it IS deleted (gone from Library), but it's stuck in the sidebar.

[assistant]
Root cause: LibraryMutationActions.deletePlaylist broadcasts the removal to
the Library view models only; nothing ever removes the sidebar pin (pre-
existing bug — any pinned playlist deleted becomes a ghost pin). Also the
detail page stays open because dismiss() is a no-op when the page was opened
from a sidebar pin rather than a navigation stack. Fixed: deletePlaylist now
unpins; a new onPlaylistDeleted @Entry environment value lets the pinned
context navigate home (nav-stack contexts keep the dismiss() fallback).

[user]
Show a spinner (check the app for precedent) while the playlist is created.
Deletion takes seconds before anything happens — lean into optimistic updates.

[assistant]
Precedent check: inline ProgressView().controlSize(.small) is the app's busy
idiom (CommandBar, artist page); no skeleton pattern exists — spinner replaces
the "+" while creating. Deletion made optimistic: pin + library entry removed
and navigation fires immediately on confirm; API call runs after; on failure
the pin is restored at its original index (new SidebarPinnedItemsManager
.insert(_:at:)), the library entry is restored, and the existing error alert
shows.


[assistant]
Verification: swiftlint --strict clean, swiftformat clean, swift build OK,
swift test --skip KasetUITests → 1776/1776 pass incl. 2 new
LibraryMutationActionsTests (deletion unpins from sidebar; failed deletion
restores pin + library entry). Hover animation uses the shared
AppAnimation.quick constant (review-pass finding). Manual test: create from
sidebar (spinner → pin → navigate), delete (instant unpin + redirect home).

[user]
ok, yep, that is good.

@tsibog
tsibog force-pushed the pr/create-playlist-sidebar branch from f80dff6 to f75d809 Compare July 11, 2026 09:19
…sidebar

# Conflicts:
#	Tests/KasetTests/LibraryMutationActionsTests.swift
@tsibog
tsibog force-pushed the pr/create-playlist-sidebar branch from 8234326 to 8c0c0dd Compare July 13, 2026 09:43
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

@sozercan sozercan left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

lgtm, thanks

@tsibog

tsibog commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

lgtm, thanks

Happy to contribute to this great project!

@sozercan
sozercan merged commit c1eedff into sozercan:main Jul 15, 2026
8 checks passed
Yoddikko pushed a commit to Yoddikko/kasetPlus that referenced this pull request Jul 16, 2026
Co-authored-by: Daniel Andersson <dandersson@evroc.com>
Co-authored-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
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