soulsync/webui/static
Broque Thomas f28f9808db Tidal: surface Favorite Tracks as virtual playlist (issue #502)
Adds the user's Tidal favorited tracks ("My Collection" in the Tidal
app) as a virtual playlist alongside their real playlists, mirroring
how Spotify's "Liked Songs" is treated.

Reporter (yug1900) located the working endpoint after the prior
`/v2/favorites?filter[type]=TRACKS` attempt returned empty data —
that endpoint is scoped to collections the third-party app created
itself, not personal favorites. Real endpoint:

    GET /v2/userCollectionTracks/me/relationships/items
        ?countryCode=US&locale=en-US&include=items

Cursor-paginated (20 per page, follow `links.next` with
`page[cursor]=...` until exhausted). Response only carries
track-level attributes — artist + album NAMES come back as
relationship-link stubs, not embedded data.

Implementation:

* Two-phase fetch — `_iter_collection_track_ids` walks the cursor
  chain to enumerate every track id (cheap, IDs only), then
  `get_collection_tracks` batch-hydrates 20 IDs at a time through
  the existing `_get_tracks_batch` helper which already knows how
  to `include=artists,albums`. No duplication of the JSON:API
  artist/album parse, no new dataclass shape.
* Virtual playlist `tidal-favorites` appended to the end of
  `/api/tidal/playlists`. ID intentionally has no colon —
  sync-services.js renderer interpolates IDs into CSS selectors
  via template literals (`#tidal-card-${p.id} .foo`) and a `:`
  would parse as a CSS pseudo-class operator.
* `tidal_client.get_playlist("tidal-favorites")` recognizes the
  virtual id and dispatches to the collection path internally, so
  every per-id consumer gets it for free: detail endpoint, mirror
  auto-refresh automation, "build Spotify discovery from Tidal
  playlist" flow.

OAuth scope expansion:

* Added `collection.read` to both OAuth flows (the
  `core/tidal_client.py::authenticate` standalone path AND the
  `web_server.py::auth_tidal` web flow — they were independent
  scope strings that both needed updating).
* Added `prompt=consent` to both flows — without it Tidal silently
  returns a token carrying only the ORIGINAL scope set even after
  re-authentication, because Tidal treats the existing
  authorization as still valid.
* New `disconnect()` method + `POST /api/tidal/disconnect`
  endpoint + Disconnect button next to Authenticate in Settings →
  Connections → Tidal — required for users whose existing token
  predates the scope expansion (forces a clean grant).

Reconnect-needed UI hint:

* `_collection_needs_reconnect` flag set on 401/403 from the
  collection endpoint, cleared on next successful walk, NOT set
  on 5xx (transient server errors must not falsely tell the user
  to reconnect).
* Listing endpoint reads the flag and surfaces a placeholder card
  titled "Favorite Tracks (reconnect Tidal to enable)" with a
  description pointing at Settings, so the user has something
  visible to act on instead of a silently missing row.

Diagnostic logging — collection request URL + response status +
first 300 bytes of body now logged at info level so future "why
is my collection empty" reports can be diagnosed from app.log
without needing live reproduction.

22 new tests pin: cursor walk (full chain, max-ids cap mid-page +
at page boundary), auth gates (no token / 401 / 403 all bail
clean), reconnect-flag lifecycle (set on 401/403, cleared on next
successful walk, NOT set on 5xx), forward-compat type filter
(non-track entries skipped), count helper, batch hydration
delegation + chunking at the 20-per-batch cap, partial-batch
failure containment, virtual-id dispatch (real playlist ids still
flow through the normal path).

Closes #502.
2026-05-10 21:36:22 -07:00
..
docs update docs page 2026-03-13 08:35:28 -07:00
vendor Add WebSocket real-time updates with automatic HTTP polling fallback 2026-03-03 18:26:29 -08:00
api-monitor.js Stop watchlist countdown refetch loop 2026-05-02 22:02:00 +03:00
artists.png add static images 2026-03-09 17:35:46 -07:00
automation.png add static images 2026-03-09 17:35:46 -07:00
beatport-ui.js Add "All Libraries (combined)" mode to PlexClient 2026-05-06 15:39:19 -07:00
core.js Rename metadata source status selectors 2026-05-02 22:02:01 +03:00
dashboard.png add static images 2026-03-09 17:35:46 -07:00
discover-section-controller.js Discover controller — Cin pre-review polish 2026-05-07 20:35:10 -07:00
discover.js Delete Recently Added / Top Tracks / Forgotten Favorites / Familiar Favorites 2026-05-08 07:31:51 -07:00
discover.png add static images 2026-03-09 17:35:46 -07:00
docs-images-needed.md Service Badges, Page Headers, Docs Page, and Bug Fixe 2026-03-10 14:47:11 -07:00
docs.js Clean up stale Artists-page references in helper.js + docs.js 2026-04-22 20:29:32 -07:00
downloads.js Manual picks: stream results, don't auto-retry, fix stuck-at-0% 2026-05-08 15:12:58 -07:00
enrichment.js Repair job card badge — show pending count, not last-scan count 2026-05-07 08:28:17 -07:00
explorer.png Create explorer.png 2026-03-30 17:23:31 -07:00
favicon.png enlarge favicon 2026-02-13 16:11:32 -08:00
help.png Add media server setup, processing settings, text import, automation history, and streaming details 2026-03-10 15:35:18 -07:00
helper.js Tidal: surface Favorite Tracks as virtual playlist (issue #502) 2026-05-10 21:36:22 -07:00
hydrabase.png Add Hydrabase dev UI and WebSocket support 2026-02-21 00:32:04 -08:00
import.png add static images 2026-03-09 17:35:46 -07:00
init.js Show artist breadcrumb on sidebar Library button when on artist-detail page 2026-05-01 17:49:34 -07:00
library.js Add download buttons + bulk action to artist top-tracks sidebar 2026-05-07 15:44:47 -07:00
library.png add static images 2026-03-09 17:35:46 -07:00
manifest.json Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
media-player.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
mobile.css Drop Show/Hide Results button + auto-restore cached results on navigate-back 2026-04-23 22:11:49 -07:00
pages-extra.js Fix bulk discography losing album source context (#399) 2026-04-30 12:42:46 -07:00
particles.js Add particle background toggle & optimize accent color caching 2026-03-13 16:05:22 -07:00
placeholder-album.png Add missing placeholder-album.png to stop 404 spam in console 2026-03-23 18:05:11 -07:00
pwa-icon-192.png Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
pwa-icon-512.png Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
search.js MusicBrainz: Fix artist images, total_tracks off-by-one, and Artist+Title queries 2026-04-24 10:17:59 -07:00
search.png add static images 2026-03-09 17:35:46 -07:00
settings.js Tidal: surface Favorite Tracks as virtual playlist (issue #502) 2026-05-10 21:36:22 -07:00
settings.png add static images 2026-03-09 17:35:46 -07:00
setup-wizard.css Add informative help text and tips to every setup wizard step 2026-04-11 23:26:01 -07:00
setup-wizard.js Rebrand folder terminology: Download→Input, Transfer→Output, Staging→Import 2026-04-18 17:35:20 -07:00
shared-helpers.js Fix non-admin profiles defaulting to Spotify on search picker 2026-05-07 11:53:02 -07:00
stats-automations.js Auto-import: bounded ThreadPoolExecutor + per-candidate UI state isolation 2026-05-09 17:45:42 -07:00
style.css Manual picks: stream results, don't auto-retry, fix stuck-at-0% 2026-05-08 15:12:58 -07:00
sw.js Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
sync-services.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
sync-spotify.js Delete Recently Added / Top Tracks / Forgotten Favorites / Familiar Favorites 2026-05-08 07:31:51 -07:00
sync.png add static images 2026-03-09 17:35:46 -07:00
trans.png Add SoulID worker with API-based debut year disambiguation 2026-03-21 10:21:06 -07:00
trans2.png Create trans2.png 2026-03-21 10:23:36 -07:00
whisoul.png Add library repair worker and UI 2026-02-21 15:00:23 -08:00
wishlist-tools.js Rename status cache to metadata_source 2026-05-02 22:02:00 +03:00
worker-orbs.js Add Discogs to worker orbs animation and fix button styling 2026-04-02 17:18:31 -07:00