Skip to content

Local-first outreach research

2026 · Implemented prototype · live verification incomplete

Primer

Turning profile browsing into a grounded research-and-draft loop

I designed and built a single-user Chrome extension that brings LinkedIn context, prior Obsidian notes, optional web research, message drafting, and vault write-back into one human-reviewed workflow—with no application server.

Primer extension icon in its original ochre color
The checked-in Primer extension icon. The repository contains no product screenshots, so this case study does not substitute a fabricated interface capture.

Quick scan

TL;DR

The shortest version: what changed, what shipped, what was verified, and where the proof stops.

Overview

Problem

Useful outreach requires recalling prior context, finding a specific point of connection, and drafting something personal without repeatedly leaving the LinkedIn page.

Move

Built a read-only, one-contact-at-a-time extension that joins visible page context with an owned Obsidian vault and optional web research, then keeps every draft under human review.

Outcome

Completed a 22-commit v0.1 implementation with the research, drafting, vault, settings, caching, and error-state paths represented in source and 43 automated tests passing.

Scope

Independent product requirements, interaction design, Chrome extension architecture, API integration, defensive parsing, vault templating, and source-level verification.

Proof

Build history
22 commitsRepository history spans the PRD, implementation, integration fixes, and selector verification across June 17–18, 2026.
Automated verification
43 / 43 testsThe current Node test suite passed all pure-function and scrape-helper checks on July 16, 2026.
Live DOM evidence
PartialOne authenticated profile and one 1:1 DM widget are documented; the broader selector matrix remains open.
Product evidence
Not yet measuredNo usage, outreach-response, latency, retention, or completed end-to-end milestone evidence was found.

01 · Context

The friction was the context switch hiding inside every introduction.

Preparing a thoughtful LinkedIn message means moving through the same sequence each time: inspect the person’s profile or conversation, remember whether they already appear in prior notes, find a credible point of connection, and shape that context into an opening that does not read like a template.

Primer was specified as a personal tool for that exact loop rather than a general prospecting platform. The primary user, existing Obsidian vault, preferred APIs, and Chrome surface were all known, which made it possible to remove multi-user accounts, hosted storage, bulk scraping, sequencing, and automated sending from v0.1.

02 · The decision

Keep the workflow local, narrow, and deliberately human at the last mile.

Read the page already open. Retrieve owned context. Draft options. Let the person decide what to send.

The defining architectural decision was to avoid an application server. A Manifest V3 service worker orchestrates direct calls to Anthropic, optional Tavily search, and the Obsidian Local REST API; the vault remains the durable record while the extension keeps only settings and a ten-minute in-memory result cache.

The product boundary is just as explicit. Primer works on one profile or one message thread the user is already viewing, never navigates on its own, never pastes into LinkedIn’s composer, and never sends. Deep research is opt-in, and generated connection points retain a basis label so scraped, vault, search, and inferred material are not presented as equivalent evidence.

Engineering noteUntrusted text crosses two rendering boundaries.

LinkedIn text and model output are treated as external input before they reach either the injected panel or the Markdown vault. The implementation escapes HTML and separately neutralizes frontmatter and document-structure injection.

  • Panel rendering escapes ampersands, angle brackets, quotes, and apostrophes before assigning generated markup through innerHTML.
  • Frontmatter values are collapsed to quoted single-line YAML scalars, with embedded quotes escaped.
  • Generated block text escapes heading and horizontal-rule patterns before it is written into a contact note.

03 · What shipped

A small extension with the complete research-to-record path represented in code.

A LinkedIn content script detects profile and direct-message URLs, reads the available DOM through isolated selector helpers, and adds a floating action, quick/deep toggle, staged loading states, source-labeled report, copyable draft variants, saved-path confirmation, and dependency-specific errors.

The background worker searches the vault by contact and company, optionally adds a bounded Tavily result set, asks Anthropic for validated JSON, creates or appends a structured contact note, and caches the result for the browser session. A settings page stores three user-supplied keys, the Obsidian port and folder scope, and the default research mode in chrome.storage.local.

Implementation detail is intentionally separated into small modules for LinkedIn selectors, prompt assembly, Anthropic validation and retry, Tavily formatting, Obsidian error classification, note templating, and cache expiry. That split made the I/O-free behavior testable without pretending the live Chrome and API path was covered automatically.

Engineering noteStructured output fails closed before it reaches the vault.

Research reports and draft variants must match explicit JSON shapes. A malformed model response receives one constrained retry; a second invalid response becomes a dependency error instead of being rendered or persisted.

  • Connection-point basis is limited to vault, scraped, search, or inferred.
  • Research and draft responses have separate validators and token ceilings.
  • A failed optional web-search step degrades to quick-mode grounding and is recorded in the grounding notes.

04 · Evidence

The source supports the implementation; the checklist limits the claim.

The repository records 22 commits across June 17–18, 2026, moving from product requirements through the extension scaffold, selector and scraping helpers, vault and model clients, orchestration, panel, settings, integration fixes, and a live-selector revision. Running the current Node test suite produced 43 passing tests with zero failures.

Those tests cover the pure boundaries: URL detection and scrape shape, selector fallbacks, frontmatter and Markdown sanitization, note paths, API response validation, prompt assembly, error classification, search-result trimming, and cache expiry. They do not cover Chrome APIs, injected rendering, the service-worker workflow, real API calls, or a complete Obsidian round trip.

The current README records one authenticated profile inspection and one authenticated 1:1 DM widget inspection, but still calls for two or more additional profiles and a full-page message-thread spot check. The remaining milestone checklist is unchecked, including the end-to-end quick research, drafting, deep-search failure, configuration errors, and daily-use polish flows. No usage, outreach-response, latency, or retention analytics were found, so none are claimed here.

05 · Reflection

The strongest choice was refusing to turn a personal workflow into a platform too early.

Because the tool had one known user and one owned knowledge base, a server, database, account system, and generic CRM model would have added operating cost before proving the loop was useful. The event-driven extension and Markdown vault keep the experiment inspectable and easy to remove or evolve.

The project also shows why implementation evidence and product evidence are different. Passing pure-function tests can establish that parsing, templating, validation, and state boundaries behave as written; it cannot establish that LinkedIn markup will remain stable, the three live integrations work together, or the suggestions improve outreach.

The next meaningful checkpoint is not another feature. It is completing the manual matrix on multiple profiles and the full-page message surface, then timing real quick-check runs and reviewing whether the retrieved context makes drafts more specific without making them less authentic.

Continue with the project

Explore Primer in context.