v0.31.0: Cassette followed playlists — a friend's playlist in your sidebar

- A followed playlist (the UFO in friend mode) appears in my sidebar with
  a purple UFO: mixed in with my playlists alphabetically, or inside a
  folder named after the friend (per-friend, in Sync Settings). It's never
  a Playlist of mine, so it stays out of the library and its merges.
- It plays from the cache. Player's queue accepts friend-track keys,
  resolved through Cassette: they scrobble to Last.fm but don't count in
  my play journal. Songs still on their way are grey and skipped.
- The cassette column there is hidden by default (right-click the
  header); pressing one takes the song into my library — straight from
  the cache if it's there, otherwise by asking for it.
- Sync Settings per friend: keep followed songs separate (default) or add
  them to my library. Unfollowing (right-click) frees their cache.
- The bottom bar says when the friend's folder last synced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 21:40:59 -07:00
co-authored by Claude Opus 5.5
parent ea7f57e38b
commit 87d43f8e0c
14 changed files with 809 additions and 24 deletions
+2 -2
View File
@@ -236,7 +236,7 @@ def test_loop_between_two_libraries(qapp, tmp_path, mp3_file):
_sync(alice.root, bob.root, token)
# Bob cassettes Alice's song and saves.
bob.save_requests(token, {5}, [], {5})
bob.save_requests(token, {5}, [])
_sync(alice.root, bob.root, token)
def run(coordinator):
@@ -326,7 +326,7 @@ def test_real_delivery(qapp, tmp_path, syncthing_pair, mp3_file):
edited.friends[token].selection = Selection(all_library=True)
a.apply_settings(edited)
assert settle(lambda: 5 in load_their_ids(b, token))
b.save_requests(token, {5}, [], {5})
b.save_requests(token, {5}, [])
assert settle(lambda: len(bob_mgr.library.tracks) == 1), "never arrived"
assert settle(lambda: not any((out_dir(a.root, token) / "outbox").glob("*"))), \
"sender's outbox never cleaned up"