Extension

Chrome Manifest V3 extension. Version 0.7.3.

Entry Points

File Role
content.ts Injected into every page, captures DOM events
background.ts Service worker, manages sessions and WebSocket
popup.ts Extension popup with tabs (Status, Trails, About)
sidepanel.ts Side panel with trail replay engine
trail-db.ts IndexedDB wrapper for local trail persistence
config.ts Constants (host URL, throttle values)
types.ts Shared TypeScript interfaces
blincr-picker.ts "Blincr this element" picker + create form
capture-policy.ts Capture boundary: deny/nav-only classification, URL auth-param stripping, sensitive-field rules (pure, unit-tested)

Content Script

Initializes once per page (guards with __blincr_injected flag). Registers:

  • Click capture — Records element tag, text, href, CSS selector path
  • Scroll capture — Throttled by SCROLL_THROTTLE_MS, records scroll position
  • Selection capture — Fires on selectionchange, records selected text
  • Clipboard capture — Tracks copy events with copied text content
  • SPA navigation — Listens to popstate and History API monkey-patches
  • Overlay shell — Injects the overlay manager container into the page

Events are sent to background via chrome.runtime.sendMessage as BROWSE_EVENT.

Background Service Worker

Manages session lifecycle:

  • START_SESSION — Creates a new DO session, connects WebSocket
  • JOIN_SESSION — Joins existing session by ID
  • STOP_SESSION — Disconnects WebSocket, saves trail locally
  • GET_STATUS — Returns current session state
  • BROWSE_EVENT — Forwards event to WebSocket (if live) and stores locally
  • GET_TRAIL — Returns current trail from memory
  • REPLAY_COMMAND — Forwards replay controls to side panel
  • ADD_WAYPOINT — Inserts a named waypoint event into the trail
  • GET_FINGERPRINT — Returns persistent install ID from chrome.storage.local
  • GET_OVERLAYS / ACTIVATE_COMMENT_MODE — Overlay plugin coordination
  • CREATE_BLINCR — Authenticated POST /api/user/blincrs (see Blincrs below)

Capture boundary (E12.2)

The cheapest data to protect is data never captured. Every page gets one of three capture modes, decided in capture-policy.ts (pure module, tested in tests/capture-policy.test.ts) and enforced at two layers — the content script attaches no capture listeners at all on protected pages (pause is physical), and background.ts's sendBrowseEvent re-checks the policy so no relay path can bypass it:

  • deny — built-in categories (banking & payments, health portals, government & identity, adult) plus any site the user marked "Never capture here" in the popup. Nothing is captured; during an active session a dismissible "capture paused" badge shows the boundary working.
  • nav_only — personal-workspace domains (webmail, chat: Gmail, Outlook, WhatsApp Web, Slack, Teams, …) where page content is largely other people's data. Only URL + title navigation events are captured — no fingerprint, clicks, scrolls, selections, or clipboard.
  • full — normal capture, hardened: auth-shaped query/fragment params (token, access_token, sig, api_key, code, …) and URL userinfo are stripped from every captured URL; clicks on password / hidden / cc-* / one-time-code fields record only {x, y, redacted: true}; and INPUT/TEXTAREA/contenteditable text never enters click payloads.

Settings live in chrome.storage.local (blincr_capture_policy): user never-capture sites, extra workspace domains, disabled categories. The popup shows the current tab's classification and a one-click "Never capture on " toggle; the background relay picks changes up via storage.onChanged, the content script on next injection.

Blincrs ("blincr this element")

A blincr is a standing page watcher. Signed-in users get cloud blincrs (account-owned, run 24/7 by the watch-worker cron); signed-out users get local blincrs (E14.1 — see below), which run entirely in the browser. Entry point: popup button "👁 Blincr this element"chrome.tabs.sendMessage(tabId, { kind: "ACTIVATE_BLINCR_MODE" })content.tsblincr-picker.ts.

  • Picker (activateBlincrPicker()): hover-highlights the element under the cursor; a click builds a CSS selector (reusing content.ts's buildSelector), guesses a recipe (price-looking text → lt comparator with a threshold at 90% of the parsed value; otherwise changed), and opens a small form (label, comparator, threshold, check frequency).
  • Create: the form sends { kind: "CREATE_BLINCR", input } to background.ts, which calls createBlincrRemote().

Auth bridge (why cookies permission is needed)

The extension has no session of its own — it piggybacks the user's existing blincr.com login. The dashboard's session cookie is HttpOnly + SameSite=Lax, which the browser never attaches to a chrome-extension:// origin's fetch (that origin counts as cross-site). So background.ts reads the cookie value directly via chrome.cookies.get({ url: API_BASE, name: "blincr_session" }) — the cookies permission plus the existing <all_urls> host permission make this a same-product read, not a third-party leak — and forwards it as an X-Blincr-Ext-Session header. getSessionToken() in src/ar.ts accepts either the cookie or this header. If no cookie is found (user not logged in on blincr.com), the watcher is saved locally instead (see below) — the toast explains the trade-off.

Local blincrs (E14.1 — autonomy ladder L0)

See docs/autonomy-ladder.md for the full doctrine. In the picker, every watcher gets an explicit "Run where?" choice — "In this browser (private, only while open)" (default) or "On blincr.com (24/7, needs sign-in)" — with the trade-off shown live underneath. background.ts (handleCreateBlincr) honors that choice: local always saves in-browser; cloud requires sign-in and returns a named error (blincr.watch.not_authenticated) rather than silently downgrading to local. Older callers that send no choice keep the original best-effort-cloud-else-local fallback.

  • Engine: the shared platform-neutral core (extension/src/blincr-engine-core.ts — compare, intervals, regex/JSON-LD extraction; the workers re-export it via src/blincr-engine.ts). Local state transitions (extension/src/blincr-local.ts, pure, unit-tested) mirror the cron runner: baseline-not-change, 6h alert cooldown, sight-lost after 5 consecutive failures.
  • Scheduler: a persistent chrome.alarms alarm (blincr-local-checks, every 5 min) wakes the service worker, which fetches due pages itself (<all_urls> host permission) and honors each watcher's interval bucket.
  • CSS extraction: MV3 service workers have no DOM, so an offscreen document (offscreen/offscreen.html + dist/offscreen.js, offscreen permission, reason DOM_PARSER) answers OFFSCREEN_CSS_EXTRACT requests with DOMParser + querySelector. Regex and JSON-LD recipes run inline.
  • Alerts: chrome.notifications (notifications permission) plus a per-watcher history (last 20) shown in the popup's Trails tab, where local watchers can be paused, resumed, deleted, or upgraded (☁ button → LOCAL_BLINCR_UPGRADE re-creates the watcher on blincr.com and deletes the local copy only on success — a failed upgrade leaves it untouched).
  • Limits, by design: checks run only while the browser is open; no webhooks or agent handoff (those are the cloud tier). The choice UI says exactly that — informed choice, no silent upsell.

Popup

Three-tab interface:

  1. Status — Session controls (start/stop/join), connection indicator, participant list
  2. Trails — Local trail library from IndexedDB with search, folder management
  3. About — Platform info, version badge (__VERSION__, __BUILD_DATE__)

Record agent session (E13.4): a checkbox next to Start explicitly labels the session as agent browsing. The toolbar badge turns purple ("AI"), the owner participant is saved with role: "agent", and the finished trail gets isAgent: true and is auto-filed into the "Agent sessions" folder — it never appears in the popup's recent-trails list. Labeling is explicit only; no cadence heuristics decide for the user.

Side Panel

Opens via chrome.sidePanel.open(). Contains the trail replay engine with play/pause, step forward/back, speed control, skip-to-next-page, and seek.

The library's trail detail view offers Replay, Export (raw .blincr file), For AI (agent-ready blincr-trail-bundle/1 export — JSON with full events plus a Markdown digest, built entirely client-side so it works for every visibility tier; see docs/trail-bundle.md), Upload, and Delete.

Trail Database (IndexedDB)

Database name: blincr-trails, version 1.

Object stores:

  • trails (keyPath: trailId) — Indexes: createdAt, folderId, title, syncStatus
  • folders (keyPath: folderId) — Indexes: parentId, name

Operations: CRUD for trails and folders, search by title/domain/tag, split, merge, export (single or full backup), import.

Overlay Plugin System

Extensible annotation layer managed by overlay-manager.ts.

  • Plugins register via registerPlugin() and provide render, update, remove, and createInputUI methods.
  • Overlays are positioned by viewport coordinates or CSS selector + offset.
  • Persisted as overlay events in the trail.

Built-in plugin: comment — Persistent page annotations with five styles (default, highlight, question, warning, success).

Build

cd extension && node build.js

Uses esbuild. Injects __VERSION__ and __BUILD_DATE__ via define. Output goes to extension/dist/. Manifest version must be bumped on every source change.