perf(queue): virtualize the queue list (#857)

* perf(queue): virtualize the queue list

Render the QueuePanel queue through @tanstack/react-virtual so the DOM stays
O(visible rows) instead of O(queue length) — a 10k+ Artist Radio queue no
longer creates tens of thousands of DOM nodes. Row logic (reorder DnD, radio/
auto dividers, lucky-mix loader, context menu) is unchanged, just wrapped in
the virtual rows. Auto-scroll to the next track moves out of useQueueAutoScroll
(which relied on every row being in the DOM) to a virtualizer scroll plus an
exact scrollIntoView on the real target row.

Phase 0 of the queue thin-state plan; no store/persist changes.

* docs(changelog): virtualized queue list (#857)
This commit is contained in:
Frank Stellmacher
2026-05-22 22:17:32 +02:00
committed by GitHub
parent 10c3d9a3ce
commit 06213cb5d8
5 changed files with 83 additions and 31 deletions
+14
View File
@@ -62,6 +62,20 @@ beforeEach(() => {
});
describe('QueuePanel — render surface', () => {
// jsdom has no layout, so the virtualized QueueList sees a 0px viewport and
// renders nothing. @tanstack/virtual-core measures via offsetHeight, so give
// the scroll viewport a height and rows a fixed height — then the virtualizer
// produces rows the way it does in the browser.
let offsetSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
offsetSpy = vi
.spyOn(HTMLElement.prototype, 'offsetHeight', 'get')
.mockImplementation(function (this: HTMLElement) {
return this.classList.contains('queue-list') ? 600 : 52;
});
});
afterEach(() => offsetSpy.mockRestore());
it('renders an empty-queue affordance when the queue is empty', () => {
const { container } = renderWithProviders(<QueuePanel />);
expect(container.querySelector('.queue-panel')).not.toBeNull();