I built OMNI — one web app for documents, whiteboards, databases and a link graph. The editors
were the easy part: I used ready-made ones, BlockNote for documents and tldraw for boards. The hard part was a promise
I made to myself: a user must never lose a note. Not after a crash, not after two open tabs, not after a bad sync.
Here is what that promise turned into in code.
1. One door for every write
All user data lives in IndexedDB (via Dexie), and there is exactly one place that writes it: a VaultRepo module.
Features never touch the database directly. That sounds bureaucratic, but it means every guarantee below is
implemented once, not once per feature.
localStorage is used for one thing only — the UI theme.
2. Every save is atomic and versioned
Each write is a single transaction. Before an entity is overwritten, the previous version goes to its revision list —
up to 30 revisions per entity. Restoring is just another write through the same door.
3. Delete means "move to trash"
There is no hard delete in normal use. Deleted items go to a trash and can be restored; emptying the trash is a
separate, explicit action. Old work that's still useful goes to an archive instead — out of sight, still searchable.
4. Two tabs, no lost edits
Two open tabs editing the same note is the classic way to lose data in a local-first app. OMNI detects the conflict and
keeps both versions instead of letting the last writer win silently.
5. Backup is one JSON file
The whole vault exports to one JSON file and imports back. Boring, and exactly what you want on a bad day.
6. Schema changes only through migrations
The database schema changes only by a new Dexie version with a migration. No "just add a field" — that is how old
data quietly breaks.
7. Tests guard the promise
All of the above is covered by the test suite, and the rule is simple: if a change breaks a data-safety test, the
change is wrong.
Keeping it fast
Boards, documents and the graph are heavy, so each one is a lazy chunk: you only download tldraw when you open a board.
The core (src/core/) has no React and no editor code, and features don't import each other — which keeps the startup
bundle small and the architecture honest.
AI without giving it the keys
OMNI also exposes an MCP server, so an AI assistant can search the vault, read notes and propose changes. Changes the
owner didn't ask for come in as drafts that wait for approval, and every edit still goes through the same door — soft
delete and revision history included — so even an over-eager AI can't destroy anything for good.
Takeaways
- Pick one write path and make it the only one.
- Make deletion reversible by default.
- Treat "two tabs" as a real user, not an edge case.
- Use battle-tested editors; spend your effort on the data layer.
I'm Gordey Fisun, I build websites, online stores, Telegram bots and AI assistants for businesses —
gordey.pro.


Top comments (0)