D-3 — hide-shorts no longer drains a whole channel/search in one burst. The
infinite-scroll trigger is derived from the FILTERED list while loadMore()
appends to the UNFILTERED one, so a shorts-heavy feed with hide-shorts ON kept
the trigger hot (the filter stripped each fetched page back to ~nothing) and
auto-fetched every page to the end of the continuation. Now it keeps loading
while pages are productive (the filtered list grows) and stops after 3
consecutive pages that add nothing visible; toggling hide-shorts off resumes.
Applies to both SearchScreen and ChannelScreen; normal (non-filtered) infinite
scroll is unaffected (productive pages keep the counter at 0).
PrefsWriter completeness — the four SP stores the vc=88 sweep didn't cover
(Enrichment, SearchCache, Playlists, FeedCache) now serialize their writes too,
so no store is left on a bare sp.edit().apply(). EnrichmentStore is the one that
mattered: put() runs 8-wide concurrently from the feed-enrichment fan-out
(Semaphore(8) on Dispatchers.IO), so its apply() ordering genuinely raced — a
real M-2 instance the audit's four-store scope missed. FeedCacheStore has no
StateFlow (caller-owned state) so it uses the captured-arg form; ordering is
safe because its sole writer (the feed VM) serializes save/clear from one
refresh coroutine.
Verified: headless compileDebugKotlin green on the straw-build image. The vc=88
PrefsWriter concurrency was cleared by an adversarial review before this builds
on it.