Where your data lives
Ask most developers where a web app's data lives and they'll point at a server. Open a Vibes app and the honest answer is: on the device you're holding. Each named database is a local replica — an IndexedDB store on the page — and that replica is the database. Reads and writes hit it directly, with no round trip and no spinner. Sync to the server happens in the background, after the fact.
That one inversion changes how everything downstream behaves — offline, conflicts, permissions, what "revoke" means. So we drew the whole path a write takes, start to finish, in a single picture:
Revocation stops the feed — it doesn’t reach back
When access is removed, the server stops routing new data to that device. Changes already synced there stay on it — “stops receiving,” not remote-erase.
access.js on the server, then fanned out or converged server-wins.Follow the numbers. A put() lands in the local replica immediately (①) — the person sees their change at once, optimistically. Reads are served from that same replica first. Only then, in the background, does the change sync across the network (②) to the server, which is the authoritative copy.
Get posts like this in your inbox
One email field. Real updates. No algorithm required.
The server is where access.js runs — and the only place it runs
On ingest, the server evaluates your app's access.js (③). This is the load-bearing fact about permissions in Vibes: access functions run only on the server. The client can't evaluate them, can't grant itself channels, and can't bypass the decision. A write that looks successful locally hasn't been accepted yet — acceptance happens here.
From the decision, two things can happen:
- Accept → the change fans out to every other replica whose access still matches. That's how a teammate's edit appears on your screen in real time, no configuration required.
- Reject → the server's authorized version quietly overwrites your local optimistic one. Server-wins, and — this is the part that surprises people — silently.
You don't catch a rejected write
Because the rejection is silent and server-authoritative, the right thing for app code to do is nothing. You don't wrap put() in a try/catch to surface a permission error, and you don't hand-roll a retry. If the rule spelled out a reason, the platform shows it to the person in a toast — but a reason-less refusal (like a database-level ACL) just converges quietly, with no popup at all. Either way your code doesn't catch it, so keep the UI driven by the live data and let the converged state speak for itself. Your job is to gate the surface up front (offer the button only to people who can use it), not to handle denials after the fact. Transport hiccups and offline are handled for you too: those writes queue and retry automatically; only sync being behind is what "offline" means.
Revocation stops the feed — it doesn't reach back
The same model makes one more thing precise. When you remove someone's access, the server stops routing new data to their device from then on. It does not reach back and erase what already synced there. The honest mental model is a downloaded file: revoking stops future delivery, but a copy someone already received is theirs to keep. So the promise we make in the product is always "stops receiving," never "loses every copy." Design your sharing flows for that reality — only share data a recipient may retain.
Why we built it this way
Local-first is what makes a Vibes app feel instant and keep working when the network doesn't. Putting the database on the device means the UI never waits on a server to paint. Keeping access.js on the server means that speed never costs you a security boundary — the authoritative decision is always made somewhere the client can't touch. Silent server-wins convergence keeps the contract simple: the server is the source of truth, and the app doesn't carry error-handling for a decision it doesn't own.
The full reference — offline, conflicts, blocking, and the exact rules for each — lives in the Local-first data & sync guide.
Build something that works offline by default
Every Vibes app is local-first from the first line. Describe it and start.
Start building →