A local-first, professional resume & CV builder that runs entirely in your browser.
Resume Workbench lets you create, edit, design, analyze, and export resumes without uploading anything to a server. All documents are stored in IndexedDB on your own device, and every feature — from WYSIWYG editing to ATS checking to PDF export — happens locally.
Built with React 18, TypeScript, Vite, Tailwind CSS, and Zustand.
- Features
- Tech Stack
- Getting Started
- Project Structure
- The Document Model
- Local Storage
- Using the App
- Responsive Design
- Scripts
- Testing
- Data Schemas & Validation
- True WYSIWYG canvas — click any text on the rendered resume to edit it in place
- Live pagination — the document reflows and re-paginates as you type, in single-page or continuous-page view
- Zoom controls — zoom in/out from 40% to 200%, with one-click reset
- Rich text — bold, italic, and other markup with sanitization (DOMPurify)
- Undo / redo — up to 120 history snapshots with debounced history commits
- In-page search — find and jump to any text in the document
- 20+ built-in section types: Summary, Experience, Education, Skills, Projects, Certifications, Awards, Publications, Research, Volunteer, Leadership, Languages, Interests, Coursework, Conferences, Memberships, References, Achievements, Hobbies, and Custom
- Add, reorder, hide/show, and delete sections from the Sections panel
- Reorder entries within a section and move sections up/down
- Per-entry fields tailored to the entry type (role/company, degree/institution, etc.), with dates, bullets, technologies, URLs, and more
- 18 built-in templates in four categories:
- ATS (5): Harvard Professional, Classic ATS, Minimal ATS, Executive ATS, Technical ATS
- Modern (6): Modern Professional, Clean Modern, Elegant, Corporate, Minimalist
- Technical (5): Software Engineer, Full Stack Developer, DevOps Engineer, Data Engineer, Cloud Engineer
- Academic (3): Academic CV, Research CV, Graduate CV
- Typography — 6 bundled fonts (Inter, Roboto, Open Sans, Source Sans 3, Lato, EB Garamond) with control over base size, name size, section headings, line height, letter spacing, weights, small caps, and uppercase headings
- Theme controls — primary/accent/text/heading/link colors, heading rules and dividers
- Per-section styling — heading rule, color, weight, transform, letter spacing, spacing, divider, and column layout
- Save your own designs as reusable templates and re-apply them later
- Paper sizes — Letter, A4, Legal, and custom dimensions; portrait or landscape
- Margins — per-side control in millimeters
- Page numbers — off,
{page},{page} / {pages}, name, or name + page - Headers & footers — enable/disable, custom text with placeholders
(
{name},{title},{email},{phone},{location},{page},{pages}), alignment, font size, gap, and "show on first page"
- 4 built-in style presets — Classic Black, Modern Navy, Elegant Serif, Clean Minimal — each bundling a coordinated theme, typography, page setup, and per-section styling
- Apply with one click from the presets dialog, toolbar, or command palette; changes are undoable (they run through the same history system as every other edit)
- Save any current design as a reusable custom preset; edit or delete your own presets (built-ins are read-only)
- Structured ↔ Freeform mode — toggle the whole canvas from content-driven layout to a free-positioned design surface
- Free-positioned elements — text, headings, lines, rectangles, dividers, lists, icons, and images placed by x/y coordinates in millimeters
- Per-element styling — font, weight, align, color, letter spacing, line height, opacity, rotation, lock, and visibility
- Canvas helpers — optional grid, snap-to-grid, alignment guides, and a ruler
- Duplicate, delete, and multi-select elements; every action is history-aware (undo/redo)
- Dedicated cover-letter view — create, edit, rename, duplicate, and delete letters from the Cover Letters page (toolbar "Letters" button or command palette)
- Start blank or "New from resume" — a letter can be seeded from any existing resume, inheriting your personal info, signature, colors, and typography
- WYSIWYG body editor — recipient details (name, title, company, address), date, salutation, a live-rendered letter canvas, and editable body paragraphs with add/remove controls
- Layout mirrors your resume — the letter uses the same theme, fonts, and page setup as your documents, so a letter and resume match visually
- Export & print — save a plain-text version or open a styled print window for PDF
- Autosave — every keystroke in the letter editor is saved with a short debounce
- Track your job hunt — companies, contacts, job postings, applications, and interviews, all cross-linked in one place
- Application snapshots — capture the exact resume, cover letter, and job posting used for any application
- Career asset library — reusable snippets in 18 categories (experience, projects, skills, certifications, and more) with per-category field schemas and version history
- Skill matrix — group skill assets by category with 5-segment proficiency ratings
- Tasks & follow-ups — task-driven to-do items linked to applications, postings, companies, contacts, and interviews, with due dates, reminders, and priority
- Asset-driven writing — insert saved assets straight into resumes and cover letters from the asset picker
- Backup & restore — export and import your entire local database (all stores) as a single JSON file
- Dark mode — light, dark, and system (follows OS) themes, applied app-wide to the shell
- Quick toggle — switch themes from the toolbar (sun/moon button), Settings, or the
command palette (
Toggle Dark Mode) - The resume canvas itself always stays white — it represents a printable page
- ATS Compatibility Check — score your resume against common applicant-tracking-system parsing rules
- Resume Quality Check — automated feedback on structure, content density, and readability
- Keyword Checker — paste a job description and see which keywords your resume matches
- Compare Versions — diff any two snapshots of the same resume
- Preflight export check — warns about empty sections, missing name/contact, etc. before export
- Named snapshots — save a point-in-time copy of any resume and restore it later
- Compare — pick any two snapshots and diff them line by line
- Delete — remove individual snapshots; snapshots are stored separately from the live document, so restoring never overwrites your current draft history
- Export:
- PDF / Print via a styled print window (browser "Save as PDF")
- JSON (
.resume.json) — lossless, round-trippable - Markdown (
.md) - Plain text (
.txt)
- Cover letter export — plain text (
.txt) and PDF / print with matching styles - Import:
- Resume Workbench JSON (wrapped or bare document)
- Saved-template JSON
- Snapshots (versions) — save named snapshots of any resume and restore or diff them later
- Autosave — debounced autosave (default 1.5 s) with save-on-tab-hide and save-on-unload
- Local-first — all data lives in IndexedDB on your device; nothing is uploaded
- Settings persisted to
localStoragefor synchronous reads
| Layer | Technology |
|---|---|
| UI | React 18 + TypeScript |
| Build | Vite 5 |
| Styling | Tailwind CSS 3 (CSS variables + dark mode) |
| State | Zustand |
| Storage | IndexedDB via idb + localStorage |
| Validation | Zod |
| Components | Radix UI primitives + custom shadcn-style UI |
| Icons | Lucide React |
| Sanitizer | DOMPurify |
| Testing | Vitest + Testing Library (jsdom) |
- Node.js 18+ and npm
npm installnpm run devOpen the printed URL (default http://localhost:5173).
npm run buildThe output is written to dist/. Serve it with any static host or preview it locally:
npm run previewresume-workbench/
├── index.html
├── package.json
├── tailwind.config.ts
├── tsconfig.json
├── vite.config.ts
└── src/
├── main.tsx # App entry
├── App.tsx # Shell: toolbar, view switch, command bus, shortcuts
├── types/
│ └── resume.ts # Core domain types (document, entries, templates, settings)
├── validators/
│ └── resumeSchema.ts # Zod schemas for documents & templates
├── services/
│ ├── db.ts # IndexedDB store + settings persistence
│ ├── export.ts # JSON/Markdown/text export + PDF/print pipeline
│ ├── import.ts # JSON & template import + normalization
│ └── backup.ts # Full-database backup & restore
├── store/
│ ├── useDocuments.ts # Resume/template/snapshot collections, views
│ ├── useEditor.ts # Open document, selection, history, panels, zoom
│ ├── useUi.ts # Dialogs, command palette, confirm modal
│ ├── useSettings.ts # App settings (theme, autosave, shortcuts)
│ ├── useCoverLetters.ts # Cover letter collection + active draft
│ ├── useStylePresets.ts # Built-in + user style presets
│ ├── useCareer.ts # Companies, contacts, jobs, applications, interviews, snapshots
│ ├── useAssets.ts # Career asset library + version history
│ ├── useTasks.ts # Tasks & follow-ups
│ └── useCareerNav.ts # Career-hub tab state
├── hooks/
│ ├── useAutosave.ts # Debounced autosave + beforeunload/visibility flush
│ ├── useTheme.ts # Applies light/dark/system theme to the shell
│ └── useKeyboardShortcuts.ts
├── lib/
│ ├── commands.ts # Command palette command registry + event bus
│ ├── resumeFactory.ts # Blank docs, duplicates, template application
│ ├── sections.ts # Section type metadata & creation
│ ├── sanitize.ts # DOMPurify wrapper / markup stripping
│ ├── fonts.ts # Font stack resolution
│ ├── freeform.ts # Freeform layout normalization helpers
│ ├── assetToResume.ts # Career asset → resume/letter section mapping
│ ├── taskSuggestions.ts # Follow-up suggestions from stale applications
│ └── id.ts # ID & timestamp helpers
├── templates/
│ ├── catalog.ts # 18 built-in template definitions
│ └── stylePresets.ts # Built-in + custom style preset helpers
├── features/
│ ├── ats/analyze.ts # ATS compatibility scoring
│ ├── quality/analyze.ts # Resume quality feedback
│ ├── keywords/match.ts # Job-description keyword matching
│ └── compare/diff.ts # Snapshot diffing
├── components/
│ ├── dashboard/Dashboard.tsx
│ ├── editor/ # EditorShell, EditorPane, SidePanel, InspectorPanel, FreeformPanel, ...
│ ├── resume/ # ResumeCanvas, ResumeFlow, blocks, RichText
│ ├── cover/ # CoverLettersView, CoverLetterEditor, CoverLetterBody
│ ├── career/ # Career hub: Companies, Contacts, JobBoard, Applications, Interviews, SkillMatrix, AssetsLibrary, TasksView
│ ├── dialogs/ # ATS, Quality, Keywords, Compare, Export, Import, Style Presets, ...
│ ├── toolbar/ # Toolbar + StatusBar
│ ├── settings/SettingsDialog.tsx
│ ├── common/ # CommandPalette, ConfirmDialog
│ └── ui/ # Reusable shadcn-style primitives
└── test/
└── setup.ts # Vitest setup (jest-dom, matchMedia, confirm)
A resume is a single ResumeDocument object:
| Field | Description |
|---|---|
id |
Unique ID |
name |
Display name |
personal |
Full name, job title, contact fields, links |
sections |
Ordered, typed sections, each with visible flag, style, and entries |
theme |
Colors, heading rules, divider style |
typography |
Fonts, sizes, weights, spacing, transforms |
pageSetup |
Paper size, orientation, margins, page numbers |
header / footer |
Header/footer config + placeholder tokens |
metadata |
Template reference, ATS mode, language, tags, timestamps |
Section entries are discriminated by entry.type (experience, education, skills, projects,
etc.), so the editor, renderer, and exporters all branch on the typed structure.
Documents are validated against Zod schemas in src/validators/resumeSchema.ts on import, so
malformed files are rejected with a readable error instead of crashing the renderer.
A cover letter is a separate CoverLetterDocument model sharing the same theme, typography,
and page setup as resumes:
| Field | Description |
|---|---|
id, name |
Unique ID and display name |
personal |
Full name, title, email, phone, location |
recipient |
Name, title, company, address |
date |
Letter date |
salutation / closing / signature |
Greeting, sign-off, signature |
body |
Array of paragraphs |
theme / typography / pageSetup / header / footer |
Shared styling |
Cover letters are created blank or seeded from a resume with
buildCoverLetterFromResume (src/lib/resumeFactory.ts), which copies your personal info and
matches the resume's visual design so the pair looks consistent.
Data never leaves your device.
IndexedDB database resume-workbench (schema v5):
Store (IndexedDB resume-workbench) |
Key path | Contents |
|---|---|---|
resumes |
id |
ResumeDocument objects |
versions |
id |
Named snapshots, indexed by resumeId |
templates |
id |
User-saved templates |
coverLetters |
id |
CoverLetterDocument objects |
stylePresets |
id |
User-created style presets |
companies |
id |
Job-hunt companies |
contacts |
id |
Contacts at companies |
jobPostings |
id |
Job postings |
applications |
id |
Job applications |
interviews |
id |
Interview events |
applicationSnapshots |
id |
Resume/letter/posting snapshots per application |
assets |
id |
Career asset library snippets |
assetVersions |
id |
Asset version history |
tasks |
id |
Tasks & follow-ups |
meta |
key |
App metadata (incl. settings mirror) |
Settings are also mirrored to localStorage (rw.settings.v1) so they can be read
synchronously on startup. Deleting the resume list deletes its snapshots too.
Backups capture all 15 stores in a single JSON file.
- Lists all resumes saved on the device, with section counts and last-updated dates
- Create a new resume (blank or from a template), import a file, or open the template gallery
- Per-resume actions: Rename, Duplicate, Delete (with confirmation)
The editor is a three-region workspace:
- Left — Sections panel: reorder, show/hide, add, and delete sections; jump to a section
- Center — Canvas: the live, zoomable resume; click any text to edit in place
- Right — Header & Footer inspector: configure header/footer text, alignment, and page numbers
Both side panels can be toggled from the toolbar. On screens narrower than 1024 px they open as overlay drawers with a backdrop; picking a section closes the panel so you can see the result. Panels default to closed on small screens and open on desktop.
- Switch the canvas from structured to freeform layout from the Sections panel or inspector; the mode is stored per document and can be toggled back any time
- In freeform mode, use the Freeform panel to add text, headings, lines, rectangles, dividers, lists, icons, and images, then drag them anywhere on the page
- Select elements directly on the canvas (or via the panel), and use duplicate, delete, and multi-select to arrange them
- Enable the grid, snap-to-grid, and guides for precise alignment; adjust element opacity, rotation, locking, and visibility per element
- All freeform edits are captured by the same undo/redo history as structured edits
- Open the Template Gallery to preview the 18 built-ins and apply one to the current resume
- Apply from the gallery, from the command palette (
Apply: <template>), or via the toolbar - Save current design (
Ctrl+Alt+Sequivalent via command) to create a reusable template - Recent templates are tracked in settings for quick re-application
- Click the Styles toolbar button (or use the command palette) to open the Style Presets dialog
- Choose one of the four built-in presets to instantly restyle the current resume — theme, typography, page setup, and per-section defaults all update together
- Use Save current to store your own palette/typography as a preset; rename or delete custom presets from the same dialog
- Applied presets are undoable, so you can freely experiment
- Open the Cover Letters page from the toolbar Letters button, the dashboard, or the
command palette (
Open Cover Letters) - New Letter creates a blank letter; New from resume lets you pick any resume to seed the letter with your contact info, signature, and matching visual style
- Click any saved letter to open the editor: edit recipient details, date, salutation, and body paragraphs on the left while watching the letter render live on the right
- The editor autosaves as you type; use the toolbar buttons to export as plain text or to open the styled print window (PDF)
- Use the Back button, the toolbar, or the command palette to return to the letter list
- Open the Job Hunt page from the dashboard or the command palette (
Open Job Hunt) - Companies, Contacts, Jobs, Applications, and Interviews tabs with full CRUD and cross-linking (jobs link to companies, applications link to jobs, etc.)
- Skills tab — skill matrix grouping skill assets by category with proficiency bars
- Assets tab — the career asset library with 18 categories, version history, favorites, archiving, and duplicate
- Tasks tab — tasks & follow-ups with due dates, reminders, priority, and status
- Application snapshots — when you apply, capture the exact resume, cover letter, and posting used so you can revisit what you sent
- Export Backup (Dashboard header, command palette, or
Export Backup) downloads all 15 stores as a single.jsonbackup file - Import Backup restores a previous backup, replacing all stored data with a per-store record count report
- Backups are validated on import; malformed or wrong-app files are rejected
The Tools menu and command palette expose:
- ATS Check — compatibility scoring for ATS parsing
- Quality Check — structural and content feedback
- Keyword Checker — match your resume against a job description
- Compare Versions — diff two snapshots
- Snapshots — save named versions and restore them later
- Page Setup — paper size, orientation, margins, page numbers
- Open the Snapshots dialog from the toolbar (camera icon) or the Tools menu
- Save snapshot captures the current resume with a label of your choice
- Restore replaces the working copy with a snapshot; Compare diffs any two snapshots side by side; Delete removes a snapshot permanently
- Export PDF / Print: opens a styled print window with matching fonts and CSS
@pagerules (headers, footers, page numbers), then lets you save as PDF or print - Export JSON: lossless
.resume.jsonround-trip - Export Markdown / Plain text: for ATS-friendly pasting or further editing
- Cover letters: export as plain text or open the same styled print window for PDF
- Import: accepts wrapped or bare Resume Workbench JSON and template JSON
- Theme: light / dark / system (applied app-wide; quick toggle in the toolbar)
- Default template for new resumes
- Autosave toggle and interval
- Keyboard shortcuts on/off
- Reduced motion
| Shortcut | Action |
|---|---|
Ctrl/Cmd + K |
Open command palette |
Ctrl/Cmd + S |
Save resume |
Ctrl/Cmd + Z |
Undo |
Ctrl/Cmd + Shift + Z, Ctrl/Cmd + Y |
Redo |
Ctrl/Cmd + F |
Search in resume |
Ctrl/Cmd + P |
Print / export PDF |
Esc |
Close dialog / clear selection |
Ctrl/Cmd + K opens a fuzzy-search palette with commands across File, Edit, View,
Template, Section, Export, Tools, and Settings categories, including hidden
commands such as adding any section type, applying a specific template, opening the style
presets, and cover-letter actions (Open Cover Letters, New Cover Letter,
New Cover Letter from Resume), Toggle Dark Mode, career navigation (Open Job Hunt,
Open Tasks & Follow-ups), and backup (Export Backup, Import Backup).
- Desktop (≥1024 px): full three-region workspace with both side panels docked
- Tablet / phone (<1024 px):
- Side panels become slide-over drawers with a dimmed backdrop; tap the backdrop to close
- Opening a document starts with panels closed so the canvas is immediately usable
- Selecting a section auto-closes the sections panel
- The toolbar scrolls horizontally (scrollbar hidden) so every control stays reachable
- Less-critical toolbar buttons (Templates below
md, Page Setup belowlg) collapse into the Tools menu / command palette - Dashboard cards reflow from 3 → 2 → 1 columns
- Editor canvas wraps its header controls and re-paginates to the available width
| Script | Description |
|---|---|
npm run dev |
Start the Vite dev server |
npm run build |
Type-check (tsc -b) then production build |
npm run preview |
Preview the production build |
npm run typecheck |
TypeScript check without emitting |
npm run test |
Run Vitest once |
npm run test:watch |
Run Vitest in watch mode |
npm run lint |
ESLint the src directory (requires eslint) |
Note:
npm run lintis declared inpackage.jsonbuteslintis not currently installed and there is no ESLint config file. Runnpm install -D eslintand add aneslint.configif you want to use it.
Unit tests run on Vitest with jsdom and Testing Library.
npm test # run once
npm run test:watch # watch modeThe suite covers the core stores, export/import, backup, and helpers:
| Test file | Covers |
|---|---|
src/store/useEditor.test.ts |
Document open, patch/history, undo/redo, delete, freeform |
src/store/useDocuments.test.ts |
Load/seed, create, open, rename, duplicate, navigation |
src/store/useCoverLetters.test.ts |
Create blank / from resume, patch/save, duplicate/rename/remove, open |
src/store/useStylePresets.test.ts |
Load merge, save current, remove, apply with history |
src/store/useCareer.test.ts |
Companies, contacts, jobs, applications, interviews, snapshots |
src/store/useAssets.test.ts |
Asset CRUD, version history, favorites, archive, duplicate |
src/store/useTasks.test.ts |
Task CRUD, status/due-date transitions, suggestion inputs |
src/services/export.test.ts |
JSON wrap, plain text (resume + letter), preflight checks |
src/services/backup.test.ts |
Backup export, import validation, restore counts |
src/lib/assetToResume.test.ts |
Asset-category → resume/letter section mapping |
src/lib/taskSuggestions.test.ts |
Follow-up suggestions from stale applications |
Test setup lives in src/test/setup.ts (jest-dom matchers, matchMedia, and confirm
stubs). Stores are tested through their public actions with IndexedDB mocked via vi.mock.
src/types/resume.ts— hand-written TypeScript interfaces for the full domain modelsrc/validators/resumeSchema.ts— Zod schemas that mirror the types; used on import to validate and normalize documents and templatessrc/services/import.ts— normalization fills any missing nested fields so rendering and export never crash on partial data
Templates are applied through applyTemplateConfig / withDefaultStyles
(src/lib/resumeFactory.ts), which merge theme, typography, page setup, header/footer, and
per-section defaults into the target document.