# Plan B: Browser-First Sync (Progressive Web App) > **AGENT INSTRUCTION — READ FIRST** > > When you complete a phase or are ready for a code review, you **must** notify the opencode session that created this plan. Use the opencode HTTP API to send a message to session `ses_225742b62ffeOSelYyCe4IPwxL`. > > **Message format:** > ``` > Subject: Plan B Review Ready — Phase X Complete > Body: > - Phase completed: [e.g., Phase 1] > - Files touched: [list of files added/modified] > - Summary: [2-3 sentence description of what was implemented] > - Tests: [which tests were added or updated] > - Blockers: [any issues or decisions needed] > ``` > > **OpenCode API Specification:** > ``` > POST https://api.opencode.ai/v1/sessions/ses_225742b62ffeOSelYyCe4IPwxL/messages > Content-Type: application/json > Authorization: Bearer > > Request body: > { > "role": "assistant", > "content": "Subject: Plan B Review Ready — Phase X Complete\n\nBody:\n- Phase completed: ...\n- Files touched: ...\n- Summary: ...\n- Tests: ...\n- Blockers: ..." > } > ``` > > Wait for a response before starting the next phase unless explicitly told otherwise. ## Goal Extend the existing web application so it works offline, supports concurrent editing, and syncs changes when reconnected. No native client needed — works on macOS, Windows, Linux, iOS, Android via browser. ## Architecture ### Current State (Already Built) - Server: Go HTTP + SQLite + file watcher + WebSocket hub - Browser: Preact + IndexedDB cache + `navigator.onLine` detection - Documents served at `/:path*` with `index.md` default - Real-time updates pushed via WebSocket when files change on disk ### What We Add ``` Browser (existing + new): ├── cache.js (exists) # IndexedDB: docs, content, hashes ├── realtime.js (exists) # WebSocket, offline indicator ├── sync.js (new) # Sync engine: queue, conflict detection, state machine ├── editor.js (new) # ProseMirror or textarea with operational transforms └── sw.js (new) # Service Worker for offline serving Server (existing + new): ├── internal/sync/ (new) │ ├── protocol.go # Browser sync protocol (simpler than Plan A) │ ├── queue.go # Server-side edit queue per document │ ├── conflict.go # Three-way merge for text │ └── broadcast.go # WebSocket broadcast for live collaboration ├── internal/auth/ (new) # Sessions + WebSocket auth └── httpserver/ (existing) ├── handlers.go # Add: POST /api/documents/:path, WebSocket auth └── websocket.go # Extend: handle edit messages ``` ## Sync Protocol v1 (Browser) ### Data Model - **Document**: `{path, content, hash, version}` - **Client Edit**: `{path, baseVersion, newHash, patch}` where patch = diff from base - **Server Ack**: `{path, version, status: accepted|conflict}` - **Server Broadcast**: `{type: update, path, newContent, newVersion, author}` ### Endpoints ``` GET /api/documents # List all docs (cached in IndexedDB) GET /api/documents/:path # Get document content (cached in IndexedDB) POST /api/documents/:path # Save document (requires auth) Body: {content, baseVersion, clientHash} Response: {version, status, conflict?} WS /api/ws # WebSocket (exists, extend for auth + edits) Client → Server: {type: "edit", path, patch, baseVersion} Server → Client: {type: "ack", path, version} Server → Client: {type: "broadcast", path, patch, author} Client → Server: {type: "cursor", path, position} // Phase 2 ``` ### Protocol Flow (Online) ``` 1. User opens /some-doc 2. Browser fetches content, caches in IndexedDB 3. User edits → client generates patch (diff) 4. Client sends WS: {type: "edit", path, patch, baseVersion} 5. Server validates: baseVersion == current version? - Yes: apply patch, increment version, broadcast to other clients - No: return conflict, client must merge 6. Other clients receive broadcast, update editor if viewing same doc ``` ### Protocol Flow (Offline) ``` 1. User goes offline (detected by navigator.onLine + fetch failure) 2. Offline banner appears 3. User edits → changes queued in IndexedDB: `pending_edits` store 4. User navigates → served from IndexedDB cache 5. User comes back online 6. Client replays queue: for each pending edit, POST to server 7. Server responds with ack or conflict 8. Conflicts shown in UI for user resolution ``` ### Conflict Resolution (Text) - Three-way merge using diff3 algorithm - If auto-merge succeeds: apply silently, show subtle notification - If auto-merge fails: show side-by-side diff in UI - User chooses: keep local, keep server, or edit merged result - Markdown-aware: try to merge at paragraph/section boundaries ## Browser Sync Engine ### IndexedDB Schema ```js const DB_NAME = 'mdhub'; const DB_VERSION = 2; const stores = { documents: 'path', // {path, title, hash, version, syncedAt} content: 'path', // {path, content, hash, fetchedAt} pending_edits: 'id', // {id, path, patch, baseVersion, createdAt, retries} sync_state: 'key' // {key: 'lastSync', value: timestamp} }; ``` ### sync.js Responsibilities - `queueEdit(path, patch)`: Add to pending_edits, try to send immediately - `syncPending()`: Replay queue when online, handle acks/conflicts - `applyBroadcast(path, patch)`: Apply real-time update from other user - `detectConflict(base, local, server)`: Run diff3, return merged or null - `saveToIndexedDB(path, content)`: Cache for offline reading ### Service Worker (sw.js) - Intercept navigation requests (`/:path*`) - If offline: serve from IndexedDB cache - If online: network first, update cache in background - Cache static assets (JS, CSS, fonts) for app shell - Enables "Add to Home Screen" PWA behavior ### Editor - Start with `