A mashup of pinboard.in and ma.gnol.ia.
A liberated, offline-first, zero-maintenance Progressive Web App (PWA) designed to manage tens of thousands of Pinboard bookmarks with "Steel and Stone" reliability. Built to last 30 years without intervention.
- The Bridge (Proxy): A Cloudflare Worker that acts as a transparent, CORS-bypassing conduit to
api.pinboard.in. It injects mandatory headers (User-Agent) to satisfy Pinboard's legacy backend and handles 429 rate-limiting backpressure. - The Engine (Local Storage): SQLite WASM backed by the Origin Private File System (OPFS). All data operations (22,000+ records) happen in a background Web Worker to ensure a guaranteed 60fps UI.
- The Search: Lightning-fast Full-Text Search (FTS5) index that handles punctuation-heavy tags via a dual-query strategy (Exact Match vs. Fuzzy).
- The Universal Fortress (Validation): A language-agnostic Playwright E2E suite that verifies the app's behavior as a black box using a Page Object Model (POM).
- The UI: "Brutally Simple" design philosophy. Single-screen, virtualized scrolling, and instantaneous reactive filtering.
The PWA cannot talk directly to Pinboard due to browser CORS restrictions.
cd proxy
npm install
npx wrangler deployTake note of the deployed URL (e.g., https://pinboard-proxy.your-subdomain.workers.dev).
Ensure the PWA is pointing to your proxy URL.
- Open
pwa/src/main.ts. - Update the
proxyUrlvariable in theSyncOrchestratorclass:private proxyUrl = 'https://YOUR_PROXY_URL_HERE';
cd pwa
npm install
npm run devVisit http://localhost:5173 in your browser.
- Authentication: Get your Pinboard API Token from pinboard.in/settings/password. Format must be
username:HEX_TOKEN. - Initial Sync: Enter your token and hit "Initial Sync". This will download your entire collection and build the local FTS index.
- Search: Just start typing. Results are debounced at 50ms and filtered via SQLite FTS5.
- Add/Delete: Operations are Local-First. Changes appear in the UI instantly and are marked with a
🔄icon until successfully flushed to Pinboard by the background sync loop (60s interval or immediate trigger). - Offline Mode: If you lose connectivity, a red OFFLINE banner appears. You can still search and add/delete bookmarks; they will sync automatically when you reconnect.
Building this fortress required deep-packet inspection of the aging Pinboard v1 backend. Here is the technical ground truth we discovered:
- Purpose: Determine if the local fortress needs a sync.
- Payload: Tiny JSON object:
{"update_time": "ISO8601"}. - The Trap: This timestamp only changes on additions or edits. Deletions are invisible to this endpoint.
- Throttling: Requires a mandatory 5s pause after calling.
- Purpose: Full ingestion or delta updates.
- Parameter:
fromdtused for Fast-Path Deltas. - The Trap: Returns the entire dataset as a single JSON array. For 22,000+ bookmarks, this is a ~15MB stream.
- PWA Fix: We ingest this in 500-record chunks to prevent worker-thread memory pressure.
- Mandatory Identity: Returns
500 Errorif aUser-Agentis missing.
- Purpose: Pinpointing deletions without downloading all 15MB of data.
- The Ghost Signal: On large accounts, the JSON endpoint (
format=json) returns200 OKwithContent-Length: 0. - Quantum Leap: The proxy now strips the JSON request, fetches authoritative XML, and performs a regex-based transformation into JSON.
- Parity Logic: We compare local date-counts vs. server counts. Any mismatch triggers a targeted reconcile.
- Purpose: authoritatively fetch all bookmarks for a specific day.
- Parameter:
dt=YYYY-MM-DD. - Structure: Returns a wrapper object
{"posts": [...]}. (Note: Many wrappers mistakenly expect a raw array).
- Atomic Upsert: We use
/posts/add?replace=yesfor both inserts and updates. - Patience Protocol: We enforce a 5-second mandatory throttle between every consecutive API call. This prevents "Quiet 429" (Silent 0-byte) responses and IP blocks. Startup is immediate, but consecutive rituals are paced with stone-cold patience.
- Zero Network Noise: Once the "Big Pull" JSON is downloaded, the ingestion into SQLite happens entirely offline.
- Event-Loop Yielding: The worker yields to the event loop between 500-record chunks to ensure progress messages are dispatched smoothly and the browser remains responsive.
- Reality: This endpoint is unstable and frequently returns 500s.
- Workaround: We perform a manual loop:
- Update all affected bookmarks locally.
- Push them upstream via
/posts/add. - Globally delete the old tag via
/tags/delete.
- Problem: FTS5 tokenizers often strip punctuation (
.and:), making it impossible to search for tags likesubject:cs.AI. - Fix: The search engine detects a
#prefix and switches from fuzzy FTS5 to a precisetags LIKE ?SQL query, ensuring punctuation is honored.
- Problem: Accounts with 0 bookmarks could deadlock the UI because the sync handshake sentinel was only written if data was ingested.
- Fix: The worker now authoritatively writes the
last_full_sync_timesentinel at the end of every hydration ritual, even for empty datasets, unlocking the UI for new users.
To guarantee the 30-year lifespan, we have armored the codebase with The Universal Fortress—a Playwright-based testing suite (see spec/004-testing-scenarios.md).
- Black Box Testing: Tests target
data-testidattributes, treating the PWA as a black box. This allows the underlying implementation to be rewritten (e.g., in PureScript or Elm) while maintaining the behavioral contract. - The 10 Rituals: We automate 10 critical scenarios including Bootstrap Sync, Offline Persistence, The Dates Hack (Deletion), and 429 Rate-Limit Backoff.
- Run Tests:
cd pwa npm test
Access these commands directly from the Browser Console (F12):
await db.debugClearDb(): Wipes the local OPFS database and resets the application to the login state.await refreshApp(): Forces the UI to re-query the database and re-render.db: TheDatabaseBridgeinstance for manual SQL execution or inspection.
- Zero Reading (0R): No manuals, no nested menus.
- Zero Maintenance (0M): No server-side databases to patch.
- 30 Year Lifespan (30Y): Built on native Web Standards and vendored binaries (SQLite WASM) to survive the decay of the modern web.