perf(frontend): patch the stores instead of invalidating them
An event carries what a consumer needs so it never has to invalidate. - `library-store` answers `TrackPlayCountChanged` by patching one track, replacing the tracks array (consumers key memoized caches on its identity) while sharing every unchanged Track — instead of discarding four collections and refetching 25 MB per song. - `playlist-store` answers `PlaylistTracksChanged` by refetching the one playlist the event names, plus the summaries, since `UpdatedAt` is a sort key. 2 668 kB and 172 ms for one heart, against 2.0 kB. It falls back to a full invalidate only where a patch cannot be shown to be equivalent: no id, a cold cache, an unknown id, or a fetch already in flight. And a store with no subscriber fetches nothing — the singleton's constructor used to put every track of every playlist on the path to first paint for a view the user might never open. - `library-store` guards every fetch with a cache generation and holds the request itself instead of deriving a promise from subscriber notifications, which fixes the library-filter race and the never-settling waiter together: they are the same bug seen from either end. - `explore-cache`'s two art caches are bounded, sharing one exported cap constant — the artist photo's data URL is held by both, so capping either alone frees nothing at all and reads as a fix that did not work. - `search-store` deliberately does *not* coalesce its notify: deferring makes a subscriber that unsubscribes synchronously after a `setTerm` miss the notification entirely, which is a semantic change rather than an optimisation, and this is the store on the keystroke path. - `selection-controller` retains its keys across a refetch rather than clearing them, since they are file paths and those survive one, and `getSelectedKeysOrdered()` gains an early exit. It stays a walk of the list: an index goes stale on any re-sort, re-filter or refetch while a file path survives all three, and 3 ms does not buy a silently mis-ordered queue insert.
This commit is contained in:
@@ -52,6 +52,19 @@ class SearchStore {
|
||||
return () => this.subscribers.delete(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliberately *not* microtask-coalesced, unlike every other store
|
||||
* here (perf.p3 asks for it, and it is wrong about this one).
|
||||
*
|
||||
* Two reasons. Deferring makes an unsubscribe that happens
|
||||
* synchronously after a set drop the notification entirely, which
|
||||
* is a semantic change, not an optimisation — `view-stores.test.ts`
|
||||
* pins both halves. And this store is on the keystroke path, where
|
||||
* the batching the audit says would hide the cost is Lit's, not
|
||||
* ours: the `requestUpdate()`s are already coalesced one layer
|
||||
* down, so the microtask buys nothing and costs a frame of term
|
||||
* staleness in the one place a frame is visible.
|
||||
*/
|
||||
private notify(): void {
|
||||
this.subscribers.forEach((callback) => callback());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user