Skip to content

Bug: Permanent loading spinner after adding a video to a playlist from the playlist page #980

Description

@wilsonbirch

Reproduction steps

  1. Open a playlist from Playlists (i.e. be on the playlist detail page, /playlist/<id>)
  2. On any video in that playlist, open the "..." menu → Add to playlist
  3. Choose Create new playlist, name it, press Create
  4. In the re-opened dialog, tick the new playlist and press Add

Adding to an existing playlist from the playlist detail page reproduces it too — "Create new playlist" is just the most common way to hit it.

Actual result

The playlist page is replaced by a loading spinner that never resolves. The page stays that way until you navigate away and back.

Note the add itself succeeds server-side — the video really is in the target playlist. Only the view dies, which makes it look like "nothing happened".

Expected result

The dialog closes and the playlist page stays rendered (refreshed in place).

Grayjay Version

Desktop, versionCode 17 (stable), Linux x86_64 (Flathub app.grayjay.Grayjay).

This is not Flatpak-specific — the cause is in the shared web frontend and I confirmed both lines below are present in current master.

What plugins are you seeing the problem on?

N/A — plugin-independent UI bug.

Are you using a VPN?

No

Root cause

Grayjay.Desktop.Web/src/pages/Playlist/index.tsx:65 passes an id-less refetch callback to PlaylistDetailView:

refetch={() => refetch()}   // drops the playlist id

refetch(id?) (same file, ~lines 15–30) takes its else branch when called with undefined and does setPlaylist(undefined). The render gate at line 37 is:

<Show when={playlist$() && !isLoading$()} fallback={<CenteredLoader />}>

so clearing the signal renders CenteredLoader permanently.

Nothing recovers it:

  • the only fetch trigger is createEffect(async () => await refetch(params.id)) (lines 32–34), which tracks params.id — unchanged, so it never re-runs;
  • the playlist detail page registers no PlaylistsChanged websocket handler (only the playlists list page does, pages/Playlists/index.tsx:51), so the broadcast from StatePlaylists.CreateOrUpdate doesn't rescue it either.

Call path: components/PlaylistDetailView/index.tsx:73UIOverlay.overlayAddToPlaylist(content, () => props.refetch?.()) → on Add, onAdded invokes the id-less refetch().

The sibling remove-handler at pages/Playlist/index.tsx:61 already calls refetch(id) correctly, which suggests this is simply an oversight.

Suggested fix

--- a/Grayjay.Desktop.Web/src/pages/Playlist/index.tsx
+++ b/Grayjay.Desktop.Web/src/pages/Playlist/index.tsx
@@ -62,7 +62,7 @@ const PlaylistPage: Component = () => {
         }}
         onAddToQueue={(v) => video?.actions?.addToQueue(v)}
         onDownload={() => {}}
-        refetch={() => refetch()}
+        refetch={() => refetch(params.id)}
         onDragEnd={async () => {
           const playlist = playlist$();
           if (playlist) {

Adjacent bug found while tracing this (same patch): Grayjay.Desktop.Web/src/overlays/OverlayDialog/index.tsx:152 calls checkedInputs.splice(index) with no delete count in the checkbox handler, so unchecking one item silently removes that item and every selection after it:

--- a/Grayjay.Desktop.Web/src/overlays/OverlayDialog/index.tsx
+++ b/Grayjay.Desktop.Web/src/overlays/OverlayDialog/index.tsx
@@ -149,7 +149,7 @@ const OverlayDialog: Component<OverlayDialogProps> = (props: OverlayDialogProps)
         changed = true;
       }
       if (!next && index >= 0) {
-        checkedInputs.splice(index);
+        checkedInputs.splice(index, 1);
         changed = true;
       }
       output.selected = checkedInputs;

Optional hardening, not in the patch above:

  • make refetch() with no id keep the current playlist instead of clearing state, so a future id-less caller degrades gracefully;
  • register a PlaylistsChanged handler on the detail page — though note StatePlaylists.AddContentToPlaylists never fires OnPlaylistsChanged, so plain adds don't broadcast at all today.

Happy to submit this as a merge request if that's useful — I understand development happens on gitlab.futo.org and self-registration there is closed, so let me know how you'd prefer to take it. Disclosure: the change is mechanical and read-verified against master, but I have not built the frontend locally to runtime-test it.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions