One unlocked vault per tab — by design
8 min read By NT²
Opening the same vault in two browser tabs is normal. Letting both tabs write the same on-device SQLite file is not. NT² Vault elects one Writer and keeps every other unlocked tab as a follower.
One unlocked vault per tab — by design
The claim is blunt: for a given vault, only one browser tab may write.
Other tabs may stay unlocked. They may show the list, open items, and react when the Writer changes data. They must not open a second SQLite handle that mutates the same Origin Private File System database, push a competing sync batch, or invent a “background write helper” that bypasses the Writer. Multi-tab convenience is real. Multi-writer local storage is how you corrupt a vault that was supposed to be the user’s source of truth.
That rule is not a UI preference. It is the concurrency boundary of a local-first PWA whose durable store is a file-backed SQLite database in the browser.
The constraint: two writers on one OPFS file
A modern vault app invites people to open more than one tab. Compare two items. Leave settings open while browsing the list. Keep a detail view pinned while searching elsewhere. Browsers make that cheap. Local SQLite on OPFS does not.
The vault database is a real file under the origin’s private file system, opened through a WASM SQLite engine that expects disciplined access. Parallel UI actions inside one tab are already serialized so the virtual file system does not see overlapping mutations. That serialization assumes a single owner of the database connection. A second unlocked tab that also “owns” the vault is a second owner. Two owners mean interleaved commits, competing journals, and failure modes that look like mysterious corruption rather than a clear product error.
Even if the file system somehow survived, the product would still be wrong.
List and detail state would diverge. Tab A saves a title. Tab B still holds yesterday’s row in memory and later overwrites it. Users blame “sync.” The bug was two writers sharing one disk file without a leader.
Cloud sync would double-apply. Optional blind replica sync must leave the edge with one coherent push/pull story per device. Two tabs each running a sync loop against the same vault identity can race cursors, duplicate work, or merge remote batches twice. Multi-tab coordination and cross-device sync are different problems. Collapsing them into “every unlocked tab may write” makes both harder.
Security posture softens under convenience. Unlock material lives in memory for the session. A second write path tempts shortcuts: shared workers that both tabs treat as writers, silent background commits, or “just this one mutation” that skips the Writer check. Those shortcuts become permanent once a release depends on them.
The failure mode is not that browsers cannot host multiple tabs. The failure mode is treating every unlocked tab as an equal database peer.
Local-first means the device owns the truth. It does not mean every document context owns a write lock.
The design: one Writer, followers by BroadcastChannel
NT² Vault separates who may commit from who may observe.
After unlock, the tab asks the browser’s Web Locks API for a lock scoped to that vault’s identity. If the lock is available, the tab becomes the Writer tab. It owns SQLite mutations, domain commits, and the optional cloud sync loop. If the lock is already held, the tab becomes a follower: unlocked for reading, blocked for writing, and subscribed for live updates.
Followers do not open a second write path. They do not invent a parallel repository. They receive the same domain event envelopes the Writer publishes after a successful commit, over a BroadcastChannel named for that vault. Projections on the follower apply those envelopes the way the Writer’s own UI would—updating the paged list window, detail surfaces, and session signals such as lock—without re-authoring the mutation.
Writer election is explicit when roles change. A follower that needs to edit uses Edit in this tab. That transfers the Writer lock and publishes a transfer event so other tabs update their role. There is no quiet promotion on focus alone. Focus is not authority.
Read-only enforcement is not only a banner. Command paths assert that the current tab is the Writer before they mutate. Sync push and full sync run on the Writer only. Followers project; they do not merge remote replica batches on their own. Cross-device sync remains a Writer job on whichever device holds the write role there.
Inside the Writer tab, SQLite access is still serialized. Parallel clicks and overlapping domain calls queue through one lock chain so the WASM VFS sees ordered work. Multi-tab policy and single-tab serialization are complementary: the first chooses which document owns the file; the second keeps that owner honest under concurrent UI.
flowchart LR
W[Writer tab]
F1[Follower tab]
F2[Follower tab]
Lock[Web Lock per vault]
DB[(vault.sqlite in OPFS)]
BC[BroadcastChannel]
W -->|holds| Lock
W -->|commits| DB
W -->|publishes envelopes| BC
BC -->|projects| F1
BC -->|projects| F2
F1 -.->|read-only| DB
F2 -.->|read-only| DB
The mental model stays small: one vault identity, one Writer, many observers.
Desktop shells that embed the same vault UI in a single process do not need the BroadcastChannel transport; an in-process bus is enough when there are not multiple browser documents competing for OPFS. The browser PWA is where multi-tab Writer election matters, because that is where users naturally open several unlocked documents against one origin store.
The trade-off: convenience costs a role
Single-Writer multi-tab is not free.
Users see a read-only banner when a second tab unlocks the same vault. Editing requires switching role or returning to the Writer tab. That is a small product tax compared with silent data loss, but it is still a tax. Engineers must thread Writer checks through every mutation path—including the tempting ones: import, trash empty, attachment replace, settings that write vault profile sections, and sync triggers. A single missed assert recreates the second write path.
BroadcastChannel delivery is best-effort within the origin. Followers dedupe events and ignore their own echoes, but a tab that was backgrounded for a long time may still need careful session handling when the Writer locks or transfers. Event envelopes must stay free of plaintext secrets; followers project facts about what changed, not decrypted payloads they should not retain.
There is also a temptation to “fix” multi-tab by giving every tab a writer and coordinating with optimistic UI only. That moves the conflict into the user’s data. We refuse that bargain. Another temptation is a shared worker that both tabs treat as an invisible co-writer. That merely relocates the second write path. The rule is not “hide the writer.” The rule is “one commit owner per vault.”
What we gain is clearer than what we lose. OPFS SQLite stays coherent. The paged list can update from projected events without each follower reloading the whole table. Optional edge sync has one device-side author. Debugging has a role to inspect: Writer or follower, not “somehow both.”
What we refuse
Architecture becomes clearer when the refusals are explicit.
We refuse a second write path. There is no follower-side repository commit, no silent background mutator, and no “read-only UI but write-capable worker” escape hatch for the same vault identity.
We refuse multi-writer OPFS SQLite. Parallel unlocked tabs do not each open a mutating database connection to the same vault file. Serialization inside one Writer is not a license for two Writers.
We refuse follower-driven cloud sync. Pulling and merging replica batches is a Writer responsibility. Followers project local envelopes; they do not become a second sync engine for the same vault on the same device.
We refuse quiet Writer promotion. Role changes are explicit. A follower does not become Writer merely because it gained focus or the user typed into a disabled field.
We refuse treating BroadcastChannel as a command bus. Followers do not send mutation commands to the Writer over the channel as a substitute for holding the lock. The channel carries committed facts outward so observers stay current.
We refuse to paper over corruption with “sync will fix it.” Multi-tab races are a client concurrency bug. Blind edge replicas move ciphertext; they are not a conflict-resolution service for two local writers that should never have existed.
These refusals keep a browser vault honest when people do the ordinary thing: open another tab.
One Writer is how local-first stays true
A private vault on the device has to feel like an app, including multi-tab browsing, without pretending the browser is a multi-master database. Electing a Writer, keeping followers read-only, and fan-out through BroadcastChannel is how NT² Vault draws that line. The durable file has one owner. Observation can be shared. Mutation cannot.
For the local-first product stance behind that choice, read Why host a heavy server when a PWA can do everything locally?. For why the vault database itself lives in OPFS rather than IndexedDB, see Why our vault SQLite database lives in OPFS, not IndexedDB. For how the interactive list stays a paged window instead of a full-table load while followers project updates, see Never load the whole vault into Svelte state.
If that concurrency model fits how you want a private vault to work, you can try NT² Vault or read more at nt2.me.
Last updated 2026-08-29