You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Feature: Offline-First Support with Automatic Conflict Resolution
Problem
Eric's workflow spans Ventress (always-on workstation), Snoke (headless server, sometimes offline), and a laptop (frequently offline). If he edits project metadata or saves a new project while offline, those changes are lost or overwritten when he reconnects because the current extension has no local queue or merge strategy.
Proposed Solution
Make the extension offline-first by treating the local globalState + a new Operation Log as the primary data store, and the remote sync backend (Gist/HTTP) as a replication target. Use CRDT-inspired last-writer-wins (LWW) registers per field with vector clocks for deterministic conflict resolution.
Data Model: Operation Log
typeOpType='saveProject'|'deleteProject'|'updateTag'|'updateColor'|'updateNote'|'updateStatus';typeOperation={id: string;// ULID (sortable, unique)machineId: MachineId;timestamp: number;// local clockvectorClock: Record<MachineId,number>;// { ventress: 5, snoke: 2, laptop: 8 }op: OpType;projectId: string;payload: any;};typeLocalLog={schemaVersion: 1;committed: Operation[];// ops that have been syncedpending: Operation[];// ops waiting for networksnapshot: MetadataIndex;// rolled-up state for fast reads};
Sync Protocol
Local Write: Every mutating action appends an Operation to pending and immediately applies it to snapshot. UI updates instantly.
Background Sync: When online, the extension:
Pulls remote ops (or the full index if using Gist).
Merges remote ops into local committed.
Replays any remote ops not yet in local committed onto snapshot.
Pushes local pending ops to remote, clearing them on ACK.
Conflict Resolution: If two machines edit the same field concurrently:
Compare vector clocks. If one dominates, it wins.
If concurrent (neither dominates), use deterministic tie-breaker: higher machineId string lexicographically wins, or prompt user if l13Projects.sync.conflictMode = 'prompt'.
Implementation Plan
Phase 1: Operation Log Engine
Create src/sync/OpLog.ts.
Wrap all existing state mutations (ProjectsState.add, TagsState.update, etc.) to emit Operations instead of writing directly to globalState.
Maintain snapshot in memory for fast reads; persist LocalLog to globalState.
Phase 2: Sync Queue
Create src/sync/SyncQueue.ts.
Manages pending ops. On network up, dequeues in batches of 50.
Implements exponential backoff on push failure (max 5 min).
Adds a status bar item: Projects Sync: ✅ 12 pending / ⏳ Syncing... / ❌ Offline.
Phase 3: CRDT Merge Logic
mergeOps(local: Operation[], remote: Operation[]): Operation[] — sort by vector clock, dedupe by id.
applyOps(snapshot: MetadataIndex, ops: Operation[]): MetadataIndex — pure function, testable.
Unit tests: concurrent color change, concurrent tag add/remove, offline delete vs. online update.
Phase 4: Gist Adapter for Ops
Because Gist is a single file, the op log is appended as a JSON Lines (.jsonl) file: ops.jsonl.
On sync, download the full ops.jsonl, merge, then upload the merged version.
Compaction: every 1000 ops, roll up into a new snapshot.json and truncate ops.jsonl.
Phase 5: Offline UI Indicators
Sidebar tree: projects modified offline show a pencil icon (✏️).
Status bar: click to see pending ops list (read-only quick-pick).
Command: Projects: Force Push Pending Changes (for when you know you're back online).
Acceptance Criteria
Eric can tag 5 projects, change 2 colors, and delete 1 project while on a plane. All changes apply instantly locally.
On reconnecting to Wi-Fi, all changes sync to Ventress and Snoke within 60 seconds without manual intervention.
If Snoke and laptop both change the same project's note offline, the deterministic tie-breaker picks a winner; no data is silently lost.
Sync state is visible at a glance via the status bar icon.
Open Questions
Should we use a real CRDT library (e.g., Yjs) for the op log, or keep it custom and lightweight? Yjs is powerful but may be overkill for ~500 projects.
Gist ops.jsonl compaction: should it happen automatically or via a manual Projects: Compact Sync Log command?
Feature: Offline-First Support with Automatic Conflict Resolution
Problem
Eric's workflow spans Ventress (always-on workstation), Snoke (headless server, sometimes offline), and a laptop (frequently offline). If he edits project metadata or saves a new project while offline, those changes are lost or overwritten when he reconnects because the current extension has no local queue or merge strategy.
Proposed Solution
Make the extension offline-first by treating the local
globalState+ a new Operation Log as the primary data store, and the remote sync backend (Gist/HTTP) as a replication target. Use CRDT-inspired last-writer-wins (LWW) registers per field with vector clocks for deterministic conflict resolution.Data Model: Operation Log
Sync Protocol
Operationtopendingand immediately applies it tosnapshot. UI updates instantly.committed.committedontosnapshot.pendingops to remote, clearing them on ACK.machineIdstring lexicographically wins, or prompt user ifl13Projects.sync.conflictMode = 'prompt'.Implementation Plan
src/sync/OpLog.ts.ProjectsState.add,TagsState.update, etc.) to emitOperations instead of writing directly toglobalState.snapshotin memory for fast reads; persistLocalLogtoglobalState.src/sync/SyncQueue.ts.pendingops. On network up, dequeues in batches of 50.Projects Sync: ✅ 12 pending/⏳ Syncing.../❌ Offline.mergeOps(local: Operation[], remote: Operation[]): Operation[]— sort by vector clock, dedupe byid.applyOps(snapshot: MetadataIndex, ops: Operation[]): MetadataIndex— pure function, testable..jsonl) file:ops.jsonl.ops.jsonl, merge, then upload the merged version.snapshot.jsonand truncateops.jsonl.Projects: Force Push Pending Changes(for when you know you're back online).Acceptance Criteria
Open Questions
ops.jsonlcompaction: should it happen automatically or via a manualProjects: Compact Sync Logcommand?