Version: 3.0
Last Updated: December 29, 2025
Status: Active Development (v0.3 - Cross-Device Sync Implemented)
- Executive Summary
- System Architecture
- Feature Implementations
- Technology Stack
- Component Details
- Data Models & Storage
- API Specifications
- Privacy & Security
- Development Setup
- Testing Strategy
- Deployment & Operations
- Troubleshooting Guide
- Cross-Device Sync
- Appendices
BrainPlus is a privacy-first browser extension that serves as an AI-powered knowledge management system. It captures and organizes your browsing history locally using machine learning embeddings for semantic search, while optionally providing personalized deal recommendations based on aggregated interest signals.
Core Value Propositions:
- 🧠 Local-First "BrainPlus" - Store and search your browsing history semantically
- 🔒 Privacy-Preserving - All content stays on your device, never uploaded to servers
- 💰 Optional Rewards - Earn points by sharing aggregated interests (not browsing history)
- 🎯 Intelligent Deals - Get personalized recommendations without compromising privacy
- 🔄 Task Continuation - Resume interrupted browsing sessions intelligently
- 📱 Cross-Device Sync - Sync history between mobile and desktop with E2E encryption
v0.2 Pivot: From Cloud-First to Local-First
v0.1 (OLD): v0.2 (CURRENT):
┌─────────────┐ ┌─────────────┐
│ Browser │ ──upload──> │ Browser │ (no upload)
└─────────────┘ └──────┬──────┘
↓ │
┌─────────────┐ ┌─────▼─────┐
│ Backend │ │ IndexedDB │
│ Stores All │ │ (Local) │
└─────────────┘ └───────────┘
↓
(optional signal)
↓
┌─────────────┐
│ Backend │
│ (Aggregated)│
└─────────────┘
Key Privacy Guarantee:
- Backend receives: "User interested in 'headphones'" ✅
- Backend never sees: "User visited Sony WH-1000XM5 review on TechRadar at 10:00 AM" ❌
| Phase | Status | Duration |
|---|---|---|
| Phase 0: Dead Code Elimination | ✅ Complete | 2 days |
| Phase 1: Foundation (IndexedDB, Dependencies) | ✅ Complete | 1 week |
| Phase 2: On-Device Inference (ML Model) | ✅ Complete | 1.5 weeks |
| Phase 3: Search Feature | ✅ Complete | 1 week |
| Phase 4: Task Continuation Feature | ✅ Complete | 1 week |
| Phase 5: Cross-Device Sync | ✅ Complete | 1 week |
| Phase 6: Testing & Refinement | 🚧 In Progress | 1 week |
Total Timeline: 7 weeks to Beta
┌─────────────────────────────────────────────────────────────────┐
│ BROWSER (Chromium MV3) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Content │ │ Service │ │ Popup UI │ │
│ │ Script │──▶│ Worker │◀─│ (React) │ │
│ │ (Scraper) │ │ (Background) │ │ │ │
│ └──────────────┘ └──────┬───────┘ └──────────────┘ │
│ │ │
│ ┌──────────────┐ │ │
│ │ Offscreen │◀────────┘ │
│ │ Document │ │
│ │ (ML Model) │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ IndexedDB │ │chrome.storage│ │ Cache API │ │
│ │ (Digests) │ │ (Config) │ │ (ML Weights) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────┬────────────────────────────────────┘
│
│ (Optional, User-Initiated)
│
┌─────▼──────┐
│ │
│ Backend │
│ (FastAPI) │
│ │
└─────┬──────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌────▼────┐ ┌─────▼─────┐
│PostgreSQL │ │ Redis │ │ Affiliate │
│ (Users, │ │(Cache, │ │ Networks │
│ Points) │ │ Rate │ │ (Amazon, │
│ │ │Limiting)│ │ShareASale)│
└───────────┘ └─────────┘ └───────────┘
| Component | Communicates With | Protocol | Frequency |
|---|---|---|---|
| Content Script | Background | chrome.runtime.sendMessage |
Per page load |
| Background | Offscreen | Create/close offscreen doc | Batched (5 pages) |
| Background | IndexedDB | IDB API | Per analyzed page |
| Background | Backend | HTTPS/JSON | User-initiated |
| Popup | Background | chrome.runtime.sendMessage |
On user action |
| Popup | IndexedDB | IDB API | On tab open |
Status: Production Ready
Completion Date: December 18, 2025
Overview: Local-first semantic search using machine learning embeddings (512-dimensional vectors). All processing happens on-device with zero data sent to servers.
Key Features:
- ✅ ArrayBuffer storage for 50% smaller footprint
- ✅ L2 normalization for fast cosine similarity
- ✅ Query caching (LRU, instant repeat searches)
- ✅ Adaptive hybrid scoring (semantic + freshness + intent + lexical + entity)
- ✅ Privacy enforcement (filter private/expired pages)
- ✅ Fallback keyword search (if ML model fails)
- ✅ 115+ comprehensive test cases
Performance:
- Query embedding: ~35ms (target: <50ms) ✅
- Ranking 1k items: ~45ms (target: <100ms) ✅
- Ranking 5k items: ~185ms (target: <200ms) ✅
- Total latency (p95): ~250ms (target: <300ms) ✅
Files:
extension/src/lib/search.ts(522 lines) - Core search engineextension/src/lib/search.test.ts(432 lines) - Comprehensive testsextension/src/popup/components/SearchBar.tsx- Search input UIextension/src/popup/components/ResultCard.tsx- Results display
Documentation: See Search Feature for detailed docs.
Status: Production Ready
Completion Date: December 18, 2025
Overview: Intelligently groups browsing sessions and offers to resume interrupted tasks when you return to your computer. Uses time-based windowing (30min gaps) and semantic clustering to detect coherent research sessions.
Key Features:
- ✅ Time-based session detection (30min gap threshold)
- ✅ Semantic coherence filtering (cosine similarity > 0.6)
- ✅ Deterministic session IDs (SHA-256 hash + hour bucket)
- ✅ Human-readable titles ("Researching React")
- ✅ Idle state detection (
chrome.idleAPI) - ✅ Badge notification (green "1" indicator)
- ✅ Tab deduplication on resume
- ✅ 30-minute cooldown (prevents spam)
- ✅ Tab cap UI (warn for >10 tabs, max 20)
- ✅ Legacy data handling (graceful degradation)
- ✅ Ghost session prevention (invalidate on page delete)
- ✅ Missing vector handling (defensive filtering)
Architecture:
User Returns → Idle State Change → Session Handler → Offscreen Worker
↓
Cluster Recent Pages (50 pages, non-private)
↓
Time Slicing (30min gaps) → Semantic Filtering (coherence > 0.6)
↓
Generate Session → Store as Pending → Set Badge
↓
User Clicks Icon → Popup Shows Resume Card → Click "Resume"
↓
Open Tabs (deduplicated, max 20) → Clear Badge
Critical Fixes Implemented:
- Offscreen Contention - Singleton manager prevents race conditions
- Legacy Data - Graceful degradation for pages without URLs
- Idle Debouncing - 30min cooldown prevents battery drain
- Tab Cap - UI warns for large sessions (>10 tabs)
- Ghost Sessions - Invalidate when page deleted
- Missing Vectors - Defensive filtering in clustering
Files:
extension/src/lib/sessionizer.ts(344 lines) - Core clustering logicextension/src/background/handlers/sessionHandler.ts(270 lines) - Session managementextension/src/offscreen/worker-sessions.ts(75 lines) - Offscreen clusteringextension/src/popup/components/ResumeCard.tsx(175 lines) - Resume UI
Documentation: See Task Continuation for detailed docs.
Testing:
- 8 manual test scenarios documented
- Test files organized in
test-pages/task-continuation/:01-react-usestate.html- React useState tutorial02-react-useeffect.html- React useEffect tutorial03-react-context.html- React Context API04-noise-cooking.html- Noise page (should be filtered)
- See
test-pages/README.mdfor quick 2-minute test procedure
Status: Production Ready
Completion Date: December 29, 2025
Overview: Sync browsing history between mobile and desktop devices using QR code pairing and end-to-end encryption. All data is encrypted before transmission with no server-side storage.
Key Features:
- ✅ QR code pairing (< 30 seconds setup)
- ✅ End-to-end encryption (ECDH P-256 + AES-256-GCM)
- ✅ Bidirectional sync (desktop ↔ mobile)
- ✅ Vector compatibility validation (prevents broken search)
- ✅ Smart merge (field-level conflict resolution)
- ✅ Native messaging host (Windows/macOS/Linux)
- ✅ WebSocket signaling server
- ✅ Progress tracking with real-time updates
- ✅ Batch processing (efficient network usage)
- ✅ Auto-expiring sessions (5-minute QR codes)
Architecture:
Desktop Signaling Server (ws) Mobile
↓ ↓ ↓
Generate QR → JOIN Route messages Scan QR → JOIN
Send public key → Pass-through → Receive key
Derive AES key ←────────────────────────────→ Derive AES key
Encrypt batches → Never decrypts! → Decrypt batches
Receive batches ← Route back ← Send batches
Smart merge Smart merge
↓ ↓
Complete ✅ Complete ✅
Security:
- ECDH P-256 key exchange (ephemeral keys per session)
- AES-256-GCM authenticated encryption
- Zero-knowledge signaling server (never sees plaintext)
- QR code expiration (5 minutes)
- Vector metadata validation (hard fail on incompatibility)
Files:
extension/src/lib/sync/- Sync infrastructure (8 modules, ~2,100 LOC)SyncManager.ts- Main coordinatorSignalingClient.ts- WebSocket communicationecdhCrypto.ts- ECDH + AES-256-GCM encryptionsmartMerge.ts- Field-level conflict resolutionqrGenerator.ts- QR code generation/parsingvectorCompatibility.ts- ML model version validationserialization.ts- PageDigest ↔ Base64 conversionnativeHost.ts- Native messaging client
native-host/- Native messaging host (Node.js)brainplus-sync-host.js- Main host scriptinstall-manifest.js- Cross-platform installer
signaling-server/- WebSocket serverserver.js- Signaling relay (room-based)
Documentation: See Cross-Device Sync for complete documentation.
| Technology | Version | Purpose |
|---|---|---|
| TypeScript | 5.2.2 | Type-safe JavaScript |
| React | 18.2.0 | Popup UI framework |
| Vite | 5.0.8 | Build tool & dev server |
| TensorFlow.js | 4.22.0 | ML inference (WebGL backend) |
| Universal Sentence Encoder | 1.3.3 | 512-dim embeddings |
| IndexedDB (idb) | 8.0.3 | Local database wrapper |
| Stopword | 3.1.5 | Keyword extraction |
| QRCode | 1.5.4 | QR code generation |
| html5-qrcode | 2.3.8 | QR code scanning (mobile) |
| WebSocket (ws) | 8.14.2 | Real-time communication |
| Chrome Extensions API | MV3 | Browser integration |
Bundle Size Target: < 5MB (including ML model)
| Technology | Version | Purpose |
|---|---|---|
| FastAPI | 0.109.0 | Async Python web framework |
| PostgreSQL | 15+ | User data, points, catalog |
| SQLAlchemy | 2.0.25 | Async ORM |
| Alembic | 1.13.1 | Database migrations |
| Redis | 7+ | Caching, rate limiting |
| Uvicorn | 0.27.0 | ASGI server |
| Pydantic | 2.5.3 | Data validation |
| httpx | 0.26.0 | Async HTTP client |
Purpose: Extract page content without network requests (Brave Shields compatible)
Key Files:
index.ts- Entry point, initializationscraper.ts- DOM extraction logic
Extraction Logic:
export interface PageFeatures {
url: string;
title: string;
metaDescription: string;
headings: string[]; // H1, H2
mainText: string; // First 500 words
productData?: { // JSON-LD structured data
name: string;
price: number;
availability: string;
};
}
function extractPageFeatures(): PageFeatures {
// 1. Privacy checks
if (chrome.extension?.inIncognitoContext) return null;
if (isBlacklistedDomain(window.location.hostname)) return null;
// 2. Extract content
const title = document.title;
const meta = document.querySelector('meta[name="description"]')?.content;
const h1 = Array.from(document.querySelectorAll('h1')).map(h => h.textContent);
const h2 = Array.from(document.querySelectorAll('h2')).map(h => h.textContent);
// 3. Extract main text (avoid nav, footer, ads)
const mainContent = extractMainContent();
// 4. Detect structured data
const jsonLd = extractJsonLd();
return { title, meta, headings: [...h1, ...h2], mainText: mainContent, jsonLd };
}Privacy Controls:
- Incognito/Private window detection → Skip indexing
- Domain blacklist (banking, health, auth) → Skip
- Form detection → Skip input fields
- Configurable in Settings
Purpose: Central coordinator, manages storage, spawns offscreen for ML
Key Files:
index.ts- Service worker entry, lifecycle managementinit.ts- Extension initialization (first install, updates)msgHandler.ts- Message routing from content/popup/offscreenjobs.ts- Periodic tasks (sync points, signal generation)handlers/- Message handlers for specific actionssessionHandler.ts- Task continuation logicpageAnalysis.ts- Page indexing logic
Architecture Pattern: Stateless + Persistent Storage
// Service workers terminate after 30s inactivity - must be stateless!
// All state persisted to chrome.storage.local or IndexedDB
// GOOD ✅
async function handleMessage(msg: Message) {
const config = await chrome.storage.local.get('config');
// ... process ...
await chrome.storage.local.set({ lastProcessed: Date.now() });
}
// BAD ❌
let queue = []; // Lost when service worker terminates!Batching Strategy:
// Queue analysis requests, process in batches of 5
const analysisQueue: PageRequest[] = [];
chrome.runtime.onMessage.addListener(async (msg) => {
if (msg.type === 'ANALYZE_PAGE') {
analysisQueue.push(msg.data);
await persistQueue(); // Save to storage immediately
if (analysisQueue.length >= 5) {
await processBatch();
}
}
});Purpose: Heavy computation (ML inference, clustering) isolated from service worker
Why Offscreen?
- Service workers don't support DOM/large computations
- Offscreen documents can load TensorFlow.js (WebGL backend)
- Can be opened/closed dynamically to manage memory
Key Files:
index.html- Offscreen document HTML shellindex.ts- Message listener, coordinates workersworker-tfjs.ts- TensorFlow.js model loading & inferenceworker-sessions.ts- Task continuation clustering
ML Model Pipeline:
import * as use from '@tensorflow-models/universal-sentence-encoder';
import * as tf from '@tensorflow/tfjs';
let model: use.UniversalSentenceEncoder | null = null;
async function loadModel() {
if (!model) {
await tf.ready();
model = await use.load();
console.log('[Offscreen] Model loaded:', model);
}
}
async function generateEmbedding(text: string): Promise<Float32Array> {
await loadModel();
const embeddings = await model!.embed([text]);
const vector = await embeddings.array();
embeddings.dispose(); // Free GPU memory
return vector[0];
}Performance Targets:
- Model load: < 3 seconds (first time)
- Inference: < 100ms per page (p95)
- Memory: ~100MB when active, 0 when closed
Purpose: User interface for interacting with BrainPlus
Framework: React 18 + TypeScript + CSS (no UI library for small bundle)
Key Components:
popup/
├── index.tsx # Entry point, React root
├── App.tsx # Main app, tab routing
├── styles.css # Global styles
└── components/
├── Dashboard.tsx # Overview: points, stats
├── BrainPlus.tsx # Search interface (NEW)
├── ResumeCard.tsx # Task continuation UI (NEW)
├── Settings.tsx # Privacy controls, preferences
├── Onboarding.tsx # First-time user flow
└── ManageStorage.tsx # Storage management UI
BrainPlus Tab (Search Feature):
export function BrainPlus() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<RankedResult[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = async (q: string) => {
setLoading(true);
try {
const results = await searchWithFallback(q, {
includePrivate: false,
limit: 20,
minScore: 0.1
});
setResults(results);
} catch (error) {
console.error('[BrainPlus] Search failed:', error);
} finally {
setLoading(false);
}
};
return (
<div>
<SearchBar onSearch={handleSearch} loading={loading} />
<ResultsList results={results} />
</div>
);
}Purpose: Local database for page digests and embeddings
Database Schema:
// Database: datapay_brain_v1
// Store: digests
// Primary Key: urlHash (SHA-256 of URL)
interface PageDigest {
// Schema version
schemaVersion: number; // Current: 3
// Indexing
urlHash: string; // Primary key, SHA-256 of sanitized URL
domainHash: string; // SHA-256 of domain (for grouping)
url: string; // ⭐ NEW in v3 - For task continuation
// Content (NEVER sent to backend)
title: string; // Page title (max 200 chars)
summary: string; // First 500 words or meta description
fullText?: string; // Optional: Full text for semantic search
// ML Outputs
vectorBuf: ArrayBuffer; // ⭐ NEW in v2 - 512-dim embedding (2KB)
vector?: Float32Array; // Runtime hydrated vector
entities: string[]; // Top 5-10 keywords (TF-IDF)
category: string; // "electronics", "fitness", "tech", etc.
intentScore: number; // 0.0-1.0 (purchase intent)
// Timestamps
timestamp: number; // Visit time (ms since epoch)
lastAccessed: number; // Last query time (for LRU eviction)
// Sync
isSynced: boolean; // Has signal been sent?
signalSentAt?: number; // When signal was sent
// Privacy
isPrivate: boolean; // User-marked private (never signal)
autoExpireAt: number; // Auto-delete timestamp (30 days default)
}Indexes for Performance:
store.createIndex('timestamp', 'timestamp');
store.createIndex('category', 'category');
store.createIndex('intentScore', 'intentScore');
store.createIndex('freshness_intent', ['timestamp', 'intentScore']); // Composite
store.createIndex('isSynced', 'isSynced');Key Operations:
class DataPayDB {
// Save digest
async saveDigest(digest: PageDigest): Promise<void>
// Query recent high-intent pages
async queryRecentIntent(hours: number, minScore: number): Promise<PageDigest[]>
// Semantic search (cosine similarity)
async searchSemantic(queryVector: Float32Array, limit: number): Promise<PageDigest[]>
// Cleanup
async pruneOldEntries(): Promise<number> // Delete expired
async clearAllData(): Promise<void> // GDPR: Right to Erasure
// Stats
async getStorageStats(): Promise<StorageStats>
// Export
async exportData(): Promise<Blob> // GDPR: Data portability
}Retention Policy:
- Time-based: Auto-delete after 30 days (configurable: 7/14/30/60 days)
- Size-based: Max 5,000 entries (configurable: 1k/3k/5k/10k)
- Eviction: LRU (Least Recently Used) when limit reached
- User control: "Forget All" button, per-page "Forget"
| Storage | Purpose | Capacity | Persistence |
|---|---|---|---|
| IndexedDB | Page digests, vectors | ~1-10GB | Persistent until cleared |
| chrome.storage.local | Config, preferences, sessions | 10MB | Persistent |
| Cache API | ML model weights | 100MB+ | Persistent |
| Memory | Service worker state | Ephemeral | Lost after 30s inactivity |
Search Feature:
searchMetrics- Performance metrics for last 100 searchessearchAuditLog- Privacy-preserving search history
Task Continuation Feature:
pendingSession- Current session waiting for user actiondismissedSessionIds- Permanently dismissed session IDs (cleared daily)lastSessionCheckTimestamp- For 30min cooldown
Core Settings:
config- User preferencesanonymousId- Hashed device identifierpoints- Cached points balance
Base URL: https://api.datapay.io (production) or http://localhost:8000 (dev)
Purpose: Match deals based on user interest signals
Request:
{
"anonymousId": "sha256_hash",
"signals": [
{
"category": "electronics",
"entities": ["noise cancelling headphones", "sony", "wh-1000xm5"],
"intentScore": 0.85,
"pageCount": 5,
"timeWindow": "48h"
}
],
"timestamp": 1702823400000
}Response (200 OK):
{
"success": true,
"matches": [
{
"type": "product",
"title": "Sony WH-1000XM5 Wireless Headphones",
"merchant": "Amazon",
"price": "$348",
"originalPrice": "$399",
"discount": "13% off",
"redirectUrl": "https://api.datapay.io/r/abc123xyz",
"reason": "Based on your interest in 'noise cancelling headphones'",
"commission": "5%",
"expiresAt": 1702909800000
}
],
"pointsEarned": 10
}Rate Limits:
- 10 requests per hour per user
- 50 requests per day per user
Core Principle: Zero-Knowledge Backend
What Extension Knows:
├─ Full browsing history (stored locally)
├─ Page titles and content
├─ Visit timestamps
├─ Vector embeddings
└─ User preferences
What Backend Knows:
├─ Anonymous ID (hashed)
├─ Interest categories (e.g., "electronics")
├─ Keyword lists (e.g., ["headphones", "sony"])
├─ Aggregate page counts (e.g., "5 pages")
└─ Time windows (e.g., "last 48 hours")
What Backend NEVER Knows:
✗ Specific URLs visited
✗ Page titles
✗ Visit timestamps
✗ Browsing patterns
✗ Session duration
User-Facing Settings:
-
Incognito/Private Window Detection
- Automatically skip indexing in private mode
chrome.extension.inIncognitoContextcheck
-
Domain Blacklist (Default + Custom)
- Banking:
chase.com,wellsfargo.com - Health:
webmd.com,healthline.com - Auth:
okta.com,auth0.com - User can add custom domains
- Banking:
-
Indexing Modes
- Full: Index all pages (default)
- Shopping Only: Only e-commerce sites
- Manual: Only when user clicks "Save Page"
-
Data Retention
- 7 / 14 / 30 / 60 days (default: 30)
- Max entries: 1k / 3k / 5k / 10k (default: 5k)
-
Right to Erasure
- "Forget All" button → Clear all local data
- "Forget This Page" → Delete specific entry
- Backend DELETE request → Remove server-side signals
- Node.js: v18+ (LTS recommended)
- npm: v9+
- Python: 3.10+ (for backend)
- Docker: v20+ (for backend databases)
- Git: v2.30+
- Browser: Chrome, Brave, or Edge (Chromium-based)
cd extension
# Install dependencies
npm install
# Copy environment config
cp .env.example .env
# Development build (watch mode)
npm run dev
# Production build
npm run build
# Type checking
npm run type-check
# Linting
npm run lint
# Testing
npm testLoad Extension in Browser:
- Open Chrome/Brave:
chrome://extensions - Enable "Developer mode" (top-right toggle)
- Click "Load unpacked"
- Select
extension/distfolder - Extension icon appears in toolbar
Hot Reload:
- Vite rebuilds automatically on file changes
- Click "Reload" button in
chrome://extensionsto refresh
Quick Test (2 minutes):
- Load Extension in Chrome/Brave
- Visit test pages:
https://en.wikipedia.org/wiki/Machine_learninghttps://en.wikipedia.org/wiki/Artificial_intelligencehttps://www.amazon.com/Sony-WH-1000XM5/dp/B09XS7JWHH
- Wait 30 seconds for indexing
- Open popup → BrainPlus tab
- Click "Search Your Brain"
- Type query: "machine learning"
- Expected: Results appear with relevance scores
Test Queries:
- Semantic: "similar to machine learning" → Related pages
- Recency: "latest news today" → Recent pages rank higher
- Entity: "sony headphones" → Amazon page ranks #1
Verification:
- ✅ Results appear within ~250ms
- ✅ Relevance scores shown (%)
- ✅ Badges: "Concept Match", "Fresh", etc.
- ✅ Click result → No errors
Quick Test (2 minutes):
-
Test HTML files are organized in
test-pages/task-continuation/:01-react-usestate.html- React useState tutorial02-react-useeffect.html- React useEffect tutorial03-react-context.html- React Context API
-
Open test pages (in this order):
- Open all 3 test files in browser from
test-pages/task-continuation/ - Spend 10-15 seconds on each page
- Open all 3 test files in browser from
-
Wait 2 minutes for processing (extension analyzes pages in background)
-
Close all tabs
-
Lock computer (Windows Key + L) for 1 minute
-
Unlock and click extension icon
-
Expected Result:
- ✅ Purple gradient card appears
- ✅ Shows "Researching React" (or similar title)
- ✅ Badge "1" on extension icon
- ✅ Shows 3 pages with coherence indicator
- ✅ Click "Resume" → All tabs reopen (no duplicates)
Full Test Scenarios:
| Scenario | Test Case | Expected Result |
|---|---|---|
| 1 | Focused Research Session | Resume card with coherent session |
| 2 | Interleaved Browsing | Noise pages filtered out |
| 3 | Large Session (15+ tabs) | Warning + "Resume Top 10" button |
| 4 | Legacy Data | Graceful degradation message |
| 5 | Idle Debouncing | Cooldown prevents spam |
| 6 | Ghost Session Prevention | Session updated when page deleted |
| 7 | Dismissal | Dismissed session doesn't reappear |
| 8 | Offscreen Contention | No "already exists" errors |
Debugging:
// Check pending session
chrome.storage.local.get('pendingSession', data => {
console.log('Pending session:', data);
});
// Check dismissed sessions
chrome.storage.local.get('dismissedSessionIds', data => {
console.log('Dismissed:', data);
});
// Check last check time
chrome.storage.local.get('lastSessionCheckTimestamp', data => {
const minutes = (Date.now() - data.lastSessionCheckTimestamp) / 60000;
console.log('Last check:', minutes.toFixed(1), 'minutes ago');
});Chrome Web Store Submission:
-
Build Production:
cd extension npm run build cd dist zip -r ../brainplus-v1.0.0.zip .
-
Prepare Assets:
- Screenshots: 1280x800 (5 required)
- Promotional images: 440x280
- Privacy policy URL
- Icon: 128x128 PNG
-
Submit to Chrome Web Store:
- https://chrome.google.com/webstore/devconsole
- Upload ZIP
- Fill metadata (name, description, category)
- Add screenshots
- Set pricing (free)
- Submit for review
Review Time: 3-7 days (typically)
Brave Listing:
- Brave uses Chrome Web Store
- No separate submission needed
- Test thoroughly with Shields enabled
Symptoms: "Manifest file is missing or unreadable" error
Solutions:
- Check
extension/dist/manifest.jsonexists - Verify build completed:
npm run build - Reload extension in
chrome://extensions
Symptoms: "Failed to load model" error in offscreen console
Solutions:
- Check internet connection (model downloads from HuggingFace)
- Verify CSP allows WASM:
'wasm-unsafe-eval'in manifest - Check Brave Shields not blocking CDN
- Fallback activates automatically (keyword-based scoring)
Symptoms: "No results found" message
Solutions:
- Check pages are indexed: Open popup → Check "Pages Indexed" counter
- Wait 30 seconds after browsing (ML model needs time)
- Try broader query (e.g., "machine" instead of "machine learning algorithms")
- Check console for errors
Symptoms: No badge after idle state change
Solutions:
- Wait longer (3-5 minutes for page analysis)
- Check console for
[PageAnalysis]logs - Verify test pages loaded properly (>100 words content)
- Check cooldown not active (30min between checks)
Symptoms: "QuotaExceededError" when saving digest
Solutions:
- Check storage usage: Settings → Manage Storage
- Reduce retention period: Settings → Privacy → Retention Days
- Lower max entries: Settings → Privacy → Max Pages
- Click "Forget All" to clear old data
Cross-Device Sync enables users to synchronize browsing history between mobile and desktop devices using QR code pairing and end-to-end encryption.
Value Propositions:
- 🔄 Seamless Sync - Research on mobile, continue on desktop (and vice versa)
- 🔒 Privacy-First - All data encrypted end-to-end, no server storage
- 📱 Device Agnostic - Works on any Chromium-based browser
- ⚡ Local-First - Direct peer-to-peer communication
- 🎯 Zero Setup - QR code pairing in < 30 seconds
User Flow:
- Desktop user clicks "Sync" → QR code appears
- Mobile user scans QR code → Automatic pairing
- History merges bidirectionally → Smart conflict resolution
- Complete! Both devices have full browsing history
High-Level Design:
┌─────────────────────────────────────────────────────────────────┐
│ Desktop Device │
├─────────────────────────────────────────────────────────────────┤
│ Extension Native Host Signaling │
│ ↓ ↓ ↓ │
│ SyncManager ←────→ brainplus-sync-host ←────→ WebSocket │
│ ↓ ↓ ↓ │
│ IndexedDB QR Generator Room: abc123 │
│ (PageDigest) Node.js ↓ │
│ ↓ Pass-through │
│ Encryption ────────────────────────────────────────────────────┤
│ (ECDH + AES) ↓ │
└────────────────────────────────────────────────────┼────────────┘
│
┌────────────────────────────────────────────────────┼────────────┐
│ Mobile Device ↓ │
├─────────────────────────────────────────────────────────────────┤
│ Extension WebSocket │
│ ↓ ↓ │
│ SyncManager ←──────────────────────────── Room: abc123 │
│ ↓ ↓ │
│ IndexedDB Pass-through │
│ (PageDigest) │
│ ↓ │
│ Encryption ←──────────────────────────────────────────────────┘
│ (ECDH + AES)
│ ↓
│ QR Scanner (html5-qrcode)
└─────────────────────────────────────────────────────────────────┘
Component Responsibilities:
| Component | Responsibility |
|---|---|
| SyncManager | Orchestrates sync flow, manages session state |
| SignalingClient | WebSocket communication, room management |
| Native Host | QR generation, local network bridge (desktop only) |
| ecdhCrypto | Key exchange, encryption/decryption |
| smartMerge | Field-level conflict resolution |
| vectorCompatibility | ML model version validation |
| serialization | PageDigest ↔ Base64 conversion |
| QRScanner | Camera-based QR scanning (mobile) |
Encryption Architecture:
Desktop Mobile
↓ ↓
Generate ECDH key pair (P-256) Generate ECDH key pair (P-256)
↓ ↓
Public key → QR code → Scan ← Public key
↓ ↓
Derive shared secret ←────────────→ Derive shared secret
(ECDH key agreement) (ECDH key agreement)
↓ ↓
AES-256-GCM key AES-256-GCM key
↓ ↓
Encrypt PageDigest batches Decrypt PageDigest batches
↓ ↓
Send via WebSocket → ← Receive via WebSocket
(Signaling server NEVER decrypts)
Key Security Features:
-
End-to-End Encryption
- ECDH P-256 key exchange
- AES-256-GCM authenticated encryption
- 128-bit authentication tags
- No server-side decryption
-
Ephemeral Keys
- New key pair generated per session
- Keys never stored persistently
- Session ends → Keys destroyed
-
Zero-Knowledge Server
- Signaling server only routes encrypted messages
- Never has access to shared secret
- Cannot decrypt browsing history
-
QR Code Expiration
- 5-minute expiration window
- Prevents stale pairing attempts
- Reduces attack surface
-
Vector Compatibility Validation
- Hard fail on ML model version mismatch
- Prevents broken semantic search
- User-friendly error messages
Smart Merge Strategy:
Instead of naive Last-Write-Wins (LWW), we use field-level merging:
function mergePageDigests(local: PageDigest, remote: PageDigest): PageDigest {
return {
// Identifiers (must match)
urlHash: local.urlHash,
url: local.url,
// Content: Keep longest/best quality
title: remote.title.length > local.title.length ? remote.title : local.title,
summary: remote.summary.length > local.summary.length ? remote.summary : local.summary,
// ML Outputs: Keep highest intent score version
intentScore: Math.max(local.intentScore, remote.intentScore),
vector: local.intentScore >= remote.intentScore ? local.vector : remote.vector,
// Entities/Keywords: Union (remove duplicates)
entities: Array.from(new Set([...local.entities, ...remote.entities])),
keywords: Array.from(new Set([...local.keywords, ...remote.keywords])),
// Timestamps: Keep most recent access
timestamp: Math.min(local.timestamp, remote.timestamp), // First visit
lastAccessed: Math.max(local.lastAccessed, remote.lastAccessed), // Most recent
// Privacy: OR (if either device marked private, keep private)
isPrivate: local.isPrivate || remote.isPrivate,
};
}Benefits:
- ✅ Preserves best data from both devices
- ✅ No data loss from naive overwriting
- ✅ Union of keywords/entities (better search)
- ✅ Respects privacy flags from both devices
Purpose: Bridge between browser extension and operating system for:
- QR code generation (requires Node.js libraries)
- Local network communication
- System-level operations
Architecture:
Chrome Extension (JavaScript)
↓
Native Messaging API (chrome.runtime.connectNative)
↓
brainplus-sync-host.js (Node.js)
↓
Operating System
Installation (Windows):
cd native-host
npm install
node install-manifest.js YOUR_EXTENSION_IDWhat it does:
- Installs manifest to Windows Registry:
HKCU\Software\Google\Chrome\NativeMessagingHosts\com.brainplus.sync_host - Points to wrapper script:
brainplus-sync-host-wrapper.bat - Wrapper calls:
node.exe brainplus-sync-host.js
Communication Protocol:
// Request (from extension)
{
type: "generate_qr",
payload: {
deviceId: "desktop-abc123",
signalingUrl: "ws://localhost:8080",
roomId: "room_xyz789",
publicKey: "base64-encoded-key",
expiresAt: 1767017906617
}
}
// Response (from native host)
{
success: true,
type: "qr_generated",
data: {
roomId: "room_xyz789",
qrCodeDataUrl: "data:image/png;base64,iVBORw0KGgo...",
expiresAt: 1767017906617
}
}Purpose: Relay encrypted messages between devices without decrypting them.
Room-Based Architecture:
Room: room_abc123
├── Client 1 (Desktop) - Connected
└── Client 2 (Mobile) - Connected
Messages:
1. JOIN - Enter room
2. KEY_EXCHANGE - Share ECDH public keys
3. HISTORY_BATCH - Send encrypted PageDigest batches
4. BATCH_ACK - Acknowledge receipt
5. SYNC_DONE - Signal completion
6. PING/PONG - Heartbeat (30s interval)
Server Implementation:
// signaling-server/server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const rooms = new Map(); // roomId → Set<WebSocket>
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.type === 'JOIN') {
// Add client to room
if (!rooms.has(msg.roomId)) {
rooms.set(msg.roomId, new Set());
}
rooms.get(msg.roomId).add(ws);
} else {
// Forward to all clients in same room
const room = rooms.get(msg.roomId);
room?.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(msg)); // Pass-through encrypted message
}
});
}
});
});Starting the Server:
cd signaling-server
npm install
npm start
# Server listening on ws://localhost:8080Files Created:
extension/src/lib/sync/
├── SyncManager.ts # Main coordinator (400 LOC)
├── SignalingClient.ts # WebSocket communication (300 LOC)
├── ecdhCrypto.ts # ECDH + AES-256-GCM (300 LOC)
├── smartMerge.ts # Conflict resolution (350 LOC)
├── qrGenerator.ts # QR generation/parsing (250 LOC)
├── vectorCompatibility.ts # Version validation (200 LOC)
├── serialization.ts # PageDigest conversion (300 LOC)
├── nativeHost.ts # Native messaging client (150 LOC)
└── types.ts # Type definitions (200 LOC)
extension/src/popup/components/
├── SyncView.tsx # Sync UI (400 LOC)
└── QRScanner.tsx # QR scanning (mobile) (200 LOC)
extension/src/background/handlers/
└── syncHandler.ts # Background message handlers (250 LOC)
native-host/
├── brainplus-sync-host.js # Native host (400 LOC)
├── install-manifest.js # Installer script (150 LOC)
└── brainplus-sync-host-wrapper.bat # Windows wrapper
signaling-server/
└── server.js # WebSocket server (150 LOC)
Total: ~3,300 LOC (not counting tests/docs)
Dependencies Added:
{
"qrcode": "1.5.4", // QR code generation
"html5-qrcode": "2.3.8", // QR code scanning (mobile)
"ws": "8.14.2" // WebSocket (signaling server)
}Manual Testing (Desktop):
-
Setup:
# Terminal 1: Start signaling server cd signaling-server npm start # Terminal 2: Install native host cd native-host npm install node install-manifest.js YOUR_EXTENSION_ID
-
Build extension:
cd extension npm run build -
Load in Chrome:
chrome://extensions→ Load unpacked → Selectextension/dist
-
Test sync:
- Click BrainPlus icon → Sync tab
- Click "Generate QR Code"
- Verify QR code appears
- Verify WebSocket connection: "Connected to signaling server"
Testing Scenarios:
| Scenario | Steps | Expected Result |
|---|---|---|
| Happy Path | Desktop generates QR → Mobile scans | Both devices sync successfully |
| QR Expiration | Wait 5 minutes → Scan QR | Error: "QR code expired" |
| Vector Mismatch | Desktop TF.js 4.22.0 → Mobile 4.21.0 | Hard fail with clear error message |
| Conflict Resolution | Same URL on both, different titles | Longest title wins |
| Large History | 5,000 pages × 2KB each ≈ 10MB | Batched transfer, progress bar updates |
| Network Interruption | Disconnect WiFi mid-sync | Reconnection with resume |
| Native Host Missing | Uninstall native host | Clear installation instructions |
Issue: "Sync Companion Required"
Cause: Native messaging host not installed or not detected.
Solution:
- Install native host:
cd native-host npm install node install-manifest.js YOUR_EXTENSION_ID - Restart Chrome completely (close ALL windows)
- Verify registry entry (Windows):
reg query "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.brainplus.sync_host"
Issue: "WebSocket connection failed"
Cause: Signaling server not running.
Solution:
cd signaling-server
npm install
npm start
# Should see: "✅ Server listening on port 8080"Issue: "Vector compatibility check failed"
Cause: Different TensorFlow.js or USE versions on devices.
Solution:
- Both devices must use exact same versions:
{ "@tensorflow/tfjs": "4.22.0", // No ^ or ~ "@tensorflow-models/universal-sentence-encoder": "1.3.3" } - Update extension on both devices
- Clear browser cache
- Retry sync
Issue: QR code doesn't appear
Cause: Native host can't generate QR code.
Solution:
- Check native host log:
# Windows type %TEMP%\brainplus-sync-host.log # macOS/Linux tail -f /tmp/brainplus-sync-host.log
- Verify
qrcodelibrary installed:cd native-host npm list qrcode
Planned Features:
-
Continuous Sync (v2)
- Background sync every hour
- Push notifications for updates
- Incremental sync (only changed pages)
-
Multi-Device Support (v2)
- Sync more than 2 devices
- Device management UI (see all paired devices)
- Selective sync (choose which device to sync with)
-
Cloud Backup (v3, Optional)
- End-to-end encrypted cloud storage
- For disaster recovery only
- User-controlled encryption keys
-
Conflict Resolution UI (v2)
- Manual conflict resolution
- Side-by-side comparison
- "Keep both" option
-
Sync Analytics (v2)
- Track sync performance
- Identify bottlenecks
- Optimize batch sizes
| Term | Definition |
|---|---|
| MV3 | Manifest V3 - Chrome's latest extension architecture |
| IndexedDB | Browser database for large structured data |
| Service Worker | Background script that runs independently of web pages |
| Offscreen Document | Hidden page for heavy computations (ML inference) |
| USE | Universal Sentence Encoder - ML model for text embeddings |
| Embedding | Fixed-length vector representation of text (512-dim) |
| TF-IDF | Term Frequency-Inverse Document Frequency - keyword extraction |
| Cosine Similarity | Measure of similarity between vectors (0-1) |
| LRU | Least Recently Used - eviction policy |
| GDPR | General Data Protection Regulation - EU privacy law |
| CSP | Content Security Policy - security headers |
| WASM | WebAssembly - low-level binary format for web |
| Metric | Target | Actual | Status |
|---|---|---|---|
| Search | |||
| Query Embedding | <50ms | ~35ms | ✅ |
| Ranking (1k items) | <100ms | ~45ms | ✅ |
| Ranking (5k items) | <200ms | ~185ms | ✅ |
| UI Render | <16ms | ~12ms | ✅ |
| Total Latency (p95) | <300ms | ~250ms | ✅ |
| Task Continuation | |||
| Clustering (50 pages) | <100ms | ~70ms | ✅ |
| Session Detection | <60s | ~45s | ✅ |
| Badge Appearance | <60s | ~50s | ✅ |
| Tab Opening | <3s | ~2s | ✅ |
| Memory Impact | <10MB | ~5MB | ✅ |
brainplus/
├── extension/
│ ├── src/
│ │ ├── background/
│ │ │ ├── index.ts # Service worker entry
│ │ │ ├── init.ts # Initialization
│ │ │ ├── msgHandler.ts # Message router
│ │ │ ├── offscreenManager.ts # Offscreen lifecycle
│ │ │ └── handlers/
│ │ │ ├── sessionHandler.ts # Task continuation
│ │ │ └── pageAnalysis.ts # Page indexing
│ │ ├── content/
│ │ │ ├── index.ts # Content script entry
│ │ │ └── scraper.ts # DOM extraction
│ │ ├── offscreen/
│ │ │ ├── index.html # Offscreen document
│ │ │ ├── index.ts # Message coordinator
│ │ │ ├── worker-tfjs.ts # ML inference
│ │ │ └── worker-sessions.ts # Session clustering
│ │ ├── popup/
│ │ │ ├── index.tsx # React entry
│ │ │ ├── App.tsx # Main app
│ │ │ ├── styles.css # Global styles
│ │ │ └── components/
│ │ │ ├── Dashboard.tsx # Overview
│ │ │ ├── BrainPlus.tsx # Search UI
│ │ │ ├── ResumeCard.tsx # Task continuation UI
│ │ │ ├── SearchBar.tsx # Search input
│ │ │ ├── ResultCard.tsx # Search result
│ │ │ └── Settings.tsx # Settings
│ │ └── lib/
│ │ ├── db.ts # IndexedDB manager
│ │ ├── search.ts # Search engine
│ │ ├── sessionizer.ts # Session clustering
│ │ ├── types.ts # TypeScript types
│ │ └── crypto.ts # Encryption utilities
│ ├── public/
│ │ ├── manifest.json # MV3 manifest
│ │ └── icons/ # Extension icons
│ ├── tests/
│ │ └── search.test.ts # Search tests
│ └── package.json
├── backend/ # Optional backend
│ ├── app/
│ │ ├── main.py # FastAPI entry
│ │ ├── api/v1/ # API routes
│ │ └── models/ # Database models
│ └── requirements.txt
├── test-pages/ # Test HTML pages for manual testing
│ ├── README.md # Testing guide
│ ├── task-continuation/ # Task continuation feature tests
│ │ ├── 01-react-usestate.html # React useState tutorial
│ │ ├── 02-react-useeffect.html # React useEffect tutorial
│ │ ├── 03-react-context.html # React Context API
│ │ └── 04-noise-cooking.html # Noise page (filtered)
│ └── general/ # General feature tests
│ └── sample-page.html # Basic test page
├── ONBOARDING_DESIGN_DOC.md # This file
└── README.md # Project overview
Documentation:
- Chrome Extensions API: https://developer.chrome.com/docs/extensions/
- TensorFlow.js: https://www.tensorflow.org/js
- Universal Sentence Encoder: https://tfhub.dev/google/universal-sentence-encoder/
- FastAPI: https://fastapi.tiangolo.com/
- PostgreSQL: https://www.postgresql.org/docs/
Internal Docs:
README.md- Project overviewextension/README.md- Extension-specific docsbackend/README.md- Backend-specific docs
Week 1: Environment Setup
- Clone repository
- Setup Node.js, Python, Docker
- Install extension in browser
- Start backend locally (optional)
- Complete "Hello World" changes (update README, submit PR)
- Review this design doc
- Watch demo video (if available)
Week 2: Component Deep Dive
- Debug content script on live pages
- Inspect IndexedDB in DevTools
- Trigger offscreen model loading
- Test search feature with real queries
- Test task continuation with test pages
- Review codebase (3+ hours)
- Pair programming session with senior dev
Week 3: Feature Implementation
- Pick starter issue from backlog (label: "good first issue")
- Implement feature (extension or backend)
- Write tests (unit + integration)
- Submit PR for review
- Address review comments
- Merge to main
Week 4: Autonomy
- Own a feature end-to-end (design → implement → deploy)
- Participate in sprint planning
- Review others' PRs
- Contribute to design discussions
Welcome to the team! 🚀
Last Updated: December 18, 2025
Document Version: 2.0
Maintained by: Engineering Team