Why it matters (viral lever)
A browser extension that scores the text you're typing in Gmail / Notion / LinkedIn puts patina into the daily writing loop ("check the score before you send"). High word-of-mouth, and distinct from the editor-focused VS Code extension (#206).
Reuse — the deterministic core, bundled for the browser
- Score core:
src/features/index.js analyzeText(text, opts) — pure JS, no LLM, no network. Returns { paragraphs: [{ hot, burstiness, mattr, lexicon }], hot }.
- Hot-paragraph % score: replicate
scripts/prose-score.mjs scoreText logic → score = hotCount / paragraphCount * 100 (keep parity with the Action/badge).
- Lexicon caveat (precise):
loadLexicon(lang, repoRoot) reads files from disk — that won't work in a browser. analyzeText accepts { lexicon } to bypass file reads. Build step must pre-generate lexicon-en.json / lexicon-ko.json from lexicon/ai-*.md and pass them in. (zh/ja lexicons don't exist yet — gate those langs off, consistent with current config.)
Scope (MVP)
- Separate repo
devswha/patina-extension, Manifest v3. Build with esbuild (new devDep is fine in the separate repo) bundling src/features/{index,segment,stylometry,lexicon}.js.
- Content script adds a small hot-% indicator to Gmail compose first (LinkedIn, Notion in follow-ups).
- Actions: "Score selection" (local), "Humanize selection" → defers to CLI/skill (no in-extension LLM).
- Hard scope cut v1: scoring is fully local (audit-only); the score path makes zero network calls (sensitive drafts never leave the browser). No API key prompts.
- Settings: language (
auto|en|ko), gate threshold.
Acceptance criteria
- Typing in Gmail shows a live hot-% badge that matches
node src/cli.js --score for the same text/lang (parity test on a fixture).
- DevTools Network panel shows no requests on the score path.
- Lexicon JSON pre-bundled at build (no runtime file/network reads).
- README cross-link from main repo
docs/integrations/.
Dependencies / links
Why it matters (viral lever)
A browser extension that scores the text you're typing in Gmail / Notion / LinkedIn puts patina into the daily writing loop ("check the score before you send"). High word-of-mouth, and distinct from the editor-focused VS Code extension (#206).
Reuse — the deterministic core, bundled for the browser
src/features/index.jsanalyzeText(text, opts)— pure JS, no LLM, no network. Returns{ paragraphs: [{ hot, burstiness, mattr, lexicon }], hot }.scripts/prose-score.mjsscoreTextlogic →score = hotCount / paragraphCount * 100(keep parity with the Action/badge).loadLexicon(lang, repoRoot)reads files from disk — that won't work in a browser.analyzeTextaccepts{ lexicon }to bypass file reads. Build step must pre-generatelexicon-en.json/lexicon-ko.jsonfromlexicon/ai-*.mdand pass them in. (zh/ja lexicons don't exist yet — gate those langs off, consistent with current config.)Scope (MVP)
devswha/patina-extension, Manifest v3. Build withesbuild(new devDep is fine in the separate repo) bundlingsrc/features/{index,segment,stylometry,lexicon}.js.auto|en|ko), gate threshold.Acceptance criteria
node src/cli.js --scorefor the same text/lang (parity test on a fixture).docs/integrations/.Dependencies / links
analyzeTextcore with distribution: web playground at patina.vibetip.help (audit-only ESM v1) #208 (playground) and viral: README "patina score" badge (shields.io endpoint) for adopting repos #282/viral: shareable before/after + score card image generator (OG-style) #283. Coordinate the browser-bundle ofsrc/features/*with distribution: web playground at patina.vibetip.help (audit-only ESM v1) #208 so the WASM-or-ESM decision is made once (note:analyzeTextis pure string ops — a plain esbuild ESM bundle works; WASM is not required).