feat: background prefetch of topic details in lists - #390
Open
MufanQiu wants to merge 3 commits into
Open
Conversation
Add an opt-in 'Open Cached Topics Instantly' setting (off by default) under Reading. When enabled, opening a topic reads the on-disk cache first and shows it immediately (no network wait), then silently refreshes the cache in the background so the next visit is up to date. Pull-to-refresh still fetches live content on demand. - PagingDataSource gains `injectCachedResponse` to populate items from an already-fetched response without a network round-trip. - TopicDetailsView orchestrates the two stages: a local-cache read (fast, no network on the Rust side) shows content instantly; on a cache miss it falls back to the normal load. Only applies to a plain first-page browse (the case that has a cache key); jump-to-floor / only-post / author-only are excluded. - Reuses the existing `local_cache` service flag; no proto or Rust changes.
When 'Open Cached Topics Instantly' is on, optionally prefetch topic details for the topics the user is viewing so opening them is instant via the cache-first path. Opt-in and off by default. Throttling is intentionally conservative to avoid looking like a crawler: - only fires after scrolling stays idle for a tunable interval (no prefetch while flinging through the list), - only the first N currently-visible topics per batch, - a PrefetchGate actor caps concurrency and spaces requests with jitter. Foreground taps never go through the gate and use a higher QoS, so prefetch never delays what the user actually does. Idle delay, batch size, concurrency and interval are all adjustable at runtime from settings.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in “cache-first” open path for topic details (show cached content immediately, then refresh in the background) and layers an optional background prefetcher onto topic lists/search/hot lists to warm the topic-details cache for faster navigation.
Changes:
- Introduces a cache-first initial-load path in
TopicDetailsView, powered by a newPagingDataSource.injectCachedResponsehelper. - Adds a background topic-details prefetcher (
PrefetchGate+prefetchTopicDetailsview modifier) and wires it into multiple topic list UIs. - Adds new Reading preferences + tunables for cache-first and prefetch behavior, plus corresponding zh-Hans localizations.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| app/Shared/Views/TopicSearchView.swift | Enables list-level topic-details prefetch in search results when settings allow. |
| app/Shared/Views/TopicRowView.swift | Reports row visibility to support list-level prefetch behavior. |
| app/Shared/Views/TopicListView.swift | Enables list-level topic-details prefetch in forum topic lists. |
| app/Shared/Views/TopicDetailsView.swift | Implements cache-first initial load + silent background cache refresh. |
| app/Shared/Views/TopicDetailsPrefetcher.swift | New prefetch infrastructure: throttling gate + visibility-driven batching. |
| app/Shared/Views/PreferencesView.swift | Adds UI toggles/sliders/steppers for cache-first + prefetch tunables. |
| app/Shared/Views/HotTopicListView.swift | Enables list-level topic-details prefetch in hot topics list. |
| app/Shared/Storage/PreferencesStorage.swift | Persists new cache-first + prefetch preferences/tunables via @AppStorage. |
| app/Shared/Models/PagingDataSource.swift | Adds injectCachedResponse to populate paging state from a cached response. |
| app/Shared/Localization/zh-Hans.lproj/Localizable.strings | Adds zh-Hans translations for new Reading settings labels. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+67
to
+71
| .environment(\.reportTopicVisible) { id in | ||
| guard enabled else { return } | ||
| if !visibleIDs.contains(id) { visibleIDs.append(id) } | ||
| scheduleAfterIdle() | ||
| } |
| guard let topic = items.first(where: { $0.id == id }) else { return false } | ||
| return !topic.hasShortcutForum && !requestedIDs.contains(id) | ||
| } | ||
| .prefix(batchSize) |
Comment on lines
+858
to
+862
| func onInitialAppear() { | ||
| guard cacheFirstApplicable else { | ||
| dataSource.initialLoad() | ||
| return | ||
| } |
Background prefetch went through the same path as a real visit: it flooded the browsing history and overwrote the unread-replies baseline, making never-opened topics look read (dimmed subject, no new-replies badge). Add TopicDetailsRequest.background: the load still refreshes the cache (its whole point) but skips insert_topic_history. The details prefetcher sets it, and also skips MNGA mock topics.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
When "Open Cached Topics Instantly" (#389) is on, optionally prefetch the
details of topics the user is currently looking at, so tapping into them is
instant. Opt-in and off by default, under Reading.
How
Prefetch requests are just plain background
topicDetailsloads whose responsesare discarded — the Rust service writes the cache on success, and #389 reads it
on open. Throttling is intentionally conservative to avoid looking like a
crawler / triggering NGA rate limiting:
(a debounce timer reset on each visibility change), so flinging through a list
prefetches nothing.
PrefetchGateactor caps concurrency and spaces requestswith a randomized delay.
higher QoS, so prefetch never delays opening a topic.
Idle delay, batch size, concurrency and interval are all adjustable at runtime
from settings. No proto or Rust changes.
Notes
This is naturally more request-heavy than the other changes, which is why it is
off by default, gated behind the cache-first toggle, conservatively throttled,
and fully tunable. Happy to adjust the defaults or add further safeguards.
Testing
nothing; opening a prefetched topic is instant; foreground taps and paging
stay responsive while prefetch runs; tunables take effect live.