Offline files and durability

Finish the storage recovery plan

Add explicit export, reset, cache replacement, failure messages, and a tested recovery path to the field-notes app.

The app is not done when writes succeed. It’s done when losing data, or changing how we store it, is something the user can recover from. Two controls are missing: export and reset.

Export

We said in lesson 1 that browser storage can vanish. Export is the answer. It pulls every note out of IndexedDB, every attachment out of OPFS, and hands the user one file they own.

The simplest portable format is a JSON document. For many large attachments a zip is better, but start with JSON:

const notes = await db.getAll('notes')
const backup = { version: 2, exportedAt: new Date().toISOString(), notes }
const blob = new Blob([JSON.stringify(backup)], { type: 'application/json' })

const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = 'field-notes-backup.json'
link.click()

The version field matters. When the user restores this file a year from now, the import code needs to know which shape it’s reading, like our IndexedDB upgrades check oldVersion.

Restore is the reverse: read the file, validate the version and the fields, then put() each note in one transaction. A backup file is input, exactly like the settings string in module 3.

Reset each owner deliberately

Each storage area has its own way to be cleared:

  • cookies: the server sends Set-Cookie with Max-Age=0 and the matching attributes
  • Web Storage: removeItem() on your known keys, not localStorage.clear()
  • IndexedDB: close every connection, then indexedDB.deleteDatabase('field-notes')
  • Cache Storage: caches.delete() by versioned name
  • OPFS: root.removeEntry(name) for each attachment ID you hold

localStorage.clear() wipes every key on the origin, including ones other scripts own. Deleting a database with an open connection fires onblocked and waits. Use the precise call for each owner and these problems don’t appear.

Two cleanup policies, not one

Never put “clear cached help” and “delete my notes” behind the same button. Cached responses are replaceable. A field note written on a mountain is not.

So the app gets two controls. “Free up space” removes caches and stale drafts, no confirmation needed. “Delete all local data” asks first, offers an export in the same dialog, then removes notes and attachments.

The final matrix

Run the whole app through every boundary this course covered, and note what the user sees at each one:

first visit, reload, second tab, browser restart, offline start, schema upgrade from version 1, full quota, persistence rejected, private window, stale cache name, export, reset, restore.

Save the evidence. When a bug report arrives in six months, this table tells you which boundary the user crossed.

Finish the app with the export and reset controls, then do the full loop: export, delete all local data, confirm the app starts empty, restore from the file, and check every note and attachment is back. Cached help should be gone after reset and download again on its own. Notes survive because you saved them. Help comes back because it’s replaceable. That distinction is the whole course.

Lesson completed