Compare commits

...
3 Commits
Author SHA1 Message Date
github-actions[bot] 1813c5b1a6 chore: bump version to 0.7.2 2026-03-08 23:42:16 +00:00
Frederic CabassutandClaude Opus 4.6 54416e392c fix: add error resilience for IDB failures and API error feedback
loadNotes() crashed entirely if IndexedDB was unavailable because
getAllNotes() was called outside try/catch. Non-OK API responses
(401, 500) were silently ignored across loadNotes, createNote, and
sync operations — users saw empty data with no feedback.

Changes:
- Wrap all IDB calls in try/catch so the app works without IndexedDB
- Show toast on non-OK API responses in loadNotes and createNote
- Redirect to /login on 401 responses
- Make sync pull/push resilient to IDB unavailability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:41:45 +01:00
github-actions[bot] 8a21950e53 chore: pin docker-compose to v0.7.1 [skip ci] 2026-03-08 23:37:55 +00:00
5 changed files with 53 additions and 21 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
services:
crumbs:
image: ghcr.io/bretzel-app/crumbs:0.7.0
image: ghcr.io/bretzel-app/crumbs:0.7.1
container_name: crumbs
ports:
- "3000:3000"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "crumbs",
"private": true,
"version": "0.7.1",
"version": "0.7.2",
"type": "module",
"license": "MIT",
"author": "Bretzel (https://bretzel.app)",
+22 -5
View File
@@ -63,9 +63,13 @@ function isNetworkError(err: unknown): boolean {
export async function loadNotes(filter: NoteFilter = 'all') {
// Load from IDB first for instant display
const cached = await getAllNotes();
if (cached.length > 0) {
notes.set(cached);
try {
const cached = await getAllNotes();
if (cached.length > 0) {
notes.set(cached);
}
} catch {
// IDB unavailable — continue with server fetch
}
currentFilter.set(filter);
notesLoaded.set(true);
@@ -79,8 +83,16 @@ export async function loadNotes(filter: NoteFilter = 'all') {
currentFilter.set(filter);
// Update IDB with server state
await clearIdbNotes();
await Promise.all(data.map((n) => putNote(n)));
try {
await clearIdbNotes();
await Promise.all(data.map((n) => putNote(n)));
} catch {
// IDB write failed — server data is still in the store
}
} else if (res.status === 401) {
window.location.href = '/login';
} else {
showToast('Failed to load notes', 'error');
}
} catch {
// Offline — IDB data already loaded above
@@ -100,6 +112,11 @@ export async function createNote(note: NoteCreate): Promise<Note | null> {
await putNote(created);
return created;
}
if (res.status === 401) {
window.location.href = '/login';
} else {
showToast('Failed to save note', 'error');
}
return null;
} catch (err) {
if (isNetworkError(err)) {
+28 -13
View File
@@ -29,7 +29,12 @@ export function getSyncStatus(): SyncStatus {
* Push local changes to server.
*/
async function pushChanges(): Promise<boolean> {
const queue = await getSyncQueue();
let queue;
try {
queue = await getSyncQueue();
} catch {
return true; // IDB unavailable — nothing to push
}
if (queue.length === 0) return true;
try {
@@ -78,27 +83,37 @@ async function pushPendingAttachments(): Promise<void> {
* Pull remote changes and merge with local state.
*/
async function pullChanges(): Promise<Note[]> {
const lastSync = (await getMeta('lastSyncTimestamp')) || '0';
let lastSync = '0';
try {
lastSync = (await getMeta('lastSyncTimestamp')) || '0';
} catch {
// IDB unavailable — pull everything
}
try {
const res = await fetch(`/api/sync?since=${lastSync}`);
if (!res.ok) return [];
const remoteNotes: Note[] = await res.json();
const localNotes = await getAllNotes();
const localMap = new Map(localNotes.map((n) => [n.id, n]));
for (const remote of remoteNotes) {
const local = localMap.get(remote.id);
if (local) {
const merged = mergeNotes(local, remote);
await putNote(merged);
} else {
await putNote(remote);
try {
const localNotes = await getAllNotes();
const localMap = new Map(localNotes.map((n) => [n.id, n]));
for (const remote of remoteNotes) {
const local = localMap.get(remote.id);
if (local) {
const merged = mergeNotes(local, remote);
await putNote(merged);
} else {
await putNote(remote);
}
}
}
await setMeta('lastSyncTimestamp', String(Date.now()));
await setMeta('lastSyncTimestamp', String(Date.now()));
} catch {
// IDB write failed — server data still returned for store use
}
return remoteNotes;
} catch {
return [];
+1 -1
View File
@@ -222,7 +222,7 @@
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/docs/DEPLOYMENT.md" target="_blank" rel="noopener">Docs</a></li>
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/LICENSE" target="_blank" rel="noopener">License</a></li>
</ul>
<span class="footer-version">v0.7.1</span>
<span class="footer-version">v0.7.2</span>
</div>
</footer>