Skip to content

[Frontend] Optimize service worker caching strategy for offline-first experience#355

Open
CodingBabe-1 wants to merge 1 commit into
AetherEdu:mainfrom
CodingBabe-1:feat/optimize-sw-caching-offline-first
Open

[Frontend] Optimize service worker caching strategy for offline-first experience#355
CodingBabe-1 wants to merge 1 commit into
AetherEdu:mainfrom
CodingBabe-1:feat/optimize-sw-caching-offline-first

Conversation

@CodingBabe-1

Copy link
Copy Markdown

📋 Summary

This PR overhauls the Workbox service worker caching strategies to deliver a truly offline-first experience for AetherMint learners, matching all six acceptance criteria defined in #276. It also enhances the offline indicator banner to surface available cached content alongside queued sync actions.


🔄 Caching Strategy Changes (frontend/public/sw.js)

# Resource Type Old Strategy New Strategy Rationale
7 HTML (Navigation) StaleWhileRevalidate NetworkFirst (5s timeout) Learners always see the freshest page content when online; cache fallback when offline
3 API GET (general) NetworkFirst (3s timeout) StaleWhileRevalidate Serves cached data instantly while refreshing in background — snappy UX
4 Images Grouped with scripts/styles CacheFirst + 50 MB size limit Prevents large images from exhausting device quota; CacheableResponsePlugin ensures only 0/200 responses cached
2 API (courses/lessons/progress/analytics) StaleWhileRevalidate StaleWhileRevalidate Unchanged — already correct
5 Scripts/Styles/Fonts/Manifest CacheFirst (30d) CacheFirst (30d) Unchanged — already correct
6 Next.js static chunks CacheFirst (1y, immutable) CacheFirst (1y, immutable) Unchanged — already correct

Key Implementation Details

  • SW_VERSION bumped from v4v5; the existing activate handler automatically purges all aethermint-*-v4 caches on activation
  • cacheableResponse module destructured from self.workbox for consistency with existing import pattern
  • Images now have a dedicated aethermint-images-v5 cache, separate from aethermint-static-v5 for scripts/styles/fonts
  • Network timeout for HTML navigation set to 5 seconds — long enough to survive spotty 3G, short enough to avoid sluggish UX
  • All internal catch handler section labels updated from 7a/7b/7c/7d8a/8b/8c/8d to match renumbering

🟢 Enhanced Offline Indicator (frontend/src/components/PWA/OfflineIndicator.tsx)

Before (v4)

  • Amber banner with pending queue count only
  • Generic "cached content remains available" message

After (v5)

  • New green "available content" chip showing count of cached courses ready for offline study
  • Adaptive banner text dynamically covers four states:
    1. Pending + Cached: "You're offline. 3 pending actions will sync... 2 cached courses are available for offline study."
    2. Pending only: "You're offline. 3 pending actions will sync when you're back online."
    3. Cached only: "You're offline — 2 cached courses are available and progress will sync automatically..."
    4. Neither: "You're offline — cached content remains available and progress will sync automatically..."
  • Reads from existing useOfflineCourses hook — zero new data fetching, zero performance impact when online
  • Both chips (pending + content) are hidden during the clear-confirm flow and error states

✅ Acceptance Criteria Mapping

Criterion Status Implementation
HTML: network-first with cache fallback NetworkFirst strategy with 5s timeout on navigation routes
Static assets (JS/CSS/fonts): cache-first CacheFirst (30d) — already existed, unchanged
API responses: stale-while-revalidate All GET /api/* use StaleWhileRevalidate (specific read-only pattern already did)
Images: cache-first with size limit CacheFirst + maxAgeSize: 50 MB + CacheableResponsePlugin({ statuses: [0, 200] })
Cache versioning and cleanup SW_VERSION bumped to v5; activate handler evicts all old-version caches
Offline indicator with available content Green content-count chip + adaptive banner messaging

🧪 Test Results

New/Updated Tests (OfflineIndicator.test.tsx)

  • 10/10 OfflineIndicator tests pass (3 new + 7 existing)
    • renders nothing when online
    • renders the banner with role="alert" when offline
    • shows a pending-count chip when there are queued offline actions
    • shows available cached content count when courses are available offline ← NEW
    • shows both pending and available counts when both are present ← NEW
    • hides the banner once dismissed and persists the choice
    • honours a custom storage key
    • does not throw when localStorage is unavailable
    • Clear → Confirm two-step wipes offline data and shows success copy
    • surfaces a clear-failure message inline when clearAll rejects

Pre-existing Failures (Unrelated)

  • ⚠️ 5 serviceWorkerRegistration.test.ts failures — jsdom environment limitation (no real Service Worker API), unchanged by this PR

Lint & TypeCheck

  • ✅ Lint: no new warnings introduced (only pre-existing no-console in hooks)
  • ✅ TypeCheck: all type errors are pre-existing (none in files modified by this PR)
  • ✅ Contracts: unchanged — no Rust/Soroban modifications

📁 Files Changed

File Δ Description
frontend/public/sw.js +91 / -16 Caching strategy overhaul; v4→v5; image size limit; module destructuring
frontend/src/components/PWA/OfflineIndicator.tsx +34 / -0 Available content count + green chip + adaptive messaging
frontend/src/components/PWA/__tests__/OfflineIndicator.test.tsx +38 / -0 3 new test cases for content count display

Total: 3 files, +163 / -16 lines


🔍 Reviewer Notes

  1. Image cache is now separate — images move from aethermint-static-v4 to aethermint-images-v5. Old cached images in the static cache will be cleaned up automatically by the activate handler.
  2. CacheableResponsePlugin is destructured from self.workbox alongside routing, strategies, etc. — consistent with the existing pattern (code review feedback addressed).
  3. Catch handler labels (7a8a, etc.) updated to match the renumbered strategy listing in the header comment.
  4. The 5-second network timeout is intentional: long enough to survive 3G dead zones typical for learners in low-connectivity regions, short enough to avoid feeling sluggish.
  5. useOfflineCourses reads from IndexedDB on mount, but the OfflineIndicator only renders when isOnline === false — zero performance impact when connected.

🚀 How to Test

# 1. Build the frontend
cd frontend
NEXT_PUBLIC_STELLAR_RECEIVER_ADDRESS=GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWNA npm run build

# 2. Start the production server
npm start

# 3. In Chrome DevTools:
#    - Open Application → Service Workers
#    - Verify "AetherMint SW v5 activating" appears in console
#    - Check "Offline" checkbox
#    - Refresh the page — it should load from cache
#    - Navigate to cached courses — they should be available

# 4. Run PWA tests
cd frontend
NEXT_PUBLIC_STELLAR_RECEIVER_ADDRESS=GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWNA \
  npx jest --testPathPattern='OfflineIndicator' --verbose

Closes #276

…erience

Overhaul Workbox caching strategies per AetherEdu#276 acceptance criteria:
- HTML navigation: NetworkFirst (was StaleWhileRevalidate) with 5s timeout
- API GET: StaleWhileRevalidate (was NetworkFirst) for all API reads
- Images: separated into own CacheFirst with 50 MB size limit
- SW version bumped v4 -> v5 for automatic cache eviction

Enhance OfflineIndicator to show available cached course count
alongside pending sync actions with adaptive banner messaging.

Closes AetherEdu#276
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.

[Frontend] Optimize service worker caching strategy for offline-first experience

1 participant