Skip to content

Commit b9e1f66

Browse files
Merge pull request #1 from ChrisBrooksbank/claude/graceful-offline-degradation-o63Cg
Degrade gracefully offline: replace big browser error with subtle indicator
2 parents 7d6dfa2 + e157807 commit b9e1f66

3 files changed

Lines changed: 35 additions & 1 deletion

File tree

app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import BottomNav from '@/components/BottomNav';
44
import BackupProvider from '@/components/BackupProvider';
55
import FontSizeProvider from '@/components/FontSizeProvider';
66
import ThemeProvider from '@/components/ThemeProvider';
7+
import OfflineIndicator from '@/components/OfflineIndicator';
78
import './globals.css';
89

910
const geistSans = Geist({
@@ -42,6 +43,7 @@ export default function RootLayout({
4243
<BackupProvider />
4344
<FontSizeProvider />
4445
<ThemeProvider />
46+
<OfflineIndicator />
4547
<main className="flex-1 overflow-y-auto scroll-smooth">{children}</main>
4648
<BottomNav />
4749
</body>

app/sw.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const serwist = new Serwist({
1414
precacheEntries: self.__SW_MANIFEST,
1515
skipWaiting: true,
1616
clientsClaim: true,
17-
navigationPreload: true,
17+
navigationPreload: false,
1818
runtimeCaching: defaultCache,
1919
});
2020

components/OfflineIndicator.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
5+
export default function OfflineIndicator() {
6+
const [offline, setOffline] = useState(false);
7+
8+
useEffect(() => {
9+
setOffline(!navigator.onLine);
10+
const handleOffline = () => setOffline(true);
11+
const handleOnline = () => setOffline(false);
12+
window.addEventListener('offline', handleOffline);
13+
window.addEventListener('online', handleOnline);
14+
return () => {
15+
window.removeEventListener('offline', handleOffline);
16+
window.removeEventListener('online', handleOnline);
17+
};
18+
}, []);
19+
20+
if (!offline) return null;
21+
22+
return (
23+
<div
24+
role="status"
25+
aria-live="polite"
26+
className="flex items-center justify-center gap-1.5 bg-elevated px-3 py-1 text-xs text-muted border-b border-default"
27+
>
28+
<span className="h-1.5 w-1.5 rounded-full bg-yellow-500" aria-hidden="true" />
29+
Offline — notes save locally
30+
</div>
31+
);
32+
}

0 commit comments

Comments
 (0)