ApplyOS

The local-first job hunt command center

Why this exists

Applying to jobs means re-typing the same 40 fields — name, links, notice period, "why us?" — hundreds of times. Then losing track of which company you applied to, which email you used on Naukri, and whether you already sent that cover letter to this recruiter three weeks ago.

The tools that exist are either SaaS platforms that want your data and a subscription, or spreadsheets that don’t help you fill the form. Neither solves the actual problem.

ApplyOS started as a personal itch: store every answer once, resolve template variables at copy-time, and have a browser extension that fills the form in one click. The local-first constraint came from the same instinct — job-hunt data is sensitive (where you’re interviewing, your salary expectations, your visa status), and I didn’t want to hand it to a server.

It ended up being a system I use every day. So I finished it properly.

Build timeline

Nov 2024

Ideation

Applied to my 200th job. Typed my notice period for the 200th time. Opened a new file and wrote: 'what if I only had to type this once?' That was the brief.

Dec 2024

Design & scoping

Decided on local-first (no backend, no accounts, no trust required). Mapped the core loop: store once → tailor fast → fill automatically → track everything. Designed the Vault schema and the field-scoring approach for autofill.

Jan 2025

MVP

Basic Vault + snippet system working. Chrome extension filling Greenhouse and Lever forms. Template variables resolving at copy-time. It was janky, but it worked on my machine and saved me 20 minutes the same day.

Feb 2025the hard part

The part that broke everything

React-controlled inputs silently ignored direct value writes. Three days debugging before I found the native prototype value setter trick — the same mechanism real browser autofill uses. Also discovered that 6 of the 9 target ATSs had some variation of this problem. The fix generalized to all of them.

Mar 2025

AI layer + Tailor module

Added offline keyword extraction (100+ skill dictionary, deterministic, no API needed) and BYOK Anthropic integration. Designed the graceful degradation path so the app is 100% useful without an API key — AI is additive, not required.

Apr–May 2025

v2: Multi-profile + encrypted passwords + auto-capture

Added multiple personas (Dev roles vs. Analyst roles) with per-profile overrides and conversion analytics. Built the AES-256-GCM credential vault with PBKDF2 key derivation and in-memory auto-lock. Extension now auto-captures company/role via JSON-LD → URL patterns → DOM fallback.

Jun 2025

Polish & launch

Dexie v1→v2 lossless migration. Duplicate warning for re-applications. Passphrase rotation with full re-encryption. Docs, cleanup, and the README that actually explains how the extension sync works.

The interesting technical decisions

1

Heuristic autofill that generalizes

Instead of site-specific scrapers, every input is scored across 7 signal types (label, placeholder, name, id, aria-label, aria-describedby, autocomplete). Highest scorer wins. This means it works on ATSs it has never seen, and doesn't break when one changes its CSS class names.

2

React-proof form filling

React-controlled inputs ignore direct value writes. The extension writes through the native prototype's value descriptor and dispatches input + change + blur events — the same mechanism real browser autofill uses. Every framework treats it as a real keystroke.

3

Zero-config extension sync

No extension IDs, no native messaging, no backend. The web app broadcasts vault data via window.postMessage; a bridge content script on localhost:3000 copies it into chrome.storage. The extension reads from chrome.storage. Two moving parts, zero configuration.

4

Structured data–first job capture

Most ATS pages embed JSON-LD JobPosting markup for Google Jobs indexing. The extension parses that first (exact company/role), falls back to URL slug patterns, then falls back to the page's h1. Captures queue in chrome.storage and drain into IndexedDB the next time the app opens.

5

Crypto done properly

PBKDF2-SHA256 (310k iterations) → AES-256-GCM with a fresh random IV per secret. The derived key is non-extractable, in-memory only, auto-wiped after 5 idle minutes. No recovery path by design. A canary ciphertext confirms the passphrase on unlock so 'wrong passphrase' is a specific error.

Tech stack

Next.js 14 (App Router)File-based routing and a stateless API proxy route for the BYOK key — zero infra, one deployment.
TypeScriptThe Dexie schema, crypto layer, and field-scorer all have enough moving parts that types are load-bearing, not decorative.
Tailwind CSSHand-rolled component classes in globals.css mean zero runtime JS and a design system I can hold in my head.
Dexie 4 (IndexedDB)useLiveQuery gives reactive UI from a local database. No state management library needed; the DB is the source of truth.
Anthropic API (BYOK)Kept AI optional and user-supplied — no ongoing API cost, no data leaving without the user's key, graceful degradation.
Chrome Manifest V3 (vanilla JS)No build step, no bundler, loads immediately in dev mode. A content script this focused doesn't need a framework.
Web Crypto APIPBKDF2 + AES-GCM in the browser's own crypto engine. Non-extractable keys, no third-party library surface area.

Project links

Source, writeup, and the developer behind it.

Want to build something?

Have a feature idea, a collaboration in mind, or just want to talk shop? I’m easy to reach.