Skip to content

fix(nav): preserve detail payloads in history + don't toggle off shuffle#24

Merged
InstaZDLL merged 3 commits into
mainfrom
fix/detail-views-nav-and-shuffle
May 15, 2026
Merged

fix(nav): preserve detail payloads in history + don't toggle off shuffle#24
InstaZDLL merged 3 commits into
mainfrom
fix/detail-views-nav-and-shuffle

Conversation

@InstaZDLL
Copy link
Copy Markdown
Owner

@InstaZDLL InstaZDLL commented May 15, 2026

Summary

Addresses two pre-existing UX bugs surfaced by CodeRabbit on #23 — both apply to album / artist / genre detail views since they share the same pattern. Targets feat/genre-detail-view so it merges in lockstep with #23.

1. Nav back/forward lost the target id

viewHistory stored only ViewId strings while activeAlbumId / activeArtistId / activeGenreId / activePlaylistId / activeWrappedYear lived in parallel useState. Sequence:

  1. visit album AactiveAlbumId = A
  2. go home
  3. visit album BactiveAlbumId = B
  4. back, back → view is album-detail but activeAlbumId is still B, not A

Fix: store HistoryEntry { id, ...payload } in viewHistory, derive every active*Id from the current entry. Drops the parallel state and lets back/forward restore the exact target.

2. Shuffle button toggled instead of enabling

The detail-view handleShufflePlay called toggleShuffle() unconditionally. If shuffle was already on, clicking "Shuffle" disabled it and played the queue in order. Fix: gate with if (!isShuffled) in all three detail views.

Collateral

Sidebar's setActivePlaylistId prop becomes navigateToPlaylist, removing the call sites that paired two setters in sequence.

Test plan

  • album A → home → album B → back → back → lands on album A (and same for artist / genre)
  • shuffle ON + click "Shuffle" in any detail view → still ON, queue plays shuffled
  • shuffle OFF + click "Shuffle" → turns ON, queue plays shuffled
  • sidebar create-playlist + select-playlist + import-m3u still navigate to the playlist
  • bun run typecheck

Summary by CodeRabbit

  • Refactor

    • Centralized navigation so views and their payloads come from a single navigation history; sidebar now navigates via a dedicated playlist navigation callback and deleted playlists return the user to Home.
  • Bug Fixes

    • Shuffle buttons in album, artist, and genre views now activate shuffle without turning it off when shuffle is already enabled.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b8fcf83c-1ec0-4072-97f2-b7a14cd95c79

📥 Commits

Reviewing files that changed from the base of the PR and between afd356c and ad44706.

📒 Files selected for processing (1)
  • src/components/layout/AppLayout.tsx

📝 Walkthrough

Walkthrough

Navigation state moved to a typed HistoryEntry stack in AppLayout; active view and payload ids are derived from the current history entry. Navigation callbacks push/replace history entries. Sidebar now navigates via a navigateToPlaylist callback. Album/Artist/Genre shuffle handlers only enable shuffle when it's currently off.

Changes

Navigation History Refactor and Shuffle State Awareness

Layer / File(s) Summary
Navigation history model and state structure
src/components/layout/AppLayout.tsx
Introduce HistoryEntry discriminated union and replace multiple navigation-related React state with a single navState containing history and index.
History navigation logic and AppLayout callbacks
src/components/layout/AppLayout.tsx
Derive activeView and payload ids from the current history entry; add pushEntry/replaceEntry; reimplement navigation callbacks to push/replace payloaded HistoryEntry objects; replace playlist-delete entry with { id: "home" }.
Sidebar navigation integration
src/components/layout/Sidebar.tsx, src/components/layout/AppLayout.tsx
Change SidebarProps to accept navigateToPlaylist(id) instead of setActivePlaylistId; rewire playlist create/import/select flows to call navigateToPlaylist; update AppLayout to pass navigateToPlaylist.
Shuffle state-aware handlers in detail views
src/components/views/AlbumDetailView.tsx, src/components/views/ArtistDetailView.tsx, src/components/views/GenreDetailView.tsx
Destructure isShuffled from usePlayer() and update handleShufflePlay to call toggleShuffle() only when isShuffled is false, preventing the Shuffle button from switching shuffle off.

Sequence Diagram(s)

sequenceDiagram
  participant Sidebar
  participant AppLayout
  participant DetailView
  participant Player
  Sidebar->>AppLayout: navigateToPlaylist(playlistId)
  AppLayout->>AppLayout: pushEntry({ id: "playlist", playlistId })
  AppLayout->>DetailView: derive activePlaylistId from currentEntry
  DetailView->>Player: playTracks(...) / check isShuffled
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • InstaZDLL/WaveFlow#23: This PR's history/refactor changes interact with genre-detail routing and shuffle behavior touched in PR #23.

Suggested labels

size: xl

Poem

🐰 I hop through entries, tidy and neat,
Payloads in pockets to make routes complete,
Sidebar nudges the stack with a gentle tap,
Shuffle listens, and won't flip the map,
A soft trail of hops — navigation on beat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the two main fixes: preserving detail view payloads in history navigation and preventing shuffle toggle-off behavior.
Description check ✅ Passed The PR description is comprehensive, addressing the two main bugs with clear before/after explanations, a detailed test plan with checkmarks, and collateral changes documented.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/detail-views-nav-and-shuffle

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


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

@github-actions github-actions Bot added scope: frontend React/Vite frontend (src/) type: fix Bug fix size: m 50-200 lines labels May 15, 2026
Base automatically changed from feat/genre-detail-view to main May 15, 2026 21:18
… shuffle

two pre-existing UX bugs in the detail views, surfaced by CodeRabbit on
the genre-detail PR but identical for album / artist / genre:

1. nav back/forward lost the target id. viewHistory only stored ViewId
   strings, with activeAlbumId / activeArtistId / activeGenreId /
   activePlaylistId / activeWrappedYear held in parallel useState. visiting
   album A → home → album B → back → back landed on "album-detail" with
   activeAlbumId still pointing at B. fix: store HistoryEntry { id, payload }
   in viewHistory and derive every active*Id from the current entry. drops
   the parallel state and lets back/forward restore the exact target.

2. shuffle button toggled instead of enabling. handleShufflePlay called
   toggleShuffle() unconditionally, so clicking "Shuffle" while shuffle was
   already on disabled it and played the queue in order. fix: gate the
   toggle with `if (!isShuffled)` in all three detail views.

Sidebar's setActivePlaylistId prop becomes navigateToPlaylist, removing
the call sites that used to call two setters in sequence.
@InstaZDLL InstaZDLL force-pushed the fix/detail-views-nav-and-shuffle branch from 03024c0 to 445cd4e Compare May 15, 2026 21:26
@InstaZDLL InstaZDLL self-assigned this May 15, 2026
@coderabbitai coderabbitai Bot added the size: xl > 500 lines label May 15, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/layout/AppLayout.tsx`:
- Around line 108-122: History entries for the Library view don't include the
selected tab, so HistoryEntry currently has only { id: "library" } while
libraryTab is stored separately; update HistoryEntry to include the selected tab
(e.g., { id: "library"; tab?: "albums" | "artists" | "tracks" | ... }) and
change all places that push { id: "library" } to instead push the entry with the
current tab, then implement a dedicated library-navigation callback (e.g.,
deriveLibraryTabFromEntry or onNavigateLibrary) that reads the tab from the
current HistoryEntry and sets libraryTab accordingly when
navigating/back/forward; update usages of HistoryEntry, navigation push calls,
and the component state updater that currently sets libraryTab externally to
always derive it from the active HistoryEntry.
- Around line 362-365: The onAfterDelete handler passed into PlaylistView
currently appends a new "home" history entry via setActiveView("home"), leaving
the deleted playlist as a back-target; instead replace/prune the current history
entry so Back doesn't return to the deleted playlist. Change the onAfterDelete
implementation to perform a history replace (e.g. use your router's
navigate(..., { replace: true }) or window.history.replaceState(...) to set the
view to home) and then update local state (setActiveView) as needed; update the
PlaylistView onAfterDelete usage to call the replace-based navigation rather
than pushing a new entry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7bfd9e52-076a-41a4-a02f-dd803a418945

📥 Commits

Reviewing files that changed from the base of the PR and between 0a5fbb9 and 445cd4e.

📒 Files selected for processing (5)
  • src/components/layout/AppLayout.tsx
  • src/components/layout/Sidebar.tsx
  • src/components/views/AlbumDetailView.tsx
  • src/components/views/ArtistDetailView.tsx
  • src/components/views/GenreDetailView.tsx

Comment thread src/components/layout/AppLayout.tsx
Comment thread src/components/layout/AppLayout.tsx
… home

deleting a playlist while viewing it called setActiveView("home") which
pushed a new entry on top of the deleted playlist. back navigated to the
ghost. add replaceEntry() that swaps the current entry in place and use
it from onAfterDelete so the deleted playlist falls out of history.
@github-actions github-actions Bot removed the size: xl > 500 lines label May 15, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/layout/AppLayout.tsx`:
- Around line 235-255: pushEntry and replaceEntry capture stale historyIndex
causing lost entries or out-of-bounds reads; change the state shape to a single
object (e.g. { viewHistory, historyIndex }) and update both values atomically in
one functional setter so the updater has current index and history together;
replace setViewHistory/setHistoryIndex uses in pushEntry and replaceEntry with a
single setHistoryState(prev => { compute newHistory =
[...prev.viewHistory.slice(0, prev.historyIndex + 1), entry] (or for replace,
copy and set newHistory[prev.historyIndex]=entry); return { viewHistory:
newHistory, historyIndex: prev.historyIndex + (push? 1 : 0) } }); update all
references to viewHistory and historyIndex to read from the combined state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 35298a9a-10b0-4bc1-b5aa-16b166517786

📥 Commits

Reviewing files that changed from the base of the PR and between 445cd4e and afd356c.

📒 Files selected for processing (1)
  • src/components/layout/AppLayout.tsx

Comment thread src/components/layout/AppLayout.tsx Outdated
splitting the history array and its index across two useState calls let
rapid navigations queue setters that all read the same stale index from
their closure, so the second push slices to the same position as the
first (losing the first entry) while setHistoryIndex still advances by
one — leaving the index past history.length - 1.

collapse both into a single { history, index } state and update them in
one functional setter so push / replace / goBack / goForward always see
the current pair.
@coderabbitai coderabbitai Bot added the size: xl > 500 lines label May 15, 2026
@InstaZDLL InstaZDLL merged commit 981144c into main May 15, 2026
13 checks passed
@InstaZDLL InstaZDLL deleted the fix/detail-views-nav-and-shuffle branch May 15, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: frontend React/Vite frontend (src/) size: m 50-200 lines size: xl > 500 lines type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant