Skip to content

Add previous/next album controls to the drawer - #306

Merged
IanHoar merged 4 commits into
mainfrom
feat/feed-album-navigation
Oct 3, 2026
Merged

IanHoar merged 4 commits into
mainfrom
feat/feed-album-navigation

Conversation

@IanHoar

@IanHoar IanHoar commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Previous album / Next album controls to the side player on the feed and on label/artist pages. They step through the page's releases and scroll the selected one into view. On the feed, going past the last loaded story loads more.

Screen.Recording.2026-10-01.at.11.32.26.AM.compressed.mp4

Review notes

  • discography.ts now takes a pluggable album source; label pages keep the existing grid, and the feed registers its own in pages/feed.ts.
  • Feed pagination works by scrolling to the bottom and waiting (up to 10s) for new stories.
  • Also ignores album loads that finish after the drawer has moved on, so clicking quickly can't show a stale album.

Testing

  • On a feed, preview a story and step through with Next/Previous album, including past the last loaded story.
  • On a label page, step through the discography with the album controls.

The feed now acts as the drawer's album source, so the player can step
through its stories. When stepping past the last loaded story, the page
is scrolled to the bottom so the feed pages in more, and the drawer waits
for them before moving on. The selected story is scrolled into view.

Only the story list is walked: the new-releases carousel above it repeats
stories in hidden slides.

Also ignore album loads that resolve after the drawer moved on, and toggle
the preview button against the album the drawer is actually showing.
@sabjorn

sabjorn commented Oct 1, 2026

Copy link
Copy Markdown
Owner

@IanHoar I’ll take a look at this soon.

Probably you saw that on Label/Artist pages this behaviour exists just without the buttons? As in — clicking next on the last track of an album goes to the next album.

I like the idea of explicit buttons so if this isn’t setup to work on Label/Artists pages too — let me know and I can look at adding

@IanHoar

IanHoar commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

I actually hadn't seen that yet. I'll double check that I haven't broken anything there, and that this is working on those pages

@IanHoar

IanHoar commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Using this now to catch up on my feed and I feel like I'm in Bandcamp nirvana

On the feed, previews were bound by the feed and again by the label
view's page-wide pass, so one click opened the drawer and the second
handler saw the album already open and minimized it.
The discography grid now opts into the drawer's previous/next album
controls and scrolls the selected album into view, like the feed.
@IanHoar IanHoar changed the title Add previous/next album controls to the drawer on the feed Add previous/next album controls to the drawer Oct 1, 2026
@IanHoar
IanHoar force-pushed the feat/feed-album-navigation branch from a115b90 to 42b0a0b Compare October 1, 2026 19:11

@sabjorn sabjorn left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

basically ready to go -- just a bit of test coverage missing and a few suggestions

Comment thread src/label_view.ts Outdated
Comment on lines +106 to +107
// Feed previews are bound by the feed and again by the label view's page-wide pass.
if (button.dataset.besBound === 'true') return;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Suggested change
// Feed previews are bound by the feed and again by the label view's page-wide pass.
if (button.dataset.besBound === 'true') return;
const feedPreviewsBounded = button.dataset.besBound === 'true';
if (feedPreviewsBounded) return;

Comment thread src/pages/feed.ts Outdated
const seen = new Set<string>();
const items: DiscographyItem[] = [];

// Only walk the story list; the new-releases carousel above it repeats stories in hidden slides.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

make sure you have a test covering this instead of a comment

Suggested change
// Only walk the story list; the new-releases carousel above it repeats stories in hidden slides.

Comment thread src/pages/feed.ts Outdated
return items;
}

// The feed pages in more stories as the reader nears the bottom, so scroll there and wait for them.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Suggested change
// The feed pages in more stories as the reader nears the bottom, so scroll there and wait for them.

Comment thread src/pages/feed.ts Outdated
});
}

function feedContainer(): HTMLElement {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

/nit /non-blocking
unless it is important to tests -- I generally prefer inline calls for this type of single line function -- i.e. directly calling in the code rather than adding a layer of abstraction

const feedContainer = document.getElementById('stories') || document.body;

I understand there are risks since maybe this changes one day and it has to be updated in multiple places -- but that is a good time to refactor into a function.

Comment thread src/discography.ts Outdated
Comment on lines +21 to +27
const discographySource: AlbumSource = {
extract: extractDiscographyOrder,
reveal: item => item.element.scrollIntoView?.({ behavior: 'smooth', block: 'center' }),
showAlbumControls: true
};

let source: AlbumSource = discographySource;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

why create just to copy over? discographySource isn't used anywhere else, right?

Suggested change
const discographySource: AlbumSource = {
extract: extractDiscographyOrder,
reveal: item => item.element.scrollIntoView?.({ behavior: 'smooth', block: 'center' }),
showAlbumControls: true
};
let source: AlbumSource = discographySource;
let source: AlbumSource = {
extract: extractDiscographyOrder,
reveal: item => item.element.scrollIntoView?.({ behavior: 'smooth', block: 'center' }),
showAlbumControls: true
};

Comment on lines +242 to +244
if (requestedAlbumId !== albumId) {
log.debug(`Album ${albumId} arrived after the drawer moved on`);
return;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

test for this guard please

Comment thread src/components/player/loader.ts Outdated
}

async function skipAlbum(load: () => Promise<boolean>): Promise<void> {
if (albumNavLoading) return;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

test for this please

Comment thread test/feed.test.ts Outdated
await vi.advanceTimersByTimeAsync(1000);

await expect(loading).resolves.toBe(false);
vi.useRealTimers();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

needs to be in the afterEach or else it can fail to be cleaned up if the line above fails

Comment thread test/playerLoader.test.ts

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

test the change the enableFetchCaching default change for loadNextAlbum

it('keeps the caching preference when hopping albums', async () => {
  discography.updateDiscographyOrder();
  await player.loadAlbumIntoDrawer('123', 'album', true);

  await player.loadNextAlbum();   // bare call — exercises the default

  expect(vi.mocked(createFetchFunction)).toHaveBeenLastCalledWith(true);
});

Inline the feed container and discography source, swap comments for
named values and tests, clean up timers in afterEach, and drop the
skipAlbum guard in favour of the disabled buttons it duplicated. Add
tests for the stale album guard, double clicks while loading, and the
caching preference carrying over when hopping albums.

@sabjorn sabjorn left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

merge away

@IanHoar

IanHoar commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

🤩 thanks for forging a path for this one. I find it extremely enjoyable to navigate my feed now

@IanHoar
IanHoar merged commit 7b4ff2d into main Oct 3, 2026
1 check passed
@IanHoar
IanHoar deleted the feat/feed-album-navigation branch October 3, 2026 00:50
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.

2 participants