Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ ActivityEntries are synced to the server when `data-sync-url` is set — all use
| Activity log | **IndexedDB** (primary) + **SQLite** (server) | Same push/pull pattern as threads; cross-user shared history |
| User config | **localStorage** | Tiny, simple, global |

**IDB name:** `annotate-{siteId}` · **Version:** 1
**IDB name:** `annotate-{siteId}` · **Version:** 2 (bumped from 1 to repair crash-corrupted databases — see Key Implementation Notes)

| Object store | Key | Indexes |
|---|---|---|
Expand Down Expand Up @@ -656,6 +656,7 @@ Three demo pages available after `npm start`:
## Key Implementation Notes

- **IIFE pattern** — all client code scoped in `(function() { ... })()` to avoid globals; esbuild wraps this in an outer IIFE via `--format=iife`, making it a nested IIFE (harmless)
- **`openDB` version 2 — crash-recovery schema repair** — `indexedDB.open('annotate-{siteId}', 2)` uses version 2 (bumped from 1); `onupgradeneeded` guards each `createObjectStore` with `!db.objectStoreNames.contains(...)` so it is idempotent and safe to re-run on any old database; the version bump ensures that databases left at v1 with no object stores (caused by a browser crash mid-`onupgradeneeded` on first ever load) trigger `onupgradeneeded` again and receive the correct schema; `db.onversionchange = function() { db.close(); }` releases the connection when a future version is opened in another tab so the upgrade is not blocked — no forced `window.location.reload()` here (a reload call would cascade during Playwright page navigations and was the root cause of the IDB E2E test flake)
- **`siteId`** — read via `document.currentScript.dataset.siteId`; IDB namespace + server/P2P scope
- **`syncUrl`** — read via `document.currentScript.dataset.syncUrl`; `null` = sync disabled
- **`roomId`** — read via `document.currentScript.dataset.roomId`; `null` = P2P disabled; activates `initP2P()` in boot sequence only when `_syncUrl` is absent (mutual exclusivity enforced)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ Point `src` at your deployed server and you're done:
- [ ] Live re-render of the Resolved tab on inbound peer updates (currently re-renders only on tab switch)

**Shipped:**
- [x] IDB schema crash-recovery — `openDB` bumped to version 2; repairs any v1 database that lost its object stores after a browser crash mid-upgrade (fixes "not a known object store name" error in server-sync mode on affected browsers)
- [x] Non-owner Resolve / Un-Resolve in server-sync mode — `PATCH /threads/:id/resolve` now handles both states and has no ownership check; client routes the action through a dedicated `syncResolve()` instead of the ownership-gated `POST /threads` upsert; behaviour is now consistent across all sync modes
- [x] Cross-node anchor + multi-mark highlights — selections that span paragraph or inline-element boundaries now survive page reload (`endXpath` field in anchor); `highlightRange` falls back to per-segment `<mark>` wrapping when `surroundContents` would throw; all mark operations (unwrap, resolve, focus) treat the group atomically
- [x] `data-sync-ms` — configurable server sync poll interval; defaults to 30 s; invalid values fall back with a console warning
Expand Down
2 changes: 1 addition & 1 deletion annotate.min.js

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions assets/js/annotate.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
return new Promise(function (resolve, reject) {
if (!siteId) { reject(new Error('Annotate.js: siteId is required')); return; }

const request = indexedDB.open('annotate-' + siteId, 1);
// Version 2: bumped from 1 so onupgradeneeded always runs for any
// existing v1 database. This repairs the case where a browser crash
// during the very first page load left the database at v1 without
// object stores, causing every subsequent transaction to throw
// "not a known object store name".
const request = indexedDB.open('annotate-' + siteId, 2);

request.onupgradeneeded = function (e) {
const db = e.target.result;
Expand All @@ -58,7 +63,16 @@
}
};

request.onsuccess = function (e) { resolve(e.target.result); };
request.onsuccess = function (e) {
const db = e.target.result;
// Close this connection when another tab opens a higher version so the
// upgrade is not blocked. The page reloads to pick up the new schema.
// Close this connection when another tab opens a higher version so the
// upgrade is not blocked. No forced reload — the caller's error handlers
// deal with subsequent failed transactions gracefully.
db.onversionchange = function () { db.close(); };
resolve(db);
};
request.onerror = function (e) {
console.error('Annotate.js: could not open IndexedDB', e.target.error);
reject(e.target.error);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "annotate-server",
"version": "0.5.15",
"version": "0.5.16",
"description": "Lightweight inline annotation and threaded comments for any web page — added via a single <script> tag.",
"main": "server/index.js",
"engines": {
Expand Down
Loading