Skip to content

Harden the offline document mirror: claim persistent storage, and surface a write failure #197

Description

@HMarzban

Goal

Harden the offline document mirror in two small ways. Nothing is broken today. Both items make an existing feature fail loudly instead of quietly.

apps/webapp/src/hooks/useYdocAndProvider.ts creates the mirror:

const persistence =
  typeof window.indexedDB !== 'undefined'
    ? new IndexeddbPersistence(documentId, ydocRef.current)
    : null

That keeps a copy of the document in the browser's own storage. It is why the pad paints before the server answers, and why an open tab survives a network drop.

The two gaps

1. Nothing asks the browser to keep it. Browser storage is best-effort by default, so a device under storage pressure may delete it with no warning and no signal. grep -rn "navigator.storage" apps/webapp/src returns nothing.

Fix: call navigator.storage.persist() once beside the mirror, behind a feature check. WebKit grants this by heuristic and favours apps added to the Home Screen (WebKit, "Updates to Storage Policy", webkit.org/blog/14403). About five lines.

2. Nothing notices when the mirror stops writing. Once the origin hits its quota, IndexedDB writes fail. grep -n "QuotaExceeded" apps/webapp/src/hooks/useYdocAndProvider.ts returns nothing, so the mirror silently stops recording while the reader believes they have an offline copy.

Fix: attach a rejection handler to the IndexeddbPersistence instance and surface QuotaExceededError through the existing status pill. Do not invent a new surface.

How urgent this is

Not very, and that is the honest answer. Both were rated "should fix", not "must fix", in the 2026-08-24 iOS and Android readiness audit. Two reasons:

  1. The server is the source of truth. The mirror is a convenience, not the record.
  2. Losing it costs a user something only when they were offline and held unsynced edits.

Constraints

  • apps/webapp/CLAUDE.md §Slug Page Entry And Skeletons owns the pre-sync gates. Do not clear providerSyncing on the IndexedDB synced event — that breaks the documented split between pre-sync and post-sync states.
  • The mirror is created and destroyed inside the ydoc-owning effect, with persistence teardown before the provider. Keep that order.
  • Do not add a new status state. ProviderStatus is a closed set in types/collab.ts.

Definition of done

navigator.storage.persist() is called once per origin, behind a feature check. A quota failure reaches the status pill instead of being swallowed. Verified on a real device with storage deliberately filled, because a desktop browser will not reproduce eviction.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions