Skip to content

feat: background prefetch of topic details in lists - #390

Open
MufanQiu wants to merge 3 commits into
BugenZhao:mainfrom
MufanQiu:feat/topic-details-prefetch
Open

feat: background prefetch of topic details in lists#390
MufanQiu wants to merge 3 commits into
BugenZhao:mainfrom
MufanQiu:feat/topic-details-prefetch

Conversation

@MufanQiu

Copy link
Copy Markdown

Depends on #389 (cache-first). This branch is stacked on top of #389, so
the diff currently also shows the cache-first commit. Once #389 is merged,
this diff collapses to just the prefetch commit (feat: background prefetch of topic details in lists). Please review/merge #389 first. Reviewing only
the second commit here is the cleanest way to see this change in isolation.

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 topicDetails loads whose responses
are 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:

  • Idle-triggered: only fires after scrolling has stayed idle for a moment
    (a debounce timer reset on each visibility change), so flinging through a list
    prefetches nothing.
  • Bounded: only the first N currently-visible topics per batch.
  • Throttled: a PrefetchGate actor caps concurrency and spaces requests
    with a randomized delay.
  • Never blocks the user: foreground taps don't go through the gate and use a
    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

  • Built and ran on a physical device.
  • Verified: prefetch only kicks in after scrolling settles; flinging triggers
    nothing; opening a prefetched topic is instant; foreground taps and paging
    stay responsive while prefetch runs; tunables take effect live.

MufanQiu added 2 commits June 30, 2026 12:28
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.
Copilot AI review requested due to automatic review settings June 30, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 new PagingDataSource.injectCachedResponse helper.
  • Adds a background topic-details prefetcher (PrefetchGate + prefetchTopicDetails view 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants