I’m building an onboarding flow for a web app and would appreciate a sanity check on recovering unsaved form drafts across browser tabs!

The backend is authoritative for committed answers. In the browser, localStorage holds recovery records, while sessionStorage holds a pointer to that tab’s draft slot.

We use an exclusive Web Lock for each slot. If another live tab holds the lock, the new tab gets a separate slot. The owner keeps its lock while its context can write; ending that context stops further writes and releases ownership.

A controlled test exposed a race:

  1. Tab B reads the snapshot belonging to Tab A.
  2. Before B acquires the lock, A stores a newer draft and ends its context.
  3. B acquires the released lock.
  4. B restores its earlier snapshot and overwrites A’s final draft.

Ownership was exclusive, but the snapshot had been read too early.

This reproduction used native Node Web Locks, in-memory storage and a deliberately delayed lock request. It establishes the adapter’s stale-read path; I’d like to strengthen the browser lifecycle coverage too.

The proposed correction is to acquire ownership before rereading and validating the current snapshot, then restore it while retaining the lock. If the current recovery records cannot be verified, preserve their bytes and allow inspection without writes.

The behaviours we want to preserve are:

  • An ordinary reload recovers the tab’s draft.
  • A duplicated tab cannot overwrite the original tab’s recovery slot.
  • Recovering a pending server operation never automatically resubmits it.
  • Where Web Locks are unavailable, an existing pointed slot stays read-only.

For people who have shipped something similar:

Does holding the lock for the lifetime of the writable context introduce lifecycle problems we should account for? I’m particularly interested in back/forward cache restoration, suspended tabs, crashes and rapid sign-out/sign-in.

Which concrete failure scenarios would you turn into regression tests before trusting this approach?**

Examples of what broke your first implementation would be especially useful. :)

Thank you!