# Job Automation Agent ATS resume tailoring system: Chrome extension + HuggingFace server pipeline. Extracts JDs from job boards, generates tailored resumes via LLM pool, returns compiled PDFs. ## Scope Lock Do NOT redesign the ATS extractor, scorer, evidence gate, or rewriting architecture. Do NOT work on V2 (multi-agent natural tailoring). Current work is V3 bulk generation. ## Security - Never hard-code API tokens into committed JS/manifest/packaged assets - Never expose server filesystem paths to the client - Sanitize compiler output before returning it - Disable shell escape in LaTeX, use unique temp dirs, apply strict compilation timeouts ## Extension Architecture (MV3) ### Files | File | Role | |------|------| | `extension/manifest.json` | MV3 manifest, `` content script, side panel | | `extension/content.js` | JD extraction + job card scanning, runs on every page | | `extension/popup/popup.js` | Side panel UI: single gen, bulk gen, results, editors | | `extension/popup/popup.html` | Side panel markup (380px popup, 100% sidepanel) | | `extension/background.js` | Service worker: generation lifecycle, SSE streaming, storage persistence | | `extension/options/options.html` | Settings: API URL/token, resume LaTeX, gen version, autofill profile | | `extension/default_resume.js` | Fallback LaTeX resume (`self.DEFAULT_RESUME_LATEX`) | ### Content Script Message Types | Message | Handler | Async | |---------|---------|-------| | `EXTRACT_JD` | `extractJD()` platform router | no | | `SCAN_JOB_CARDS` | `scanJobCards()` / `scanJobCardsLinkedIn()` | no | | `NAVIGATE_EXTRACT_JOB` | `navigateAndExtractJD(jobId)` — LinkedIn pushState | yes (`return true`) | | `FETCH_EXTRACT_JOB` | `fetchExtractJD(url)` — background fetch+parse | yes | | `CLICK_AND_EXTRACT_JOB` | `clickAndExtractJob(index)` — click card fallback | yes | | `SCAN_FORM` | `scanApplicationFields()` | no | | `APPLY_FORM_FILL` | `applyAutofillAnswers(answers)` | no | ### LinkedIn Bulk Extraction (V3) LinkedIn uses SDUI (Server-Driven UI) with virtualized rendering: - `li[data-occludable-job-id]` elements exist for all 25 jobs but only ~7 visible ones have content - Programmatic scroll does NOT trigger hydration - **Solution:** `scanJobCardsLinkedIn()` reads job IDs from ALL `
  • ` elements (even empty placeholders). `navigateAndExtractJD(jobId)` uses `history.pushState` to set `currentJobId` in the URL + `dispatchEvent(popstate)` to trigger LinkedIn's SPA router. Detail pane updates with full JD. - No clicking, no navigation, no profile redirects. - `#job-details` element contains the full JD text in the detail pane. - `expandDetailPane()` clicks only `