Read-only offline dashboard

Merged
FR
6d ago
+138-3

What & why

Follow-up to #654–#657. When offline, show the user's last-loaded abode instead of the bare offline page.

  • Pages kept for a week (d606a67). The service worker already served cached page HTML when offline, but expired it after one day, so most offline launches landed on /~offline. It now keeps pages for 7 days (still max 32 entries, and still cleared when the signed-in user changes). Item images were already cached, so the grid comes back with its covers.
  • Offline page links to the saved dashboard. If the page asked for was never cached, "Open your saved abode" goes to /dashboard. When that isn't cached either, the offline page simply comes back.
  • Offline banner (6f315ce) across the signed-in app, driven by a new useIsOnline hook (useSyncExternalStore, assumed online during SSR). It says the page is as last loaded and that changes need a connection, and it fires offline_banner_shown.

Checklist

  • User-facing change → added/updated a PostHog event, or N/A: offline_banner_shown
  • User-facing or service change → docs updated, or N/A: README features (PWA line)
  • New error paths report via captureServerException / the error boundary, or N/A: N/A
  • New behavior is covered by tests, or N/A: offline-banner.test.tsx (hidden online; appears and disappears with connectivity; shows on a page loaded offline), the offline page link test, and a story
  • If this fixes a recurring defect, considered a guardrail via the learn skill, or N/A: N/A

Notes

  • Verified against a production build with Playwright, using a throwaway public page that renders the banner, since the dashboard needs a DB:
    • Online, the banner is hidden.
    • Offline, a reload serves the cached copy (same server timestamp), it hydrates, and the banner shows. No console errors.
    • An uncached page offline falls back to the offline page with the new link.
  • Not done: disabling mutation controls while offline. Saves and edits fail with their existing error toasts, which the banner explains.

Generated by Claude Code