The idea
A browser extension that lets you select text on any page, attach a note to it, and have that annotation reappear as a highlight the next time you visit the same URL. Notes are anchored by surrounding text context rather than fragile DOM paths, so they survive minor site redesigns. Everything is stored in IndexedDB locally and syncs in the background to a self-hosted REST endpoint you configure with a single bearer token.
There is no login screen, no account creation, no analytics ping. Your annotations live in your browser and on your server.
Why build this
Hypothesis is the gold standard for web annotation but its default mode is cloud-hosted and semi-public. Readwise Reader has private highlights but requires a subscription and owns your data. Notion Web Clipper captures whole pages rather than precise inline selections.
The audience is developers, researchers, and writers who annotate web resources as part of their daily workflow and want those notes stored privately, at a URL they control, without paying for an annotation SaaS. The privacy argument is concrete: a researcher annotating academic papers, legal documents, or competitive intelligence may not want that reading history sitting on a third-party server.
Manifest V3 extensions still support chrome.storage.local and chrome.alarms for offline-first storage and reliable background sync. The technical surface is genuinely small — a content script that injects highlight UI, a background worker that drains a sync queue, and a REST backend that is essentially a CRUD store for annotation objects keyed by URL.
Stack sketch
- Extension: Manifest V3 Chrome and Firefox extension; content script in vanilla JS to stay lightweight and avoid CSP issues on strict sites; React only in the options page and popup
- Highlight anchoring:
apache/incubator-annotatorTextQuoteSelector— anchors highlights by quoting surrounding text context rather than a CSS selector; survives most site redesigns and is the same model Hypothesis uses internally - Local storage: IndexedDB via the
idbwrapper; annotations keyed by{url, id}; a separate outbox table for unsynced creates, edits, and deletes - Sync backend: FastAPI + SQLite deployed as a single Docker container;
POST /annotations,GET /annotations?url=...,PATCH /annotations/:id,DELETE /annotations/:id; bearer token in theAuthorizationheader - Sync protocol: optimistic local write first, background sync on network availability via
chrome.alarmsfiring every 60 seconds; last-write-wins on conflict (correct for a single-user tool) - Options page: React + Tailwind; fields for sync endpoint URL and bearer token; toggle for auto-sync; full JSON export
Scope for v1
- Select text on any page and press a configurable keyboard shortcut (default:
Alt+A) or use the right-click context menu to open an annotation input - Saved annotations render as yellow highlights on subsequent visits; hover to see the note text; click to edit or delete
- Offline-first: all reads and writes go to IndexedDB first; sync is best-effort and never blocks the UI
- Options page for the sync endpoint URL, bearer token, and a per-domain blocklist for sites where the content script should not inject
- Export all annotations to a single JSON file
- Deliberately out of scope for v1: tag filtering on annotations, public sharing links, PDF annotation, rich text or images in note bodies, multi-device conflict resolution beyond last-write-wins
Where it could go
A full-text search popup is the highest-leverage follow-on. A keyboard shortcut that opens an extension popup with a search input — querying GET /annotations?q=... against your sync server — turns your annotation history into a searchable index of everything you have ever read and marked. The backend already stores the full note body and the source URL; the search route is a single WHERE note LIKE ? query against SQLite. For a researcher with two years of annotations across hundreds of papers, this feature alone justifies the project.
The second expansion is one-time import from Hypothesis. Hypothesis exposes a full user annotation API at https://api.hypothes.is/api/search?user=.... An import script that fetches your public annotations and writes them into the local database would let existing Hypothesis users migrate without losing their history. The TextQuoteSelector format is identical, so the import is a straight translation with no normalization step.
Watch out for
Manifest V3 service workers are killed by the browser after a few seconds of inactivity — a sync job that relies on the worker staying alive will silently fail on any busy machine. Use chrome.alarms with a 60-second interval to re-wake the worker and drain the outbox. Register the alarm from the onInstalled listener, not from the service worker top level, so it survives extension updates.