Offline & Local-First Sync
Relevant source files
The following files were used as context for generating this wiki page:
PRKS implements a local-first synchronization system that allows research workflows to continue without a server connection. The system separates user-owned durable state from a disposable download cache to ensure data integrity during offline operations.
Sources: frontend/js/offline-runtime.js:1-15, tests/test_frontend_local_store.py:6-10
Architecture Overview
The synchronization architecture consists of three primary components: the Offline Store, the Local Store, and the Sync Runtime.
- Offline Store (
prks-offline-v1): Stores read-only disposable convenience copies of server data. It acts as a cache for previously opened files and metadata. - Local Store (
prks-local-v1): Manages durable, unsynchronized user edits. This database is physically separate from the cache to prevent data loss during cache clearing. - Sync Runtime: Coordinates the flow of semantic operations between the client and the server, managing retries, conflicts, and reconciliation.
Sources: frontend/js/offline-store.js:18-20, frontend/js/local-store.js:25-28, tests/test_frontend_local_store.py:17-25
Data Flow
This diagram shows how user actions flow through the local-first architecture to reach the server.
Sources: frontend/js/offline-runtime.js:335-375, frontend/js/sync-runtime.js:1-20, docs/local-first-sync.md
Offline Runtime & Connectivity
The Offline Runtime tracks connectivity state and manages the read-through policy. Reachability is determined by the success or failure of actual PRKS requests rather than the browser's navigator.onLine hint.
Connectivity States
| State | Description |
|---|---|
online | The server is reachable and responding to requests. |
offline | The server is unreachable; the system uses cached data. |
reconnecting | A background probe is currently checking server availability. |
Sources: frontend/js/offline-runtime.js:21-23, frontend/js/offline-runtime.js:132-155
Coherence Domains
The system uses "coherence domains" to invalidate groups of related cached records when a mutation occurs. For example, renaming a Concept invalidates the Concept index, the Concept detail, and any cached relatives that display its name.
Sources: frontend/js/offline-runtime.js:46-55, frontend/js/offline-runtime.js:1210-1230
Durable Local Operations
User edits are enqueued as semantic operations in the Local Store. These operations are not replayed HTTP requests but structured intents that the server understands.
Core Envelopes
Every durable operation carries a specific envelope:
op_id: A unique UUID for the operation.entity_id: The ID of the target record (e.g., a Work or Folder).operation: The semantic type (e.g.,SET_WORK_METADATA_FIELD).payload: The actual data change.base_revision: The server revision the client observed before making the edit.
Sources: frontend/js/local-store.js:45-60, tests/test_frontend_local_store.py:46-52
Conflict Management
Conflicts occur when the server's revision for an entity is ahead of the operation's base_revision. The Sync Runtime detects these through 409 Conflict responses or specific protocol error codes like REVISION_CONFLICT.
This diagram illustrates the revision-based conflict resolution flow.
Sources: frontend/js/sync-runtime.js:250-280, frontend/js/work-metadata-editor.js:200-220
Synchronization Protocol
The sync runtime processes the queue of pending operations. It ensures idempotency by reusing the op_id during retries.
Reconciliation Procedure
When an operation is acknowledged (ACK) by the server, the client must reconcile its local cache:
- Advance Generation: The runtime increments the coherence generation for the entity to prevent stale reads from overwriting the update.
- Patch Cache: The specific field or relationship is updated in the Offline Store.
- Retire Operation: Once the cache is successfully patched, the operation is removed from the Local Store.
Sources: frontend/js/offline-runtime.js:335-360, frontend/js/sync-runtime.js:400-420
Reconciler Types
| Reconciler | File | Target Entities |
|---|---|---|
reconcileWorkField | offline-runtime.js | Work metadata, metadata-state projections. |
reconcileWorkTag | offline-runtime.js | Work records, tag assigned/known-absent states. |
reconcileRecentOpen | offline-runtime.js | The Recent browse list. |
reconcileWorkNote | offline-runtime.js | Whole-document Research Notes and private notes. |
Sources: frontend/js/offline-runtime.js:440-480, frontend/js/offline-runtime.js:750-780
Implementation Details
Byte Limits
Durable payloads are bounded by size to prevent excessive storage usage and sync overhead. Limits are measured in UTF-8 bytes rather than character length.
MAX_PAYLOAD_BYTES: 1,048,576 (1 MiB)MAX_ERROR_CHARS: 500
Sources: frontend/js/local-store.js:20-22, tests/test_frontend_local_store.py:100-105
Storage Physical Separation
The physical separation of the databases is enforced at the code level to ensure that clearing the cache (e.g., to free space) never affects the outbox of pending edits.
// local-store.js
const DB_NAME = 'prks-local-v1';
// offline-store.js
const DB_NAME = 'prks-offline-v1';
Sources: frontend/js/local-store.js:18, frontend/js/offline-store.js:18, tests/test_frontend_local_store.py:33-40
Summary
PRKS uses a revision-tracked, semantic synchronization protocol. By separating cached server data from durable local intents, the system maintains high data safety. The use of coherence domains and generation tracking prevents the "stale read" problem where older server responses might undo newer local acknowledgements.