← back to the chaos

verified against source, not aspirational

Technology Radar

Adopt, Trial, Assess, Hold — what this app runs on today, and why. Every dependency entry is checked in CI against the real package.json/Cargo.toml files on every push; an entry naming something removed from the stack fails the build instead of quietly going stale.

Tauri 2 — Adopt — Renderer & UI1React 19 — Adopt — Renderer & UI2TanStack Query 5 — Adopt — Renderer & UI3TanStack Router — Adopt — Renderer & UI4Zustand 5 — Adopt — Renderer & UI5Tailwind CSS 4 — Adopt — Renderer & UI6Motion (motion/react) — Adopt — Renderer & UI7Hand-rolled state machine (lib/machine.ts) — Adopt — Renderer & UI8XState — Hold — Renderer & UI9WCAG 2.2 AA — Adopt — Renderer & UI10WebGL / shader-driven landing experiences — Hold — Renderer & UI11SQLite via rusqlite (bundled) — Adopt — Rust Core & Data12Tokio — Adopt — Rust Core & Data13keyring-core (OS-native credential storage) — Adopt — Rust Core & Data14chromiumoxide (headless Chromium automation) — Adopt — Rust Core & Data15reqwest — Adopt — Rust Core & Data16Zod — Adopt — Rust Core & Data17Ollama (local + cloud) — Adopt — Rust Core & Data18Live model listing, no hardcoded defaults — Adopt — Rust Core & Data19A dedicated vector database — Hold — Rust Core & Data20Nominatim — Hold — Rust Core & Data21Typst (typst / typst-pdf / typst-layout / typst-svg) — Adopt — Documents & Export22docx-rs — Adopt — Documents & Export23lopdf — Adopt — Documents & Export24pdf-extract — Adopt — Documents & Export25image (Rust crate) — Adopt — Documents & Export26Dual-engine golden-parity migration — Adopt — Documents & Export27TypeScript — Trial — Build, Ship & Trust28Vite 8 — Adopt — Build, Ship & Trust29Vitest 4 — Adopt — Build, Ship & Trust30Playwright — Adopt — Build, Ship & Trust31Turborepo — Adopt — Build, Ship & Trust32semantic-release — Adopt — Build, Ship & Trust33Husky + lint-staged pre-commit gate — Adopt — Build, Ship & Trust34ESLint + ast-grep architecture guardrails — Adopt — Build, Ship & Trust35Sentry crash reporting — Adopt — Build, Ship & Trust36CodeRabbit — Adopt — Build, Ship & Trust37Next.js (static export) — Adopt — Build, Ship & Trust38Codecov + SonarCloud — Hold — Build, Ship & Trust39React Compiler — Assess — Build, Ship & Trust40

Renderer & UI

AdoptIn production today — the default choice for new work in this area.

  • Tauri 2

    Adopt

    OS-native WebView desktop shell — not Electron.

    Rust backend plus the OS's own WebView (WebView2 on Windows, WebKit on macOS/Linux) instead of bundling Chromium: installers land in the tens of MB rather than 150+ MB, and the renderer only reaches Rust through commands explicitly allowed by Tauri's capability manifest. The trade-off — WebKit on macOS doesn't always render identically to WebView2 — is handled with a cross-platform CSS pass before each release.

  • React 19

    Adopt

    Concurrent rendering, Actions, first-class TanStack support.

    The renderer is React 19.2 throughout apps/desktop and apps/landing — Actions/useActionState for async transitions, ref as a plain prop, and the concurrent-safe integration TanStack Query and Zustand both depend on.

  • TanStack Query 5

    Adopt

    The only sanctioned way for a component to reach IPC.

    Every server-state read/write goes through a service hook in renderer/services/ — no useState + useEffect fetching, no direct window.api access from features/routes/components. Query keys are centralized so cache invalidation after a mutation is deliberate, not guessed at.

  • TanStack Router

    Adopt

    File-based routing with typed route + search params.

    Chosen for compile-time-checked routes and search-param types over a stringly-typed router API — the desktop app has 11+ feature routes, and a renamed route param fails at build time instead of at runtime.

  • Zustand 5

    Adopt

    Minimal client-only state — persisted prefs, transient session.

    Used over Redux because the client-state slices (persisted preferences, the transient generation session) are simple enough that Redux's action/reducer boilerplate bought nothing. Zustand is a plain hook with no provider tree, and it's React 19 concurrent-safe.

  • Tailwind CSS 4

    Adopt

    CSS-first @theme config — the design-token backbone.

    packages/ui defines every design token (--color-brand, --color-surface-elevated, …) as a CSS custom property consumed via Tailwind 4's @theme. ESLint then bans a raw hex color in any className, so a color can't drift from the token file without the linter catching it.

  • Motion (motion/react)

    Adopt

    Animation library, wrapped behind named transition tokens.

    packages/ui/src/lib/motion.ts exposes named presets (transition.fast/.spring/.modal/…) over the raw library; ESLint blocks an inline { duration, ease } object anywhere in feature code, so every animation in the app traces back to one small token file.

  • Hand-rolled state machine (lib/machine.ts)

    Adopt

    ~80-line machine + a useMachine hook for any 3+-state flow.

    Any flow with 3+ states (onboarding, streaming generation) gets a state machine. Named states replace boolean tangles (isLoading && isDone) and make an impossible state impossible to represent, without pulling in a general-purpose library — see XState, held, for why not that one.

  • WCAG 2.2 AA

    Adopt

    The non-negotiable accessibility floor for every route.

    Enforced via eslint-plugin-jsx-a11y, @axe-core/playwright, and apps/landing's own check:a11y script; /accessibility publishes the conformance statement. Colour is never the sole signal for state — including on this very page's ring encoding — and every interactive element ships a visible :focus-visible ring.

HoldDeliberately not used here. Proceed with caution; the reasoning is in the entry.

  • XState

    Hold

    Considered for the same job as our micro state machine — held.

    The flows in this app top out at a handful of linear states, so XState's parallel states, history, and guards buy nothing today, at the cost of bundle weight and its own config DSL. Kept as an explicit option if a flow ever genuinely needs what it offers — this entry exists so that reasoning stays visible instead of getting re-litigated.

  • WebGL / shader-driven landing experiences

    Hold

    Two hand-built WebGL landing pieces were built, then shelved.

    TERMINAL VELOCITY (a scroll-driven CG retelling of the job hunt) and RIPBOOK (a notebook-styled concept) were each built across real milestones and then deliberately abandoned before their visual approval gates passed — the landing site returned to a plain static/Next export. The dormant webgl-author/shader-engineer/webgl-reviewer agent trio stays dormant; a returning WebGL surface would route back to them, not the general frontend author.

Rust Core & Data

AdoptIn production today — the default choice for new work in this area.

  • SQLite via rusqlite (bundled)

    Adopt

    The whole local-first store — no external DB process.

    rusqlite's bundled feature ships SQLite inside the binary, so there's no version mismatch or separate database process to install. Seven independent databases (documents, conversations, ai_generations, job_preferences, contact_profile, jobs, pipeline_cache) keep faults isolated — a corrupt conversations.db doesn't touch documents.db.

  • Tokio

    Adopt

    Async runtime for board scraping and background jobs.

    Board scrapers run concurrently via tokio::spawn, each holding a CancellationToken so a scrape can be stopped mid-run; long operations are tracked by a SQLite-backed job tracker with retry rather than blocking command handling.

  • keyring-core (OS-native credential storage)

    Adopt

    API keys and board passwords never touch the renderer.

    Credentials live in the OS keychain (Credential Manager/DPAPI on Windows, Keychain on macOS, libsecret on Linux) via keyring-core's platform adapters. The renderer calls credential commands over IPC and never handles a raw secret — a renderer XSS can't reach them because they live outside the web context entirely.

  • chromiumoxide (headless Chromium automation)

    Adopt

    Drives a real browser for boards that block plain HTTP.

    LinkedIn is scraped over plain HTTP, and the walled boards (Indeed, Glassdoor, StepStone, Xing, Workday) mostly go through the Adzuna/JSearch aggregator — chromiumoxide backs the specific boards and login flows that genuinely need a real, scriptable browser rather than a bare HTTP client.

  • reqwest

    Adopt

    The one HTTP client every scraper and AI call goes through.

    Centralized in net/http.rs so every outbound call — scraping, AI providers, geocoding — shares one client, one timeout policy, and one place to audit against the network-egress boundary.

  • Zod

    Adopt

    Schema-first validation at every IPC and form boundary.

    IPC payloads and form data are validated with Zod at the boundary (IPC receive, form submit); inside the app the inferred TypeScript types are trusted, so component logic isn't full of defensive if (!data?.id) checks.

  • Ollama (local + cloud)

    Adopt

    The offline-first AI provider — a local model, not just an API key.

    The one AI provider that needs no API key and no network call at all: a locally-run model the app talks to over loopback HTTP, with an optional Ollama Cloud mode alongside it. It's the concrete reason 'no API key yet' doesn't mean 'no AI features yet' for a local-first app.

  • Live model listing, no hardcoded defaults

    Adopt

    Every provider model list is fetched live — never a curated array.

    Four stale-model defects shipped in one session from hand-curated model arrays (a retired embedding model left as the default, a shut-down Gemini preview, its equally-dead list neighbours). ADR-0022 deleted every hardcoded array; a provider's own /models endpoint is now the only source, and onboarding pre-selects nothing.

HoldDeliberately not used here. Proceed with caution; the reasoning is in the entry.

  • A dedicated vector database

    Hold

    Considered for posting-embedding search — held.

    Posting embeddings are stored in SQLite alongside the documents and searched with an in-memory cosine pass, not in Pinecone/pgvector/a standalone vector engine — the corpus a single local user accumulates is small enough that the extra dependency and the extra moving part it would add buy nothing measurable today.

  • Nominatim

    Hold

    Retired as the geocoding fallback — its usage policy forbids autocomplete.

    Location autocomplete answers offline from a bundled GeoNames index for virtually every query; only a genuine miss falls through to Photon (OpenStreetMap-backed) as the network fallback. Nominatim filled that fallback role first and was retired because its usage policy explicitly forbids autocomplete-style querying.

Documents & Export

AdoptIn production today — the default choice for new work in this area.

  • Typst (typst / typst-pdf / typst-layout / typst-svg)

    Adopt

    One pure-Rust engine renders every résumé and cover-letter PDF.

    A single Typst engine backs both documents so résumé and cover-letter output share one layout system instead of two. The whole family is exact-pinned to =0.15.1 in lockstep — a solo version bump anywhere in it is a red flag, not routine maintenance.

  • docx-rs

    Adopt

    Native DOCX generation for the résumé's second export format.

    Renders the canonical document model straight to a real two-column DOCX table with native ATS-mode support, guarded by golden invariants so parity between the DOCX and PDF paths doesn't silently drift.

  • lopdf

    Adopt

    Low-level PDF manipulation — including inline annotation dicts.

    Used where PDF structure needs direct manipulation rather than pure rendering; inline (non-referenced) /Annots dictionaries needed custom parsing since lopdf's own annotation handling assumes the referenced form.

  • pdf-extract

    Adopt

    Text extraction for imported PDF résumés.

    Backs step one of the document-import pipeline (format detection → text extraction → SQLite storage → chunking → embedding) for PDF specifically; DOCX and images go through their own dedicated parser/OCR paths.

  • image (Rust crate)

    Adopt

    Raster handling for OCR input and export assets.

    Exact-pinned alongside the Typst family (=0.25.10) since Typst's own SVG/PDF export path depends on it — kept in lockstep rather than left to float independently.

  • Dual-engine golden-parity migration

    Adopt

    The legacy renderer stays compiled as the parity reference.

    layout_pdf and model_docx are on by default now that the canonical layout engine has snapshot parity with the legacy line-based renderer, but the legacy path stays compiled — it's still the parity reference, and it still renders cover letters — so --no-default-features can fall back to it if the canonical path ever regresses.

Build, Ship & Trust

AdoptIn production today — the default choice for new work in this area.

  • Vite 8

    Adopt

    Dev server + build tool for the desktop renderer, landing, and the extension.

    Every buildable frontend workspace (apps/desktop, apps/landing, apps/extension, packages/ui) builds on Vite; Vitest is Vite-native, so the same config/plugin surface backs both dev and test.

  • Vitest 4

    Adopt

    The one test runner, in every workspace.

    A root vitest workspace config aggregates every package + app project — plus a dedicated node-env project for build/release scripts — into one coverage report, so `pnpm test` is a single command regardless of which package changed.

  • Playwright

    Adopt

    Axe-driven accessibility checks, and the desktop app's E2E suite.

    Backs apps/landing's check:a11y (@axe-core/playwright) and apps/desktop's Playwright E2E suite (test:e2e) — the same tool covers both a static export and a live Tauri window.

  • Turborepo

    Adopt

    Incremental builds across an 8-workspace monorepo.

    Tracks file hashes per package, so an unchanged packages/shared build is skipped entirely when only apps/desktop changed — the dependency graph is why CI build time doesn't scale linearly with workspace count.

  • semantic-release

    Adopt

    Commit-driven, manually-triggered versioning — never automatic.

    Driven by Conventional Commits (feat → minor, fix/perf → patch, BREAKING CHANGE → minor pre-1.0), but nothing runs on push/merge to main — a release is a deliberate Actions dispatch, never an automatic side effect of merging.

  • Husky + lint-staged pre-commit gate

    Adopt

    Every commit is linted/formatted before it lands, not after.

    Pre-commit runs eslint --fix on staged TypeScript and Prettier on the rest; commitlint checks the message. Pre-push runs the full gate (typecheck, lint, cargo check/test/clippy, formatting) so main never carries a known lint or type error.

  • ESLint + ast-grep architecture guardrails

    Adopt

    The rules in AGENTS.md are enforced, not just written down.

    Package-boundary imports, the ports-and-adapters window.api ban, hardcoded hex colors, inline transition objects, raw <button>/<select>/<textarea> — every one of those rules is an ESLint error or an ast-grep scan rule, not a convention someone has to remember and a reviewer has to catch by eye.

  • Sentry crash reporting

    Adopt

    Desktop-only, default ON, consent-gated, whole-event redacted.

    Adding remote crash reporting reversed a published no-telemetry promise, so the decision to do it is recorded explicitly: default on, but nothing transmits until the first-run wizard has actually shown the consent screen, every outgoing event is redacted, the DSN is baked only into signed release builds, and both the browser extension and the landing site are excluded.

  • CodeRabbit

    Adopt

    The always-on AI PR reviewer — advisory only, never blocking.

    Free and unlimited on public repos, and it overlapped three separate advisory lanes (a reviewdog ESLint/Clippy pass, a Dangerfile, an actionlint lane) closely enough that all three were retired in its favour. It's configured to never approve or block on its own — the required check stays CI, plus an on-demand deep-dive review for anything that needs one.

  • Next.js (static export)

    Adopt

    apps/landing is plain HTML/JS at runtime — no server, ever.

    output: 'export' — no middleware, no Server Actions, no ISR, no dynamic route handlers, no headers()/cookies(). A permanent check:parity gate diffs the built out/ against the legacy static site so a route can never silently change shape.

TrialShipping, with a specific caveat or a partial rollout worth knowing about.

  • TypeScript

    Trial

    7.x (native compiler) everywhere except apps/landing, pinned to 6.x.

    apps/desktop and every packages/* workspace run TypeScript 7's native compiler; apps/landing stays pinned to 6.0.3 because Next 16's build-time verifyTypeScriptSetup doesn't recognise TS 7's native-compiler package layout and crashes the build worker. Trial, not Adopt, until Next supports it and the pin can drop repo-wide.

AssessNot in the codebase yet — worth understanding and watching before committing.

  • React Compiler

    Assess

    GA since React 19, not yet wired into any build here.

    No babel-plugin-react-compiler or eslint-plugin-react-compiler dependency exists anywhere in the monorepo today — manual memoization discipline is still how the renderer avoids re-render cost. Worth a real trial once it's had more mileage against a render-heavy, streaming-text-updating UI like this one; not adopted yet because nobody has actually run it here.

HoldDeliberately not used here. Proceed with caution; the reasoning is in the entry.

  • Codecov + SonarCloud

    Hold

    Dropped in favour of the zero-external-SaaS CI default.

    The CI program's stated default is Actions-native, zero external SaaS advisory tooling; both were dropped for exactly that reason before CodeRabbit was even evaluated. CodeRabbit's own free-tier scan (ESLint, Clippy, Semgrep, secret-scan) made a paid hosted coverage/quality dashboard even less necessary once it was adopted.