Switching the annotation/bookmark sidebar to a flat virtualized list eliminates
the per-item layout reads that caused multi-second jank when toggling tabs on
books with hundreds of notes.
A. Virtualize the list with react-virtuoso
- Flatten group headers + notes into a single FlatBooknoteRow array.
- Embed an OverlayScrollbars instance inside the tab so scrollbar styling
is preserved while Virtuoso owns the viewport (same nested pattern as
TOCView).
- Track the parent scroll-container's height with ResizeObserver to give
Virtuoso a bounded viewport.
- Replace the per-item useScrollToItem (which called getBoundingClientRect
and closest() on every BooknoteItem on every progress tick — O(n) sync
reflow on 1000+ items) with a single virtuosoRef.scrollToIndex driven by
nearestCfi.
B. Stabilize derivations with useMemo / useCallback
- filteredNotes, sortedGroups, flatItems, nearestCfi all useMemo so an
unrelated config change (e.g. viewSettings autosave) no longer triggers
a full sort + group rebuild.
- handleBrowseBookNotes is now useCallback so BooknoteItem's React.memo
can hit on prop equality.
C. Memoize BooknoteItem
- Wrap the component in React.memo. With stable item / onClick references
from the parent, re-renders triggered by sibling progress updates no
longer cascade across every visible row.
- Cache marked.parse(item.note) and dayjs(item.createdAt).fromNow() in
useMemo. marked is the dominant per-render cost for note rows.
- isCurrent moves to a useMemo over isCfiInLocation; the per-item
scrollIntoView is removed since BooknoteView now drives scrolling.
useScrollToItem is intentionally left intact — SearchResults still uses it
and its smaller list does not exhibit the same jank.
* feat: add empty state hints and loading indicators for annotations, bookmarks, notes, font import, and Moon+ Reader import
- BooknoteView: show 'No annotation yet' / 'No bookmark yet' when empty
- Notebook: show 'No note yet' when no notes/excerpts exist
- Annotator: add loading overlay with spinner during Moon+ Reader import
- mrexpt: yield to event loop every 5 entries to keep spinner animating
- CustomFonts: show in-place loading card during font import, spinner transitions to font name without layout jump
* fix(ui): respect e-ink overlay styling and drop dead className branch
- Annotator: use modal-box on the mrexpt import overlay so eink picks up
the no-shadow + 1px border override automatically.
- CustomFonts: collapse importing-card clsx ternary whose branches were
identical into a flat className.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add H and L keyboard shortcuts for back and forward navigation, consistent with Vimium
* Fallback bookmark text to page number if current page has no text, closes#51
* Add delete and edit buttons for highlights and booknotes in the sidebar, closes#57
* Each book now has its own sidebar default tab