Skip to content

fix(ui): virtualize the long track lists and label every plugin world - #908

Merged
InstaZDLL merged 6 commits into
mainfrom
fix/plugin-badge-virtual-lists
Oct 18, 2026
Merged

InstaZDLL merged 6 commits into
mainfrom
fix/plugin-badge-virtual-lists

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Two frontend defects, plus one more found while fixing them.

The plugin store showed a raw world name

The store card kept its own copy of the world labels, which had no canvas entry, so a canvas plugin showed waveflow:canvas/v1. Both cards now share one PluginWorldBadge (source, metadata, ui, canvas) and cannot drift apart again. All 17 locales already had the labels.

Long track lists rendered every row

The project rule is virtual scroll against the page scroller for every long list. Five views broke it:

  • Liked tracks: virtualized.
  • Genre and Year detail: virtualized, through their shared table. A genre can be a third of a library, so "bounded by its entity" did not hold.
  • Artist detail: virtualized, since a prolific artist can have hundreds of tracks.
  • History: virtualized by day, so each day stays a whole section and its sticky header still docks and gets pushed off by the next day. Infinite scroll and the month timeline are unchanged.

A shared usePageVirtualizer hook holds the logic, and re-measures when content above the list changes size (a bio expanding, albums loading). Album detail is left as is, since an album is short by nature.

Each view keeps its row markup, double-click, Enter and Space play from the right index, the keyboard and right-click menus, the now-playing highlight, and its loading and empty states.

Also fixed

  • History: jumping to a month scrolled the window, which never moves in this app, instead of the page.
  • Liked: the unlike button only appeared on mouse hover. It now also shows on keyboard focus and on devices without hover, with a focus ring (local CodeRabbit finding).
  • Docs: invariants.md and ui.md describe the rule and the hook. A false claim that the statistics rows were virtualized is removed (they are top-10 lists).

Checks

  • bun run typecheck, bun run lint and prettier --check pass.
  • Local CodeRabbit review: one finding, applied.
  • Not yet tried in the app (another session held the dev port).

To check in the app:

  • Liked: scroll a long list to the end, play from a deep row, use Tab then Enter and Shift+F10.
  • A large genre and a year.
  • An artist: collapse and expand the bio, then scroll to the end.
  • History: scroll back several pages, check that day headers stay sticky, and jump to a month from deep down.
  • The plugin store: a canvas plugin's badge.

@coderabbitai review

Summary by CodeRabbit

  • Nouvelles fonctionnalités
    • Le défilement virtuel est étendu aux listes de titres aimés, à l’historique d’écoute et aux pages de détails des genres, des années et des artistes. Seuls les éléments visibles sont rendus, pour une navigation plus fluide dans les longues listes.
    • Dans les réglages des plugins, le badge du monde du plugin affiche les libellés traduits lorsqu’ils sont disponibles, et conserve le nom d’origine dans les autres cas.
    • Les actions de retrait des titres aimés restent accessibles au clavier et sur les appareils sans survol.

…ed list

The store card kept its own copy of the world-to-label table, written
before the canvas world existed, so a canvas plugin in the store showed
the raw waveflow:canvas/v1 string while the installed list already
called it Canvas. Both cards now render one shared PluginWorldBadge, so
a world added later is labelled in both places or in neither.
The liked view mapped every liked track into the DOM, artwork and all,
and nothing bounds how many tracks someone likes. The rows now go
through a virtualizer driven by the page scroller, like the playlist
and library tables, with the same 56 px row pitch.

The scrollMargin machinery those tables each copy is now one hook,
usePageVirtualizer, which also re-measures when the view's content
above the list changes size, not only the list or the scroller.

Rows keep their markup, keys, double-click and Enter / Space play,
the keyboard menu, the hover-only unlike button and the now-playing
highlight; the divider moves from divide-y onto each row but the last.
These detail tables mapped every row on the grounds that one entity
bounds its own list. That holds for an album, not for a genre, which
can be a third of the library, nor for a year or a prolific artist.
DetailTrackTable (genre and year) and ArtistTrackTable now go through
usePageVirtualizer; the album view stays in flow.

Row markup, keys, play-from-here indexing, the playing indicator, the
like toggle, the album link and the keyboard menu are unchanged.
The history appends a page of 100 plays each time the sentinel comes
into view and never drops one, so a long scroll back kept every row it
passed mounted. It now virtualizes days against the page scroller: each
day stays a whole, measured section, so its sticky header still docks
under the top bar and is pushed off by the next day's, and the
infinite-scroll sentinel still follows the list.

Jumping to a month also meant to scroll back to the top, but it
scrolled the window, which never scrolls; it now scrolls the page.
The invariant exempted every detail view of one entity, which covered
genres and artists whose lists are not small. Only the album stays in
flow now. Name usePageVirtualizer as the way a new list gets the page
scroller, say how the history keeps its day groups, and drop the claim
that statistics rows are virtualized: they are top-ten lists.
It only appeared on mouse hover, so a keyboard user tabbing to it, or a
touch screen with no hover, had an invisible control. It now also shows
when the row holds focus and on devices without hover, with a visible
focus ring, like the other row actions.
@InstaZDLL InstaZDLL added scope: frontend React/Vite frontend (src/) scope: docs Docs, README, assets scope: plugins Plugin runtime, SDK, store, and bundled plugins type: fix Bug fix size: xl > 500 lines labels Oct 18, 2026
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: InstaZDLL/WaveFlow/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 8f8d89f9-674f-41a4-803a-b40b7997eda9
📥 Commits

Reviewing files that changed from the base of the PR and between 0dd8d15 and 0799463.

📒 Files selected for processing: 10
  • docs/architecture/invariants.md
  • docs/features/ui.md
  • src/components/views/ArtistDetailView.tsx
  • src/components/views/GenreDetailView.tsx
  • src/components/views/HistoryView.tsx
  • src/components/views/LikedView.tsx
  • src/components/views/settings/PluginStoreCard.tsx
  • src/components/views/settings/PluginWorldBadge.tsx
  • src/components/views/settings/PluginsCard.tsx
  • src/hooks/usePageVirtualizer.ts

Included review availability: This review used your included allowance. 5 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 8 reviews per hour.

📝 Walkthrough

Le changement ajoute la virtualisation des listes longues dans plusieurs vues, avec un hook commun lié au défilement de page. Il extrait aussi l’affichage des mondes de plugins dans un composant partagé utilisé par les cartes de paramètres.

Changes: Virtualisation des listes

Layer / File(s) Summary
Hook commun et périmètre documenté
src/hooks/usePageVirtualizer.ts, docs/architecture/invariants.md, docs/features/ui.md
usePageVirtualizer configure useVirtualizer avec le scroller de page, une marge mesurée, un overscan par défaut de 8 et une clé d’élément facultative. Les documents précisent quelles listes sont virtualisées et quelles listes restent non virtualisées ou limitées.
Groupes journaliers de l’historique
src/components/views/HistoryView.tsx
HistoryView utilise le scroller de page pour les sauts vers un mois. HistoryDayList virtualise les groupes journaliers, mesure leur hauteur et ne rend que les groupes visibles, avec deux groupes de marge.
Titres par artiste, genre et année
src/components/views/ArtistDetailView.tsx, src/components/views/GenreDetailView.tsx
Les tableaux rendent les lignes visibles à partir d’une hauteur estimée de 56 px. Ils positionnent chaque ligne selon les mesures du virtualiseur.
Pistes aimées
src/components/views/LikedView.tsx
LikedView délègue le rendu à LikedTrackList, qui virtualise les lignes et conserve les interactions de lecture, de menu, de navigation et de retrait. Le bouton de retrait est aussi visible au focus et sur les appareils sans survol.

Badges des mondes de plugins

Layer / File(s) Summary
Badge partagé dans les cartes de plugins
src/components/views/settings/PluginWorldBadge.tsx, src/components/views/settings/PluginStoreCard.tsx, src/components/views/settings/PluginsCard.tsx
PluginWorldBadge traduit quatre préfixes WIT connus. Sans correspondance, il affiche le nom WIT brut. Les deux cartes utilisent ce composant à la place de leur badge local.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant HistoryView
  participant usePageVirtualizer
  participant HistoryDayList
  participant DayGroup
  HistoryDayList->>usePageVirtualizer: Configurer la virtualisation des jours
  usePageVirtualizer-->>HistoryDayList: Retourner les éléments visibles et la hauteur totale
  HistoryDayList->>DayGroup: Rendre les groupes visibles
Loading
Merge Risk: ⚪ Minimal · up to 07994

No actionable issue remains before merge. The planned in-app checks are still useful for confirming scrolling and keyboard behavior.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 8 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
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.
Title check Passed Le titre est concis, descriptif et conforme au format Conventional Commits. Il couvre les deux changements principaux : la virtualisation des listes longues et l’affichage des mondes des plugins.
Description check Passed La description est détaillée, structurée et couvre les changements, les raisons, les tests automatisés et les vérifications manuelles prévues. Elle signale clairement que les tests dans l’application …
Full details: Description check

Explanation

La description est détaillée, structurée et couvre les changements, les raisons, les tests automatisés et les vérifications manuelles prévues. Elle signale clairement que les tests dans l’application n’ont pas encore été exécutés. Les sections formelles Checklist et Linked issues ne sont pas reprises, mais leur absence ne rend pas la description incomplète.

Full details: Docstring Coverage

Explanation

Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 8 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@InstaZDLL InstaZDLL added this to the v2.0.0 milestone Oct 18, 2026
@InstaZDLL InstaZDLL self-assigned this Oct 18, 2026
@InstaZDLL
InstaZDLL merged commit e5931da into main Oct 18, 2026
16 checks passed
@InstaZDLL
InstaZDLL deleted the fix/plugin-badge-virtual-lists branch October 18, 2026 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: docs Docs, README, assets scope: frontend React/Vite frontend (src/) scope: plugins Plugin runtime, SDK, store, and bundled plugins size: xl > 500 lines type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant