A Chrome extension that suggests answers for job application form fields — never auto-fills, always shows a suggestion first so you stay in control.
When you focus a form field on a supported job site, a tooltip appears below the field with a suggested value pulled from your profile or generated by an LLM. You decide whether to accept it, dismiss it, or regenerate with feedback.
Two suggestion modes:
- Simple fields (name, email, phone, location, LinkedIn, GitHub, etc.) — resolved instantly from your saved profile using deterministic lookup with fuzzy matching. No network call.
- Complex fields (cover letters, "why this company", salary, visa/sponsorship, open-ended questions) — sent to an LLM that reads your full profile plus the job description extracted from the current page. The suggestion includes a collapsible "Why this answer?" reasoning section.
Regeneration with feedback — if the suggestion isn't right, click Regenerate, type optional context ("prefer $130k+", "mention my Python background", "make it shorter"), and the LLM incorporates your feedback in the next attempt.
Supported sites: Greenhouse, Workday, Lever, LinkedIn, Indeed, SmartRecruiters, Jobvite, Taleo, iCIMS, BambooHR, Ashby, Rippling, ApplyToJob.
┌─────────────────────────────────────────────────────┐
│ Chrome Extension │
│ │
│ ┌──────────┐ ┌──────────────────────────────┐ │
│ │ Popup │ │ Content Script │ │
│ │(React/TS)│ │ detector → classifier → │ │
│ │ │ │ suggester → tooltip UI │ │
│ │ Auth + │ │ (Shadow DOM, per-field) │ │
│ │ Profile │ └──────────────┬───────────────┘ │
│ └────┬─────┘ │ chrome.runtime │
│ │ chrome.storage.local │ .sendMessage │
│ │ (JWT + profile) │ │
│ ┌────┴────────────────────────▼───────────────┐ │
│ │ Service Worker (background) │ │
│ │ - reads JWT from storage │ │
│ │ - session cache (SHA-256 key per field) │ │
│ │ - proxies to Supabase Edge Function │ │
│ └────────────────────────┬────────────────────┘ │
└───────────────────────────│─────────────────────────┘
│ HTTPS (Bearer JWT)
┌─────────────────▼──────────────────┐
│ Supabase (cloud) │
│ │
│ Auth ──► profiles table (RLS) │
│ │ │
│ Edge Function: /suggest │
│ - verifies JWT │
│ - fetches profile from DB │
│ - calls Groq LLM │
│ - returns { reasoning, │
│ suggestion } │
└─────────────────────────────────────┘
| File | Role |
|---|---|
content/detector.ts |
Queries all visible input, textarea, select elements; extracts a human-readable label via a 5-step chain (aria-label → associated <label> → placeholder → name → id) |
content/classifier.ts |
Decides whether a field is simple (deterministic lookup), complex (LLM), or skip (password, hidden, etc.) based on label keywords |
content/field-map.ts |
~60-entry lookup table mapping normalized label strings to dot-paths in ProfileData; includes Levenshtein fuzzy matching (distance ≤ 2) for typos and variations |
content/jd-extractor.ts |
Extracts the job description from the current page using site-specific selectors (Workday data-automation-id, Greenhouse #content, etc.), capped at 3000 chars |
content/suggester.ts |
Orchestrates the full suggestion flow: loads profile, calls classifier, resolves simple fields locally or requests LLM suggestions via the service worker, manages tooltip lifecycle |
content/ui/tooltip.ts |
Self-contained Shadow DOM tooltip (style isolation). States: loading spinner → suggested answer + accept/dismiss/regenerate buttons → optional context input for feedback-guided regeneration → collapsible reasoning panel |
background/service-worker.ts |
Chrome MV3 service worker. Reads JWT directly from chrome.storage.local (no Supabase import — avoids WebSocket crash). Maintains a chrome.storage.session cache keyed by SHA-256 of fieldLabel + pathname. Proxies SUGGEST_FIELD messages to the Edge Function |
popup/ |
React app for sign-in (Supabase email/password auth) and profile editing. Writes profile to both Supabase Postgres and chrome.storage.local so the content script can read it synchronously |
shared/types.ts |
ProfileData type: personal, education[], experience[], projects[], skills[] |
| Component | Role |
|---|---|
migrations/001_profiles.sql |
profiles table with JSONB columns for each profile section. Row-level security ensures users can only read/write their own row. A Postgres trigger auto-creates an empty profile row on signup |
functions/suggest/index.ts |
Deno Edge Function. Verifies the JWT, fetches the caller's profile, builds a structured prompt (profile + job description + field label + optional user feedback), calls Groq llama-3.3-70b-versatile, parses the JSON response, returns { reasoning, suggestion, source } |
User focuses field
│
▼
detector.ts finds element + label
│
▼
classifier.ts → "complex"
│
▼
suggester.ts shows loading tooltip
│
▼ chrome.runtime.sendMessage("SUGGEST_FIELD", { label, jobDescription, pathname, userContext })
│
▼
service-worker.ts
├─ cache hit? → return cached { reasoning, suggestion }
└─ cache miss → fetch Edge Function with JWT
│
▼
Edge Function
├─ verify JWT
├─ load profile from DB
├─ buildPrompt(label, jobDescription, profile, userContext)
└─ Groq LLM → parse JSON → return { reasoning, suggestion }
│
▼
service-worker caches result, sendResponse back
│
▼
tooltip.ts resolves: shows suggestion + reasoning toggle
│
▼
User: Accept → applyValue() (native setter + input/change events for React/SPA compat)
└── Regenerate → context input → re-runs with userContext injected into prompt
# 1. Install extension dependencies
cd extension
npm install
# 2. Copy env file and add your Supabase project URL
cp .env.example .env
# VITE_SUPABASE_URL=https://<ref>.supabase.co
# VITE_SUPABASE_ANON_KEY=<anon key>
# 3. Build
npm run build
# Output: extension/dist/
# 4. Load in Chrome
# chrome://extensions → Developer mode → Load unpacked → select extension/dist/
# 5. Deploy the Edge Function
npx supabase functions deploy suggest
# Set GROQ_API_KEY in Supabase dashboard → Project Settings → Edge FunctionsNo auto-fill — the extension only suggests. The user explicitly clicks "Use this". This avoids overwriting fields the user has already typed into and keeps the experience trustworthy.
Profile stays local — profile data is written to chrome.storage.local by the popup, so simple-field lookups are instant and work offline. The Edge Function reads from Postgres only for LLM calls (where network latency is already unavoidable).
Service worker has no Supabase import — importing the Supabase JS client into a service worker causes a crash because Supabase Realtime tries to open a WebSocket, which MV3 service workers don't support. The JWT is read directly from storage instead.
Shadow DOM tooltip — style isolation prevents the host page's CSS from affecting the tooltip and vice versa. The host element uses position: fixed; z-index: 2147483647 to stay above page content.
Pathname-scoped cache — suggestions are cached per SHA-256(fieldLabel + pathname) for the browser session. Using pathname (not hostname) means different job postings on the same site get fresh LLM suggestions.