6.9 KiB
JobSpeed - AI Coding Agent Instructions
Project Overview
JobSpeed is a Firefox browser extension (Manifest V3) that auto-fills job application forms using parsed resume data. Built with TypeScript (strict mode), Vite, and native Browser WebExtensions API. The project uses a Turborepo monorepo structure with two packages: the main extension and a demo React app for testing.
Key Features
- 📄 Resume Storage: Upload and store resume (PDF/DOCX) securely in extension
- 🔍 Smart Form Detection: Automatically detect form fields on job application pages
- 👀 Preview Mode: See what content will be filled before applying changes
- ⚡ One-Click Auto-fill: Apply resume data to forms with a single click
- 🎯 Intelligent Matching: Smart field matching based on labels and field names
Architecture & Data Flow
Three-Script Extension Architecture
- Background Script (
src/background.ts): Service worker managing storage viabrowser.storage.local, message routing between popup and content scripts - Content Script (
src/content.ts): Injected into web pages, detects forms, creates preview overlays, and fills fields - Popup/Upload UI (
src/popup.tsx,src/upload.tsx): React-based UI for resume upload and extension control
Critical Message Flow: Popup → Background (storage operations) → Content (page manipulation). All inter-script communication uses browser.runtime.sendMessage() with async response handling (return true to keep channel open).
Resume Parsing Pipeline
- Input: PDF/DOCX files via
upload.tsx - Parsing:
resumeParser.ts→ usespdfjs-dist(worker configured atimport.meta.url) andmammothfor extraction - Sectionization:
parsers/sectionizer.tssplits raw text into logical sections (header, summary, workExperience, education, skills) using keyword mapping - Storage: Structured
ResumeDatainterface stored inbrowser.storage.local
Form Detection & Matching
- Detection (
services/formDetector.ts): Queries visible, enabled text inputs/textareas/selects, extracts labels from<label for="">, parent labels, or nearby text - Matching (
services/fieldMatcher.ts): Fuzzy text matching on normalized field text (label + name + id) againstResumeDataproperties. Priority: specific fields (firstName, lastName) before generic (name)
Monorepo Structure
Turborepo Configuration
- Root package: Extension source in
src/ - Workspaces:
job-application-demo/(defined inpnpm-workspace.yaml) - Shared commands:
pnpm turbo:build,pnpm turbo:test,pnpm turbo:lintrun tasks across all packages with caching - Filtering: Use
--filter=job-application-demoto target specific packages
Demo App (job-application-demo/)
- Purpose: Material UI job application form matching extension's
ResumeDatainterface for E2E testing - Testing: Vitest for unit tests + Playwright for E2E (configs:
playwright.config.ts,vitest.config.ts) - Build: Standalone Vite app, runs on
localhost:5173in dev mode
Development Workflows
Extension Development
pnpm dev # Watch mode, rebuilds to dist/ on changes
# Load in Firefox: about:debugging → Load Temporary Add-on → dist/manifest.json
# After changes: Click "Reload" in about:debugging (auto-rebuild in dev mode)
Build Process Gotchas
- Upload UI: Separate React build via
scripts/build-upload.mjs(runs after main build) - Icons: Must manually copy via
pnpm copy:icons(included inpnpm build) - Vite Plugin: Uses
vite-plugin-web-extensionwith Firefox browser target invite.config.ts
Testing Strategy
- Extension Unit Tests:
pnpm test(root) - tests services likefieldMatcher.test.ts - Demo Unit Tests:
cd job-application-demo && pnpm test- tests React components - E2E Tests:
cd job-application-demo && pnpm test:e2e- Playwright tests against demo form - CI: Uses custom Docker image (
ghcr.io/.../jobspeed-playwright-runner:latest) with Node 22, pnpm 10.18.1, Playwright pre-installed
Project-Specific Conventions
TypeScript Patterns
- Strict mode enabled (
tsconfig.json): All files must handle nullability, avoidany - Browser API types: Reference
/// <reference types="firefox-webext-browser" />at top of files usingbrowser.*APIs - Type imports: Use
import typefor interfaces/types (e.g.,import type { ResumeData } from './types')
Message Handling Pattern
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Async operations: MUST return true to keep channel open
doAsyncWork().then(result => sendResponse({ success: true, data: result }));
return true;
});
Field Matching Order
fieldMatcher.ts checks specific fields before generic (e.g., firstName/lastName before fullName) to prevent incorrect matches. When adding new fields, place specific matchers before generic ones.
CSS Isolation for Content Scripts
Preview panel uses #jobspeed- prefixed IDs and .jobspeed- classes to avoid conflicts with host page styles.
Key Files & Patterns
src/manifest.json: Manifest V3 config, defines permissions (storage,activeTab,scripting), host_permissions, and script loadingturbo.json: Task dependency graph, definesoutputsfor caching (e.g.,dist/**,coverage/**)pnpm-workspace.yaml: Defines workspace packages, includesonlyBuiltDependencies: [esbuild]for install optimizationscripts/Dockerfile: Ubuntu 24.04 image for CI/CD Playwright tests, synced withpnpmversion (10.18.1)
Common Pitfalls
- PDF.js Worker Path: Must configure
pdfjsLib.GlobalWorkerOptions.workerSrcusingimport.meta.url(seeresumeParser.ts) - Extension Reload: Changes to manifest.json require full extension reload (unload + re-load), not just "Reload" button
- Content Script Timing: Content script may run before page DOM ready - check
document.readyStateor useDOMContentLoaded - Turborepo Cache: Use
turbo run build --forceto skip cache when troubleshooting build issues - E2E Tests: Require
pnpm devserver running or CI webServer config inplaywright.config.ts
CI/CD Details
- Status Checks (
.github/workflows/status-checks.yml): Runs extension unit tests, demo unit tests, and demo E2E tests on PRs - Playwright Image (
.github/workflows/build-playwright-image.yml): Auto-builds Docker image on Dockerfile changes, pushes to ghcr.io - Container Auth: E2E job uses
${{ secrets.GITHUB_TOKEN }}to pull private ghcr.io images
Debugging Tips
- Background logs: Browser Console (Ctrl+Shift+J or about:debugging)
- Content script logs: Page Web Console (F12 on target page)
- Popup logs: Right-click extension icon → Inspect → Console
- Storage inspection: about:debugging → Storage tab or use
browser.storage.local.get()in console