Skip to content

fix(nowplaying): resolve album art via Tidal, not Spotify-only#499

Merged
thewrz merged 2 commits into
mainfrom
fix/nowplaying-art-via-tidal
Jun 19, 2026
Merged

fix(nowplaying): resolve album art via Tidal, not Spotify-only#499
thewrz merged 2 commits into
mainfrom
fix/nowplaying-art-via-tidal

Conversation

@thewrz

@thewrz thewrz commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Why

On production, the kiosk now-playing / recently-played views showed no album art while the Queue did. Root cause confirmed from prod logs: now-playing art was resolved by a single Spotify lookup (lookup_spotify_album_art), and Spotify is returning 403 — "Active premium subscription required for the owner of the app" for the app's Search API. Every now-playing track got album_art_url: null.

Meanwhile the Queue's art comes from guest search results, which are Tidal-primary — so Tidal-backed art worked while the Spotify-only now-playing path silently failed (errors are swallowed, art is non-critical).

What

Make now-playing art resolution layered, with Tidal (the app's primary provider) ahead of Spotify, in handle_now_playing_update:

  1. Reuse the matched request's art — guest search already fetched it (Tidal-primary), so when a now-playing track fuzzy-matches a request, use its artwork_url. Free, no API call. (Previously the match set matched_request_id but discarded its art.)
  2. Fresh Tidal lookup via the event owner's session — new lookup_tidal_album_art, mirroring how search resolves cover art.
  3. Spotify as last resort (still records spotify_track_id/uri when it's the source).

Reorders the function to fuzzy-match before art resolution so step 1 can reuse the request's art. Spotify/Tidal failures stay swallowed — now-playing art is non-critical and must never break a now-playing update.

No frontend change needed — the kiosk already renders nowPlaying.album_art_url, which this populates.

Testing

  • TDD: new TestLookupTidalAlbumArt (cover URL / no-track / error→None) + TestHandleNowPlayingUpdate cases (reuse request art, Tidal primary, Spotify fallback, all-miss→null). Confirmed RED before, GREEN after.
  • Full backend suite: 3071 passed, coverage 88.60% (≥85% gate)
  • ruff + format + bandit clean
  • CI green
  • Post-deploy: now-playing/recently-played art appears on the kiosk for live tracks (independent of Spotify's 403)

🤖 Co-authored by Claude Opus 4.8 (1M context). Follow-up to the #492 join-code / now-playing work.

Now-playing album art was resolved by a single Spotify lookup
(lookup_spotify_album_art). On production that 403s — "Active premium
subscription required for the owner of the app" — so every now-playing track
got album_art_url=null, while the Tidal-primary guest-search path kept the
queue's art working. Result: art on the queue, none on the now-playing /
recently-played kiosk views.

Make now-playing art resolution layered, with Tidal (the app's primary provider)
ahead of Spotify:
  1. reuse the matched request's already-fetched artwork_url (free; guest search
     is Tidal-primary) — previously the match set matched_request_id but threw
     away its art;
  2. else a fresh Tidal lookup via the event owner's session (new
     lookup_tidal_album_art, mirroring how search gets cover art);
  3. else Spotify as a last resort (still sets spotify_track_id/uri when used).

Reorders handle_now_playing_update to fuzzy-match before art so step 1 can reuse
the request's art. Spotify failures remain swallowed — art is non-critical.

Regression: TestLookupTidalAlbumArt + new TestHandleNowPlayingUpdate cases
(reuse request art, Tidal primary, Spotify fallback, all-miss → null).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thewrz, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 hours, 19 minutes, and 22 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

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 credits.

🚦 How do rate limits work?

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

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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 02d387aa-21af-421b-8028-bb7bc3b54bd5

📥 Commits

Reviewing files that changed from the base of the PR and between f533c00 and c49b095.

📒 Files selected for processing (3)
  • server/app/services/bridge_integration.py
  • server/app/services/now_playing.py
  • server/tests/test_now_playing.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/nowplaying-art-via-tidal

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

Adversarial review caught that lookup_tidal_album_art runs the owner's Tidal
session via get_tidal_session(), which commits the DB session on an OAuth token
refresh. Because the art lookup ran after the request->PLAYING transition and
now_playing upsert were already staged, a token refresh could prematurely
commit this handler's bridge mutations before its own db.commit().

Resolve the fuzzy match and album art (the only DB-committing external call)
first, into locals, while nothing of ours is staged; then stage the archive,
status transitions, now_playing upsert and request link, and commit once. Art
precedence (matched request -> Tidal -> Spotify) is unchanged and Spotify stays
fallback-only. Pinned with a regression test asserting the art lookup runs
before the request is transitioned to PLAYING.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thewrz

thewrz commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator Author

Ran an adversarial Codex review (gpt-5.5, read-only) on this branch. Two findings; disposition below.

1. Transaction boundary — fixed in c49b095. lookup_tidal_album_artget_tidal_session() commits the DB session on a Tidal OAuth token refresh (tidal.py:162). Because the art lookup ran after the request→PLAYING transition and the now_playing upsert were staged, a token refresh could prematurely commit those mutations before the handler's own db.commit(). Reordered so the fuzzy match + art resolution (the only DB-committing external call) run before any bridge write is staged; the archive/transition/upsert/link are then staged and committed once. Added a regression test asserting the art lookup runs before the request is transitioned to PLAYING. No behaviour change — art precedence (matched request → Tidal → Spotify) preserved, Spotify still fallback-only, full suite green (3072 pass, 88.6% cov).

2. spotify_track_id/spotify_uri left null when request/Tidal art wins — declined (intentional). Your tests (test_prefers_tidal_art_when_no_request_match, test_reuses_matched_request_art) deliberately assert Spotify isn't called in those paths, so populating the IDs would require an unconditional Spotify call — the very thing this PR removes. Impact is low: playhistory_feedback tier-0 (spotify_track_id exact) only degrades for Spotify-working owners, and tiers 1–2 (dedupe_sig / fuzzy) still match. Flagging for awareness; left as your design choice.

@thewrz thewrz merged commit 8e02b6e into main Jun 19, 2026
10 checks passed
@thewrz thewrz deleted the fix/nowplaying-art-via-tidal branch June 19, 2026 14:36
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.

1 participant