Skip to content

Fix stuck 'Searching Spotify' notification and silent library-fetch failures - #1023

Open
FlatterAtMainz wants to merge 8 commits into
jaedb:masterfrom
FlatterAtMainz:fix/search-notification-stuck
Open

Fix stuck 'Searching Spotify' notification and silent library-fetch failures#1023
FlatterAtMainz wants to merge 8 commits into
jaedb:masterfrom
FlatterAtMainz:fix/search-notification-stuck

Conversation

@FlatterAtMainz

Copy link
Copy Markdown

Summary

  • getSearchResults starts a "Searching Spotify" process notification (uiActions.startProcess) but never resolved it on request failure or on cancel-button click — only the success path called processFinished. Since a process notification only auto-closes in response to PROCESS_FINISHED/PROCESS_CANCELLED, a failed search (network blip, rate limit, expired token) left it stuck in running forever, and clicking the notification's × just flipped it to a dead-end cancelling state with no way out (nothing ever called processCancelled for this action).
  • Fixed by dispatching processFinished on the error path, and checking for a cancelling status in the success handler (matching the pattern already used correctly elsewhere in this file, e.g. getLibraryMoods).
  • Found and fixed the same underlying gap — no rejection handler at all — in five other Spotify library-fetch actions (getLibraryMoods, getLibraryPlaylists, getLibraryAlbums, getLibraryArtists, getLibraryTracks). These run with notification: false so the bug wasn't visibly stuck in the UI, but a failed request silently left running process state in Redux forever and the promise rejection was completely unhandled (not even surfaced as an error notification).

Test plan

  • Trigger a Spotify search, let it complete normally — confirm the "Searching Spotify" notification still clears as before.
  • Trigger a Spotify search, then click the notification's × while it's still in flight — confirm it now clears instead of getting stuck grey.
  • Force a search request to fail (e.g. throttle network) — confirm the notification clears and an error notification appears, instead of the process notification staying forever.
  • Force a library fetch (moods/playlists/albums/artists/tracks) to fail — confirm an error notification now appears instead of a silently swallowed rejection.

FlatterAtMainz and others added 8 commits July 20, 2026 17:32
.output-control__inner capped its height with max-height on mobile but
used overflow-y: visible, so a snapserver device/group list taller than
the popup just overflowed off-screen with no way to scroll to it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
max-height: calc(100vh - 140px) sizes the popup using the full
layout viewport, which on mobile includes space the browser's
address bar can occupy. When the address bar is visible, the
actually-visible viewport is shorter than 100vh, so the popup
(anchored to the bottom, growing upward) extends above what's
visible, hiding its top row. Add a 100dvh override, which tracks
the real visible viewport, after the 100vh fallback for browsers
that don't support dvh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
On mobile the popup only auto-opens via PlaybackControls expanding
(force_expanded=true), which also hides the popup's own close button
(display:none in CSS) and suppressed its own click-outside overlay.
That left no way to dismiss just the output-control popup - only
tapping in the empty space above it (collapsing the whole playback
bar) worked. Always render the click-outside overlay regardless of
force_expanded so the popup can close independently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Errors surfaced through handleException often ended up either blank
or as a bare technical fragment (e.g. "Could not load your profile"
with no explanation, or a raw "SyntaxError: JSON.parse: unexpected
end of input"). Add a humanizeError util that recognises common
failure shapes (invalid/empty JSON responses, network failures,
expired/revoked OAuth grants, known HTTP status codes) and prefixes
the description with a plain-language explanation, applied centrally
in the HANDLE_EXCEPTION handler so every existing call site benefits.

Also:
- handleException now falls back to a plain Error object's .message
  for the description, and no longer throws when data.error is
  undefined (message derivation had the same unguarded access).
- Wrap the JSON.parse of xhr.responseText in HANDLE_EXCEPTION in a
  try/catch - a non-JSON error response would otherwise throw inside
  the exception handler itself.
- Spotify getMe() and refreshToken() failures now get specific
  messages pointing at expired/revoked authorization, since that was
  the actual root cause behind the vague errors we hit in practice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… optional chaining

- Humanized error explanations now live under errors.* in en.yaml
  (via i18n()) instead of being hardcoded in JS, so they're
  translatable like the rest of the UI.
- Replace `x && x.y` null checks with optional chaining per review
  feedback.
…ailures

getSearchResults never resolved its process notification on request
failure or on cancel-button click, leaving it stuck in the UI forever.
The five Spotify library-fetch actions had the same underlying gap
(no rejection handler at all), silently swallowing errors and leaking
stuck process state on any failed request.
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