This case study is private

Enter the password to view it.

{{ error }}
← Back to portfolio
← Back to portfolio Product design & engineering
Case study · SaaS product design & engineering

A SaaS app for job seekers

A SaaS app that helps job seekers adapt their resume, track applications, and manage the whole search from one place.

Role
Product design + frontend engineering
Timeline
Apr–Aug 2026
Stack
React · Node/Express · Claude API · Supabase · Stripe
Status
Live at app.atsfixer.com
Team
JD
MD

Me on design and frontend, plus a backend engineer (MD).

My scope

Product design, prompt design, and the React frontend. Backend (Node/Express, Claude API, Supabase, Stripe) built with MD.

Tools
Figma React Claude API
Status

Live in production, real job seekers using it every week.

Live at app.atsfixer.com
77commits shipped
7ATS platforms scored independently
34%faster repair pipeline
55–60%less repair output per pass, same result
Process 01 Premise 02 Who it's for 03 Approach 04 Flow 05 Design system 06 UI 07 Frontend 08 What broke
02 Who this is built for

One person, applying to a lot of jobs

PM

Priya, 31

Marketing coordinator, between roles · 10–15 applications a week

Goal

A score she can trust reflects how the actual system at that company reads her resume.

Frustration

Every checker gives a different confident number, with generic "add more keywords" advice.

Behavior

Runs the same base resume against 5–10 postings a week, on whichever ATS the employer runs.

This is why the app scores per platform instead of averaging, tracks every application she sends, and caps usage against real weekly volume instead of a guess. Less time managing the search, more time prepping for the interview.

01 The premise

Job hunting shouldn't feel like a second job

Taleo parses a resume differently than Workday. A checker that averages every platform into one score is either wrong for six of them, or isn't really checking any of them. On top of that, most people track applications in a spreadsheet that goes stale after week two. I built ATSFixer to adapt the resume per platform, log the application automatically, and get out of the way so the actual work, preparing for the interview, gets the attention.

03 The approach

Fast, honest, and never stuffed with keywords

Speed

Patch-only repairs, a cached resume-facts layer, and an async job keep a 60–180s Claude pipeline from feeling stuck.

Truthfulness

The model can't invent a certification or metric not in the source resume. Enforced in the prompt, checked again after.

Restraint

A keyword caps at three uses per resume. Past that, modern ATS scoring reads it as stuffing, not relevance.

04 The golden path

Mapped end to end before any screen

A wait with nothing on screen reads as broken even when it's working, so every gate and failure point had to be designed, not just the happy path.

1
Land + auth check
Session, or sign up via email / Google
→
2
Upload + JD
Resume and target job description
→
3
Generate, async
1–4 Claude passes, gated on plan cap + device
↓
6
Application tracked
Loop closes in the tracker
←
5
Cover letter, optional
Follow-up kit
←
4
7-platform results
Auto-logs to the Job Tracker
Gates designed for failure Generation cap hit → upgrade modal Device conflict → claim device, retry Unverified email → resend, block
Wireframes

One skeleton, reused across the flow

Low-fidelity passes on the four screens that matter most, before any visual design.

01 · User auth
02 · Add job description
03 · New resume dashboard
04 · Job tracker
05 A design system, documented

ATSPro: one token set, one naming convention

Every surface, text color, and shadow in the running app maps to a --c-* token in design.js, so nothing gets picked from memory. Full dark mode ships free: every token carries a light and dark value, toggled by body.dark, no component restyle needed.

01 · Color
{{grp.title}}
{{tok.name}}
Plan accent chips
{{chip.label}}
02 · Typography

Poppins for all UI, Anonymous Pro for monospace values (tokens, IDs, hashes).

{{row.label}} {{row.spec}}
04 · Icons

Material Symbols Rounded, one <Icon name /> component everywhere.

{{icon}}
03 · Spacing & radius
Spacing, 4px grid
4 · 8 · 12 · 16 · 20 · 24 · 28 · 32 · 40 · 48 · 64
Radius (+ 9999 pills)
{{r.px}}
Shadows
{{sh.name}}
05 · Buttons
{{btn.label}} {{btn.note}}
06 · Inputs & placeholders
{{inp.text}}
{{inp.label}}
07 · Badges / tags
{{b.label}}
08 · Toasts
Resume saved. Auto-dismisses in 4–5s.
Generation failed. Stays until dismissed.
06 · UI

Built the way it shipped

Real screens from the running app, in the order someone moves through it.

{{tab.num}} {{tab.label}}
07 Frontend, briefly

How the pieces connect

React
Client UI, polling hooks
→
REST + polling
Node / Express
API, job orchestration
→
SQL, triggers
Supabase
Postgres, auth, storage
Claude API
Called from the Node layer for rewrite + scoring passes, never from the client
Stripe
Billing, plan caps enforced server-side via webhooks
08 What shipping taught me

Three things that broke after real users hit it

Finding

Generations over ~60s came back as a 502, even when they'd finished.

Fix

Rebuilt as a background job that returns a job ID instantly; client polls status every 2.5s.

Finding

Credits sometimes reset to full on refresh after an anonymous generation.

Fix

Deduction was gated on a silent insert. Server now deducts unconditionally the moment a generation logs.

Finding

A successful generation would sometimes never appear in the tracker.

Fix

A trigger cast its payload to text instead of jsonb, rolling back silently. Rewrote it to build real jsonb.

Where it stands

Shipped, not a prototype

Live since April 2026, 77 commits by August, real job seekers adapting resumes and tracking applications at app.atsfixer.com.

140s → 93sper 4-pass generation
911 → 509 tokenspass-1 output on a cache hit
7, alwaysplatforms scored independently
up to 4rewrite passes, gated on real signal
What I'd carry into the next one
01

Design a cache so a miss costs a little and a hit-gone-wrong costs a lot, never the reverse.

02

The fix that looks right in code isn't the fix until prod logs agree with it.

03

Running two design systems on purpose beat freezing the product to redesign it properly.

04

A single believable number is an easier sell than seven honest ones. I'd ship the honest ones again.