Skip to content

Respect the custom order of playlists and favorites#207

Merged
phanan merged 4 commits into
masterfrom
fix/playlist-custom-order
Jul 12, 2026
Merged

Respect the custom order of playlists and favorites#207
phanan merged 4 commits into
masterfrom
fix/playlist-custom-order

Conversation

@phanan

@phanan phanan commented Jul 12, 2026

Copy link
Copy Markdown
Member

Fixes #202.

Problem

On mobile, playlist and favorites songs were always re-sorted — defaulting to Title, with only Title / Artist / Recently added available. That discarded the user-defined order the server returns:

  • Playlists: playlists/{id}/songs->orderByPivot('position') (since koel 7.0.2)
  • Favorites: songs/favorites->orderBy('favorites.position'), with a favorites/move endpoint (since koel 9.0.0)

So the app couldn't play them in their intended order, and "Recently added" sorts by the song's date rather than the add order.

Changes

  • $sort: a 'position' field preserves the server-provided order (reversible via the sort direction) instead of comparing a field. It also returns a new list rather than sorting in place, so it no longer mutates the cached list.
  • Sort menu: adds Custom order.
  • Playlist details and Favorites: default to Custom order and offer it in the sort button.

Older servers

'position' is a sort-mode sentinel, not a per-song field — nothing is read from the response, so the sort can't break on any server. But an older server returns a raw (non-custom) order, so the Custom order default and menu entry are gated behind the server version (Feature.customPlaylistOrder = 7.0.2, Feature.customFavoritesOrder = 9.0.0); older servers keep the previous Title default.

Tests

  • playable_list_test.dart: Custom order keeps the server order; descending reverses it; other fields still sort; $sort doesn't mutate the original.
  • features_test.dart: version thresholds for the two new features.

Summary by CodeRabbit

  • New Features
    • Added a “Custom order” (position) option to sort menus for playlist details and favorites.
    • Playlist details and favorites now default to custom order when supported; otherwise they default to title.
  • Bug Fixes
    • Sorting by position now preserves the server/custom order.
    • Descending position correctly reverses the custom order.
    • Sorting no longer mutates the original list.
  • Tests
    • Added coverage for custom playlist/favorites ordering, sort direction behavior, and non-mutation.

The app always re-sorted playlist songs (default by title), discarding
the order the server returns — which for a regular playlist is the
custom order defined on the web (pivot `position`).

Add a "Custom order" sort that preserves the fetched order (reversible),
make it the default for playlists, and make `$sort` return a new list so
it no longer mutates the cached playlist in place.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@phanan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 06adf53e-96f1-4fe6-869c-9f4306c4d89c

📥 Commits

Reviewing files that changed from the base of the PR and between 90d077f and e99d7aa.

📒 Files selected for processing (1)
  • test/extensions/playable_list_test.dart
📝 Walkthrough

Walkthrough

Playlist and favorites sorting now support server-defined custom order. API-version feature flags gate the controls, ascending position sorting preserves server order, descending sorting reverses it, and tests cover feature support and sorting behavior.

Changes

Custom ordering

Layer / File(s) Summary
Feature flags and version thresholds
lib/utils/features.dart, test/utils/features_test.dart
Adds feature flags for custom playlist and favorites ordering with minimum API versions and tests for supported and unsupported versions.
Position-aware sorting
lib/extensions/playable_list.dart, test/extensions/playable_list_test.dart
$sort preserves or reverses position order by direction, with tests covering fallback title sorting and list immutability.
Feature-gated sorting controls
lib/ui/widgets/playable_list_sort_button.dart, lib/ui/screens/playlist_details.dart, lib/ui/screens/favorites.dart
The sort menu labels position as “Custom order,” and each screen conditionally defaults to and exposes position sorting based on feature support.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppState
  participant FeatureSupport
  participant PlaylistOrFavoritesScreen
  participant SortButton
  participant PlayableSort
  AppState->>FeatureSupport: check API version
  FeatureSupport-->>PlaylistOrFavoritesScreen: report custom ordering support
  PlaylistOrFavoritesScreen->>SortButton: provide position field when supported
  SortButton->>PlayableSort: select position and direction
  PlayableSort-->>PlaylistOrFavoritesScreen: preserve or reverse server order
Loading

Possibly related PRs

  • koel/player#124: Updates the same sort-field mapping with related sorting options.
  • koel/player#127: Relates to sort-field-dependent UI behavior in playlist and favorites screens.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Favorites-specific ordering, feature flags, and tests go beyond the linked playlist-only issue #202. If unintended, split favorites support into a separate issue or PR; otherwise add a linked issue covering favorites custom order.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies issue #202 by preserving the custom playlist order in the app and supporting reversal and defaults.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preserving custom order for playlists and favorites.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/playlist-custom-order

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Favorites carry a server-side custom order (favorites.position, with a
favorites/move endpoint) just like playlists. Default the Favorites
screen to Custom order and offer it in the sort button.
@phanan phanan changed the title Respect a playlist's custom order Respect the custom order of playlists and favorites Jul 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/extensions/playable_list_test.dart (1)

8-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use descriptive variable names instead of single-letter identifiers.

a, b, c violate the coding guideline for **/*.dart files: single-letter names are only permitted for loop counters (i, j) and the test harness (h). Consider songA, songB, songC or names reflecting their role in the test.

As per coding guidelines: "Don't use single-letter variable names except for: i and j for loop counters, and h for the test harness. For all other cases (callback params, destructured fields, lambda args, etc.), pick a descriptive name that says what it is."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/extensions/playable_list_test.dart` at line 8, Replace the single-letter
Song fields a, b, and c in the playable list tests with descriptive names such
as songA, songB, and songC, and update every reference consistently while
preserving the existing test behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/extensions/playable_list_test.dart`:
- Around line 38-41: Extend the existing “does not mutate the original list”
test to also exercise sorting via the position field, using the position sort
configuration and asserting the original list remains unchanged. Keep the
current title immutability assertion intact.

---

Nitpick comments:
In `@test/extensions/playable_list_test.dart`:
- Line 8: Replace the single-letter Song fields a, b, and c in the playable list
tests with descriptive names such as songA, songB, and songC, and update every
reference consistently while preserving the existing test behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c4551d5-5e2c-499d-a052-73abfa2e0f7b

📥 Commits

Reviewing files that changed from the base of the PR and between 9defe28 and d5e0b30.

📒 Files selected for processing (4)
  • lib/extensions/playable_list.dart
  • lib/ui/screens/playlist_details.dart
  • lib/ui/widgets/playable_list_sort_button.dart
  • test/extensions/playable_list_test.dart

Comment thread test/extensions/playable_list_test.dart Outdated
Comment on lines +38 to +41
test('does not mutate the original list', () {
final original = customOrder();
original.$sort(config('title', SortOrder.asc));
expect(original, [c, a, b]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add an immutability assertion for the position sort path.

The immutability test only covers the title field. Since position is the primary new feature, explicitly verifying that it also doesn't mutate the original list would close the coverage gap.

🧪 Suggested additional test
     test('does not mutate the original list', () {
       final original = customOrder();
       original.$sort(config('title', SortOrder.asc));
+      original.$sort(config('position', SortOrder.asc));
+      original.$sort(config('position', SortOrder.desc));
       expect(original, [c, a, b]);
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('does not mutate the original list', () {
final original = customOrder();
original.$sort(config('title', SortOrder.asc));
expect(original, [c, a, b]);
test('does not mutate the original list', () {
final original = customOrder();
original.$sort(config('title', SortOrder.asc));
original.$sort(config('position', SortOrder.asc));
original.$sort(config('position', SortOrder.desc));
expect(original, [c, a, b]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/extensions/playable_list_test.dart` around lines 38 - 41, Extend the
existing “does not mutate the original list” test to also exercise sorting via
the position field, using the position sort configuration and asserting the
original list remains unchanged. Keep the current title immutability assertion
intact.

phanan added 2 commits July 12, 2026 22:19
Playlist ordering landed in koel 7.0.2 and favorites ordering in 9.0.0.
On older servers the returned order isn't a custom order, so gate the
"Custom order" default (and menu entry) behind the server version,
falling back to the previous Title default. Nothing reads a per-song
position field, so the sort itself was already safe on any server.
Rename songA/B/C and also assert $sort doesn't mutate the original list
when sorting by position.
@phanan phanan merged commit 2982f19 into master Jul 12, 2026
2 checks passed
@phanan phanan deleted the fix/playlist-custom-order branch July 12, 2026 20:23
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.

Playlist order defined in web is ignored inside app

1 participant