Skip to content

feat: add offline experience with tiered cache and network monitoring#152

Merged
BrunoCerberus merged 5 commits intomasterfrom
feat/offline-experience
Feb 14, 2026
Merged

feat: add offline experience with tiered cache and network monitoring#152
BrunoCerberus merged 5 commits intomasterfrom
feat/offline-experience

Conversation

@BrunoCerberus
Copy link
Owner

Summary

  • Add persistent disk cache (L2) alongside existing in-memory cache (L1) with 24-hour TTL for offline content availability
  • Add NetworkMonitorService using NWPathMonitor to track connectivity and display an animated offline banner
  • Implement graceful degradation: cached content stays visible on failed refresh, offline-specific error views with wifi.slash icon, and typed PulseError.offlineNoCache error
  • Update documentation (README, CLAUDE.md, AGENTS.md) to reflect the new caching and offline architecture

Details

Tiered Cache

Layer Implementation TTL Survives App Kill
L1 (Memory) LiveNewsCacheStore (NSCache) 10 min No
L2 (Disk) DiskNewsCacheStore (JSON in Caches/) 24 hours Yes

Fetch Flow

  1. L1 hit (non-expired) → return
  2. L2 hit (non-expired) → promote to L1, return
  3. Offline → serve stale from L1/L2; if nothing → PulseError.offlineNoCache
  4. Online → network fetch → write-through L1+L2; on failure → stale L2 fallback

New Files

  • PulseError.swift - Typed offline error enum
  • NetworkMonitorService.swift - Protocol + Live (NWPathMonitor) + Mock
  • DiskNewsCacheStore.swift - Persistent file-based cache
  • OfflineBannerView.swift - Animated offline banner
  • DiskNewsCacheStoreTests.swift - 8 unit tests for disk cache

Modified (key files)

  • CachingNewsService.swift - Tiered L1+L2 cache with fetchWithTieredCache() + offline awareness
  • HomeDomainInteractor+DataLoading.swift / MediaDomainInteractor / SearchDomainInteractor - Offline error detection, preserve data on failed refresh
  • CoordinatorView.swift - Offline banner integration
  • HomeView / MediaView / SearchView - Show cached data over error, offline-specific error views

Test plan

  • DiskNewsCacheStoreTests - write/read, TTL, remove, corrupted file, overwrite (8 tests)
  • Existing CachingNewsService*Tests updated for new init signature
  • HomeViewStateReducerTests and HomeViewModelTests updated for isOfflineError field
  • Manual: Kill app → airplane mode → relaunch → cached content visible from disk
  • Manual: Airplane mode while viewing → banner appears, pull-to-refresh keeps content
  • Manual: First launch on airplane (no cache) → offline error with wifi.slash icon
  • Manual: Restore connectivity → banner disappears, refresh fetches fresh data

🤖 Generated with Claude Code

BrunoCerberus and others added 4 commits February 14, 2026 00:25
Add persistent disk cache (L2) alongside existing in-memory cache (L1),
network connectivity monitoring via NWPathMonitor, and graceful UI
degradation when offline. Cached content remains visible during network
failures, and an animated offline banner appears at the top of the app.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the tiered cache (L1 memory + L2 disk), NetworkMonitorService,
offline banner, graceful degradation, and new key files added in the
offline experience feature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tiered caching (L1 memory + L2 disk) to Media tab via
CachingMediaService decorator, and add offline-aware error handling
to Feed tab with dedicated offline UI (wifi.slash icon, no retry button).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@BrunoCerberus BrunoCerberus force-pushed the feat/offline-experience branch from 8e3bb16 to 6968791 Compare February 14, 2026 14:00
Include ~/Library/Caches/org.swift.swiftpm in the SPM cache path so
binary XCFrameworks (LeapSDK) are preserved across runs. Previously
only package metadata was cached, causing SPM to re-download artifacts
each run and fail with "already exists in file system" errors.

Bumps cache key to v6 to pick up the expanded cache path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@BrunoCerberus BrunoCerberus force-pushed the feat/offline-experience branch from 6968791 to 10fd97a Compare February 14, 2026 14:01
@BrunoCerberus BrunoCerberus enabled auto-merge (squash) February 14, 2026 14:49
@BrunoCerberus BrunoCerberus merged commit dfe8f6c into master Feb 14, 2026
7 checks passed
@BrunoCerberus BrunoCerberus deleted the feat/offline-experience branch February 14, 2026 15:04
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.

1 participant