Blueprint · Local-first Software
Local-first Notes App
A notes app that works offline first, syncs later, and handles conflicts gracefully.
ResearchingHardIndexedDBSyncOfflineEncryptionCRDTsConflict Resolution
Overview
A notes app where the local device is the source of immediate truth. The app should feel fast offline, sync later, and make conflicts understandable rather than scary.
Problem
Cloud-first notes are convenient until the network disappears or sync behavior becomes mysterious. Users need confidence that their writing is available locally and that sync will not silently destroy work.
Core users
- Writers who work offline
- Developers keeping technical notes across devices
- Students with unreliable connectivity
- Privacy-conscious users who want local-first behavior
MVP scope
- Create, edit, delete, and search notes locally
- Store notes in IndexedDB
- Register device identity
- Push local changes to a sync API
- Pull remote changes with a sync cursor
- Use last-write-wins conflict handling for MVP
- Show sync state clearly in the UI
Non-goals
- Do not build collaborative editing in MVP
- Do not implement full CRDTs before the simpler sync model is understood
- Do not hide conflicts behind vague success states
- Do not require the server for local note editing
Core system components
- Local database layer using IndexedDB
- Sync client that tracks local changes and server cursors
- Sync API for push and pull
- Conflict detector for changed documents
- Optional encryption layer for note documents
- Device registry
- Sync status UI
Suggested architecture
- Frontend: browser app stores notes, notebooks, local changes, and sync cursors in IndexedDB.
- Backend: sync service accepts pushed changes, stores encrypted note documents or deltas, and returns changes since a cursor.
- Database: Postgres stores user/device metadata, note document metadata, changesets, and conflict markers.
- Storage: object storage can hold larger encrypted note blobs or attachments later.
- Conflict handling: last-write-wins in MVP, with later migration to CRDTs or field-level merge strategies.
- Auth: user sessions plus device IDs so sync behavior can be traced per device.
Data model
- Note: id, notebookId, title, body, updatedAt, deletedAt, version
- Notebook: id, ownerId, name, createdAt
- Device: id, userId, name, publicKey, lastSeenAt
- SyncCursor: id, deviceId, lastChangeId, updatedAt
- ChangeSet: id, noteId, deviceId, operation, payload, createdAt
- Conflict: id, noteId, localChangeId, remoteChangeId, resolution, createdAt
API design
- POST /api/sync/push - send local changes to the server
- GET /api/sync/pull - fetch changes after the device cursor
- POST /api/devices - register a device
- GET /api/changes - inspect recent changes for debugging
- POST /api/conflicts/:id/resolve - mark conflict resolution
Key technical challenges
- Offline edits across multiple devices
- Sync conflicts and trustworthy resolution UI
- Device identity and cursor tracking
- Encryption without breaking search
- Efficient delta sync
- Clear UI around sync state and pending changes
Tradeoffs
- Use last-write-wins first to ship a working sync loop before CRDT complexity.
- Keep search local in MVP so offline behavior remains strong.
- Encrypt document bodies later if it complicates early debugging too much.
- Treat conflict logs as product UI, not just backend state.
Security considerations
- Authenticate every sync request.
- Scope notes, changes, devices, and conflicts to the owning user.
- Consider client-side encryption for note bodies.
- Avoid logging raw note content in backend logs.
- Support device revocation.
- Rate-limit sync pushes to prevent accidental loops.
Scaling path
- Start with document-level changes.
- Add attachments and object storage.
- Add CRDT-backed collaborative notebooks.
- Add background sync and push notifications.
- Add import/export and encrypted backups.
Observability
- Sync duration, pushed change count, pulled change count, and conflict count.
- Per-device last-seen and failed sync logs.
- Client-side debug panel for pending changes and current cursor.
- Server logs with request IDs but without note body content.
Future features
- CRDT merging
- Shared notebooks
- Encrypted attachments
- Markdown backlinks
- Import/export formats
- Sync diagnostics page