---
id: TASK-21
title: Offline-first support with IndexedDB persistence and Service Worker
status: Done
assignee: []
created_date: '2026-02-18 19:39'
labels:
  - feature
  - offline
  - infrastructure
dependencies: []
priority: high
---

## Description

<!-- SECTION:DESCRIPTION:BEGIN -->
Added full offline-first capability to rSpace apps. Automerge documents and sync state now persist to IndexedDB via a new OfflineStore class, enabling instant load from cache, offline editing, and automatic incremental merge on reconnect. A Service Worker caches the app shell (HTML/JS/WASM) for loading without network.

## Changes
- **lib/offline-store.ts** (new): IndexedDB wrapper storing Automerge doc binary + SyncState per community slug, with debounced saves
- **lib/community-sync.ts**: Integrated offline persistence — initFromCache(), #scheduleSave(), #persistSyncState(), saveBeforeUnload(), infinite reconnect with capped backoff
- **website/sw.ts** (new): Service Worker — cache-first for hashed assets, network-first for HTML, skip WS/API
- **website/canvas.html**: SW registration, OfflineStore init, offline/online status UI, beforeunload save
- **vite.config.ts**: build-sw plugin to produce dist/sw.js without content hash
- **tsconfig.json**: Excluded sw.ts from main typecheck (WebWorker types)
<!-- SECTION:DESCRIPTION:END -->

## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 IndexedDB persists Automerge doc binary per community slug
- [ ] #2 Cached content loads instantly on page refresh (before WebSocket connects)
- [ ] #3 Offline editing works — shapes can be created/moved/deleted without network
- [ ] #4 Changes merge automatically on reconnect via Automerge sync protocol
- [ ] #5 Service Worker caches HTML/JS/WASM for offline app shell loading
- [ ] #6 Reconnect retries indefinitely (30s max backoff) when offline store is present
- [ ] #7 beforeunload saves pending changes immediately
<!-- AC:END -->

## Final Summary

<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented offline-first support for rSpace. Created OfflineStore (IndexedDB wrapper) and Service Worker. Integrated into CommunitySync with debounced saves after every doc mutation, SyncState persistence for incremental reconnect, and infinite retry with capped backoff. Canvas UI shows offline/online status. Build produces dist/sw.js alongside main app. Pushed to Gitea main branch (6b06168).
<!-- SECTION:FINAL_SUMMARY:END -->
