Extension structure
extension/
manifest.json MV3 manifest (permissions, entry points)
build.js esbuild bundler (ESM for bg/popup/sidepanel, IIFE for content)
src/
config.ts API_BASE, WS_BASE, storage keys, scroll throttle
types.ts BrowseEvent, SessionStatus, RuntimeMessage, overlay types
content.ts Injected into every page — event capture + replay
background.ts Service worker — session management, WebSocket, replay engine
popup.ts Extension popup UI (Session, Trails, About tabs)
sidepanel.ts Side panel — live trail tree + library manager
trail-db.ts IndexedDB wrapper for local trail CRUD
plugins/
overlay-manager.ts Plugin registry, overlay state, positioning, drag, SPA nav
comment.ts Comment plugin — render, edit, delete, 5 style themes
popup/
popup.html Popup markup
popup.css Popup styles
sidepanel/
sidepanel.html Side panel markup
sidepanel.css Side panel styles
dist/ Built bundles (.gitignored)
Manifest permissions
activeTab,tabs,webNavigation— tab tracking and navigation eventsstorage— persisting session state and faviconsscripting— programmatic content script injectionalarms— keepalive and reconnect timerssidePanel— Chrome side panel API<all_urls>host permission — content script injection on any site
Content script (content.ts)
Injected as IIFE into every page. Uses a __blincr_injected guard to prevent double injection. Captures seven event types:
- click — Captures
clientX/Y,pageX/Y, CSS selector, tag name, text content, and semantic context (ARIA labels,data-testid, closest heading, link href, element role). Alt+Click is intercepted to activate comment creation instead. - scroll — Throttled to 200ms. Records
scrollX,scrollY, viewport height, and scroll max. - selection — Fires on
mouseupafter 300ms debounce. Captures highlighted text (2-5000 chars), truncated to 500 chars in the event payload. Includes selector and semantic context. - clipboard — Fires on the
copyDOM event. Captures the copied text with the same metadata as selection events. - navigation — Monitors SPA route changes by patching
history.pushStateandhistory.replaceState, pluspopstateevents. Triggers overlay show/hide for the new URL. - page_info — Sent on page load. Includes page title, a structural fingerprint (hash of headings + external resources), favicon URL, and load time from the Navigation Timing API.
- overlay — Plugin-driven persistent UI components placed on the page (see Overlay Plugin System below).
The content script also handles replay: applyReplayEvent() processes incoming click, scroll, and overlay events from viewers, using a cascade of selector strategies (exact selector, data-testid, ARIA label, text content match) to find target elements.
Overlay plugin system (plugins/)
A generalized architecture for placing persistent UI components on web pages, tracked as trail events and visible during replay.
Plugin interface (OverlayPlugin): Each plugin provides render(), update(), remove(), and createInputUI(). Plugins are registered during initBlincr() via registerPlugin(). The registry is a compile-time pattern — esbuild bundles plugin imports into the IIFE, so no dynamic loading is needed.
Overlay manager (plugins/overlay-manager.ts): Sits between the plugin registry and the DOM. Manages:
activeOverlaysstate map (overlayId to instance + DOM element)- Position resolution: viewport-ratio positioning or element-anchored via CSS selector with pixel offset
- Drag-to-reposition with automatic event emission
- SPA navigation: hides overlays for the old URL, shows/loads overlays for the new URL
- Connector lines: dashed SVG line from comment bubble to anchored element with a blue dot at the anchor point
- Page load restoration: requests materialized overlay state from the background script via
GET_OVERLAYS
Event shape: A single "overlay" event type with a pluginId discriminator in the payload:
payload: {
pluginId: "comment"
action: "create" | "update" | "delete"
overlayId: UUID (stable across CRUD operations)
url: page URL
position: { strategy, vpX, vpY, selector?, offsetX?, offsetY? }
data: plugin-specific (e.g. { text, style, authorName, authorId })
}
CRUD is append-only: create/update/delete are separate events pointing to the same overlayId. The background materializes current state by replaying events in order. During trail replay, overlay events are forwarded to the content script's handleOverlayEvent() which delegates to the plugin's render/update/remove.
Comment plugin (plugins/comment.ts)
First overlay plugin instance. Provides styled, draggable comment bubbles on any web page.
Activation: Alt+Click on any page element or empty space. Also available via "Add Comment" button in the popup (sends ACTIVATE_COMMENT_MODE to the content script).
Creation flow: Alt+Click shows an inline form with a textarea, a style picker (5 colored circles), and Cancel/Post buttons. Enter submits, Shift+Enter for newlines, Escape cancels.
5 visual themes:
- default — white background, blue left border accent
- highlight — amber background, yellow accent (important notes)
- question — light blue background, blue accent (questions for team)
- warning — light red background, red accent (issues/concerns)
- success — light green background, green accent (approvals)
Editing: Click the edit button (✎) to transform the comment body into an inline textarea. Enter saves, Escape reverts.
Deletion: Click the delete button (✕) to emit a delete event. The overlay fades out over 200ms.
Drag: The comment header is a drag handle. Mousedown starts drag mode, mousemove repositions, mouseup emits an update event with the new viewport-relative position.
Background service worker (background.ts)
Central orchestrator running as a Chrome service worker. Key responsibilities:
Session lifecycle — createSession() POSTs to the backend, opens a WebSocket, sets the badge to green "ON", and injects the content script into the active tab. stopSession() saves the trail to IndexedDB, closes the WebSocket, clears alarms, and resets state. joinSession() connects as a viewer.
WebSocket relay — Maintains a persistent WebSocket to the SessionCoordinator DO. Queues events when disconnected. Exponential backoff reconnect (2^n seconds, max 30s) via chrome.alarms.
Event routing — Content script events arrive via chrome.runtime.onMessage with kind: "BROWSE_EVENT". The background enriches them with timestamp and participant ID, appends to the local trail, broadcasts to side panel ports, and sends over the WebSocket.
Replay engine — Full replay system with play/pause/stop, prev/next, skip-to-next-site, speed control (1x/2x/4x), and seek. Opens a dedicated replay tab and sends events to the content script for visual replay. Detects stale pages by comparing structural fingerprints.
IndexedDB bridge — Exposes all trail-db.ts operations via message passing (DB_LIST_TRAILS, DB_GET_TRAIL, DB_DELETE_TRAIL, DB_EXPORT_TRAIL, etc.) so the popup and side panel can access IndexedDB through the service worker context.
Side panel communication — Maintains an array of connected blincr-sidepanel ports. Broadcasts trail events, sync messages, replay state, and stale page warnings.
Popup (popup.ts + popup.html)
Three-tab popup accessible from the extension icon:
- Session — Start a co-browse session (owner) or join one (viewer) by session ID. Shows active session info, participants, and trail count.
- Trails — Shows total saved trail count and the 5 most recent trails as mini cards (title, page/event count, duration, date). "Open Trail Manager" button opens the full side panel.
- About — Version info, build date, server host, install ID. Connection test button. Captured event type reference.
Side panel (sidepanel.ts + sidepanel.html)
Two-view side panel opened from the popup or Chrome's side panel toggle:
Live view — Hierarchical domain-grouped trail tree with expandable domains and pages. Shows event counts, timestamps, and favicon icons. Includes a full replay control bar (play/pause, prev/next, next-site, speed selector, scrubber, waypoint jump dropdown). Stale page detection toasts with skip/dismiss actions.
Library view — Lists all locally saved trails as cards with sync status badges (local/synced/modified), domain favicons, tags, and folder labels. Supports search, folder filtering, and bulk export/import. Trail detail view with editable title/tags/description, tree preview, and actions: replay, export (.blincr), upload to server, delete.
Trail storage (trail-db.ts)
IndexedDB database blincr-trails with two object stores:
- trails — Keyed by
trailId(UUID). Indexed oncreatedAt,folderId,title,syncStatus. Each trail stores the full event array, participant list, metadata (domain list, page/event/site counts, duration), and sync state. - folders — Keyed by
folderId. Indexed onparentIdandname.
Operations: saveTrail, getTrail, deleteTrail, listTrails, searchTrails, updateTrailMeta, removeEventsFromTrail, removePageFromTrail, splitTrail, mergeTrails, exportTrail, importTrailData, exportAllTrails, importBackup, getTrailCount.
Auto-title generation: single domain shows "Trail on example.com", multiple domains are comma-joined, 4+ domains show "a.com, b.com +2 more".