setProgress was called multiple times per swipe burst, each call writing
into readerStore.viewStates[key].progress. ~65 places in the reader
subtree subscribed to useReaderStore() without a selector, so every
setProgress fan-out re-rendered all of them -- even the 51 that didn't
care about progress. On Android release builds this showed up as
Layout = 9.8% and Function Call = 9.6% of main-thread self time in
Chrome DevTools' Bottom-Up profile during a reading session.
Fix:
- New tiny store store/readerProgressStore.ts holds the per-book
BookProgress map. setBookProgress only fires its own subscribers.
- readerStore.setProgress now writes progress to the new store and only
touches bookDataStore for the primary view (secondary parallel views
shouldn't overwrite the shared config).
- readerStore.getProgress is kept as a delegating facade so existing
imperative call sites don't break.
- Components / hooks that genuinely need to react to progress changes
subscribe via the new useBookProgress(bookKey) hook. The handful of
call sites that just want a one-shot read use getBookProgress(key) so
they don't subscribe at all.
- readerStore.clearViewState calls clearBookProgress so the map doesn't
grow unbounded across book opens/closes.
See store/readerProgressStore.ts header for the full rationale.