Architecture

CareerIndex is a local-first Electron desktop app. All business logic runs in the Electron main process; the renderer (React) communicates exclusively via IPC through a context-bridge preload. SQLite (WAL mode) is the single source of truth for all persistent data.

Contents

Tech Stack

Concern Library
Runtime Electron (main process) + Chromium (renderer)
UI framework React + TypeScript (renderer process)
Browser scraping Playwright (Node.js)
Light scraping / HTTP node-fetch + cheerio
RSS feeds rss-parser
LLM calls Anthropic TypeScript SDK
Structured LLM output validation Zod
Relational storage better-sqlite3 (WAL mode)
DB migrations Custom versioned migration runner
Resume templating Nunjucks + xelatex
PDF preview Electron’s native Chromium PDF renderer
Credential storage keytar (OS keychain)
Packaging + distribution electron-builder (auto-update via electron-updater)

Repository Structure

/
├── electron/
│   ├── main.ts                  # Electron main process entry + IPC handlers + startup validation
│   ├── preload.ts               # Context bridge — exposes safe IPC API to renderer
│   └── connection-manager.ts   # better-sqlite3 instance, WAL setup, write-lock state, IPC event bus
│
├── src/                         # Renderer process (React + TypeScript)
│   ├── App.tsx                  # Root component + view routing
│   └── views/
│       ├── Profile.tsx          # Profile editor
│       ├── SearchConfig.tsx     # Search intent + term review + ban list + keyword filters + ranking weights
│       ├── JobBoard.tsx         # Aggregated postings view + favorites
│       ├── ResumePreview.tsx    # Resume tailoring + PDF preview
│       ├── Tracker.tsx          # Application history (sortable table)
│       ├── Analytics.tsx        # BI metrics dashboard
│       └── Settings.tsx         # API key + preferences + backup
│
├── core/                        # Main-process business logic (TypeScript)
│   ├── profile/
│   │   ├── repository.ts        # CRUD — SQLite is the single source of truth
│   │   └── models.ts            # Zod schemas + inferred types
│   ├── resume/
│   │   ├── agent.ts             # Claude API call + prompt construction
│   │   ├── validator.ts         # Zod schema for structured LLM response
│   │   ├── renderer.ts          # Nunjucks → .tex file
│   │   ├── compiler.ts          # child_process xelatex + error handling + recompile-from-snapshot
│   │   └── previewer.ts         # PDF path → Electron BrowserWindow PDF display
│   ├── jobs/
│   │   ├── aggregator.ts        # Orchestrates scraper mods, owns scrape state machine + staging + pre-commit filtering
│   │   ├── searchTermGen.ts     # Claude-generated per-adapter search term lists from global user intent
│   │   ├── scorer.ts            # Sequential LLM affinity scoring + cache logic + skip threshold
│   │   ├── ranker.ts            # Post-commit filtering (ban list, keywords, YOE) + composite score assembly
│   │   └── adapters/
│   │       ├── base.ts          # Abstract adapter interface + available_signals declaration
│   │       ├── linkedin.ts      # Playwright-based scraper + hardcoded normalization
│   │       ├── indeed.ts        # Playwright-based scraper + hardcoded normalization
│   │       ├── hackernews.ts    # node-fetch + Algolia API + hardcoded normalization
│   │       └── rss.ts           # rss-parser generic feed adapter + hardcoded normalization
│   └── tracker/
│       ├── repository.ts        # CRUD for application records
│       ├── analytics.ts         # Aggregated BI queries over application history
│       └── models.ts
│
├── db/
│   ├── database.ts              # better-sqlite3 instance factory, WAL mode setup
│   ├── migrations/              # Versioned migration scripts (plain SQL + metadata)
│   └── schema.sql               # Human-readable schema reference
│
└── templates/resume/
    ├── classic.tex.njk
    └── modern.tex.njk

Process Boundary

Renderer (React)  ──IPC──►  Main process  ──direct──►  SQLite (WAL)
                                  │
                             Worker thread
                             (scraper mods)
                                  │
                        postMessage → main
                        (staged results)

The renderer never touches SQLite directly. All Node.js access goes through the context bridge in preload.ts.


End-to-End Data Flow

SQLite (profile_entries)
    └──► Resume Engine (main process)
              └──► Claude API ──► Zod validation ──► Nunjucks
                       │                                  │
                   llm_usage                          xelatex (child_process)
                                                          │
                                                     .tex + PDF
                                                          │
                                          IPC → Renderer (React)
                                          PDF displayed via Chromium native renderer
                                          User hits Apply
                                          └──► shell.openExternal(url)
                                          └──► job_postings.status = applied

User enters global intent (Renderer)
    └──► IPC ──► main process ──► Claude API ──► per-adapter SearchTerm lists
                                       │                  │
                                   llm_usage       search_terms table
                                                          │
                                          IPC → Renderer: user reviews + edits
                                          User confirms → crawl starts

Worker thread (scraper mods, rate-limited)
    └──► JobPostings (normalized per-mod) ──► Zod contract check
                                                         │
                                               parse_failed? → log + skip
                                               5 consecutive? → abort mod
                                                         │
                                             dedup (URL hash + composite hash)
                                                         │
                                         In-memory staging buffer (Worker)
                                         postMessage → main process
                                                         │
                                         PRE_COMMIT_FILTER (main, synchronous)
                                           1a. Ban list
                                           1b. Keyword filter
                                                         │
                                         PENDING_COMMIT: IPC → Renderer shows summary
                                         [Commit] → better-sqlite3 bulk insert
                                         [Discard] → staged results dropped
                                                         │
                                    Ranker (main process, runs on every job board load)
                                         Stage 1: Hard Filter
                                           1a. Keyword filter
                                           1b. YOE filter
                                           1c. excluded_stack filter
                                                         │
                                    Stage 2: Conditional LLM Affinity Scoring
                                         Skip if candidates ≤ affinitySkipThreshold
                                         (batched by token budget, cached per posting)
                                                    │
                                                llm_usage
                                                         │
                                    Stage 3: Composite Score Assembly
                                         (adapter-declared signals only;
                                          affinity weight excluded if skipped)
                                                         │
                                    IPC → Renderer: Job Board view (React)
                                         (score + reasoning + badges)

Modules

Module Page
1 — Profile Repository Profile
2 — Resume Engine Resume Engine
3 — Job Aggregation Pipeline Job Aggregation
4 — Matching & Ranking Matching & Ranking
5 — Concurrency Model Concurrency
6 — Application Workflow Application Workflow
7 — Ban List Ban List
8 — Keyword Filtering Keyword Filtering
9 — Affinity Score Skip Threshold Matching & Ranking § Skip Threshold
10 — Analytics Dashboard Analytics
11 — Data Export / Import Data Export & Import
Platform (Startup, Logging, Security, Packaging) Platform

Table of contents


CareerIndex — local-first, all data stays on your machine.

This site uses Just the Docs, a documentation theme for Jekyll.