feat: offline article caching + smart pull-to-refresh reshuffle
Some checks failed
CI / Build · macos-14 · Debug (push) Has been cancelled
CI / Build · macos-15 · Debug (push) Has been cancelled
CI / Build · macos-14 · Release (push) Has been cancelled
CI / Build · macos-15 · Release (push) Has been cancelled

Offline caching:
- BackgroundRefreshManager.preCacheArticles() fetches top article per story
  (score ≥60, up to 20 stories) and stores as CachedArticle in SwiftData
- Runs after every BG fetch AND after every foreground sync via StoryStore
- Articles are available in ArticleReaderView offline without ever opening them

Pull-to-refresh reshuffle when nothing new:
- loadStories(refresh:) now returns newCount (genuinely new story IDs)
- If server returns 0 new stories, feed merges in any BG-cached stories not
  already in the live feed, then re-sorts by signal score (reshuffle)
- seenSnapshot is always cleared so everything re-ranks fresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Robin Kutesa
2026-06-21 03:01:44 +03:00
parent a1d7979048
commit 6677033faa
3 changed files with 68 additions and 12 deletions

View File

@@ -251,11 +251,20 @@ struct SignalFeedView: View {
.animation(.snappy, value: readStories.count)
.onAppear { captureSeenSnapshot() }
.refreshable {
// Reshuffle: clear the seen snapshot so all stories re-rank by signal
// score, and any stories cached by background refresh float to the top.
seenSnapshot = []
seenTracker.ids = []
await store.loadStories(refresh: true)
let new = await store.loadStories(refresh: true)
// Nothing new from server pull in any stories cached by background
// refresh that aren't already in the live feed, then reshuffle.
if new == 0 {
let liveIds = Set(store.stories.map(\.id))
let extra = cachedStories
.filter { !liveIds.contains($0.id) }
.map(StorySummary.init(cached:))
if !extra.isEmpty {
await store.mergeStories(extra)
}
}
}
}