Build Log — March 2026

I built a job search tool
to solve my own problem.
Here's what happened.

Shortlist is an AI-powered conversation-based job matching app I designed and built independently. This is the working record of how it came together — the decisions, the tradeoffs, the things that broke, what I learned, and how I experienced its impact in real time.

Nan Doud
Solo build
Active
Python / Flask Claude API Claude Code Product Design UX Writing AI Integration

Demo walkthrough — condensed for time. The Coach conversation runs longer in practice.

01 — The Problem

Job search tools were built for databases. Not people.

I've spent years inside product and operations teams watching how friction accumulates when nobody asks whether the flow actually works for the person using it. When I started job searching in earnest recently, I turned that same lens on the process itself.

Traditional job boards are not human-optimized in a number of ways. They let you enter a title, select a few other preferences from dropdowns, and check "remote" and call that a search. They have no way to capture what I'd walk away from, or what kind of environment empowers me to do my best work, or that I've been quietly drawn to automation-forward companies for years without quite knowing how to boil it all down.

"A job search should start with a conversation, not a form. The thinking behind Shortlist is that simple."

So I built something that starts with a conversation instead. After uploading any documents you would like to have it take into consideration (e.g., resumes, previous interview prep materials, etc.) an AI coach then interviews you about your actual experience, values, and preferences. It synthesizes that into a structured profile. Then it scores job listings against the whole picture and surfaces results with plain-language explanations. Currently, everything runs locally on your machine.

What wasn't working

  • Keyword matching with no understanding of context
  • No way to express values, culture fit, or deal-breakers
  • Results that required hours of filtering to be usable
  • No explanation for why a job was surfaced
  • Stale, recycled, and scam listings in every search

What Shortlist does instead

  • Builds a full profile from natural conversation
  • Scores jobs against the whole person, not just titles
  • Explains every result before the user clicks
  • Filters stale and low-signal postings automatically
  • Runs locally with the user's own API keys
02 — How I Built It

The stack, the approach, and how I actually worked.

The Stack

Python and Flask on the backend. Plain HTML, CSS, and JavaScript on the front end with no framework. The Claude API powers the Coach conversation, profile synthesis, and job scoring. JSearch via RapidAPI pulls job listing data. Everything runs locally, packaged for distribution via PyInstaller.

Working with Claude Code

I am not a software engineer. While I possess basic HTML and CSS skills, I want to be upfront about that. I used a combination of Claude chat and Claude Code extensively throughout this build. Claude Code is an agentic coding tool that writes, runs, and debugs code directly. My job was to think clearly about what I wanted, communicate it precisely, and make good decisions about what it produced. Using Claude chat to formulate the best possible prompts had me moving in the right direction and at the rapid clip of my ideation from the get go.

That turned out to be most of the job.

Every feature in this app started as a conversation with Claude chat that led to a thorough prompt that fed Claude Code exactly what it needed to start building my idea into reality. I used Claude chat as a tutor to help me create strong prompts that included: a structured spec with clear behavior definitions, edge cases, implementation notes, and test criteria. In tandem with Claude chat, I wrote hundreds of these over the course of the build.

Writing a good Claude Code prompt requires the same skills as writing a good project brief. Clear problem framing, explicit success criteria, awareness of edge cases, and enough systems thinking to anticipate what could go wrong. The prompts are artifacts of structured thinking, not just instructions to a machine.

Here's an example from the ATS quality weighting feature, which scores job results higher when they come from Greenhouse, Lever, or Ashby (platforms that tend to attract more established, intentional employers):

Sample Claude Code Prompt — ATS Quality Weighting I want to add ATS-based quality weighting to the search results pipeline. The goal is to surface results from quality ATS platforms (Greenhouse, Lever, Ashby) higher in the ranked results, and deprioritize Workday unless the user has explicitly adjusted their preferences. WHERE TO MAKE THE CHANGE Find the section of code where JSearch results are received and processed before scoring. The weighting logic should be applied after scoring is complete, as a final ranking multiplier before results are sorted. ATS DETECTION Each JSearch result includes a job_apply_link field. Use this URL to detect the ATS platform: - Greenhouse: job_apply_link contains boards.greenhouse.io - Lever: job_apply_link contains jobs.lever.co - Ashby: job_apply_link contains ashbyhq.com - Workday: job_apply_link contains myworkdayjobs.com SCORE WEIGHTING After the Claude fit score (1-10) is assigned, apply a multiplier: - Greenhouse: 1.15 - Lever: 1.10 - Ashby: 1.10 - Unknown: 1.0 - Workday: 0.85 The displayed score should still show the original Claude fit score. The multiplier only affects sort order. IMPLEMENTATION NOTES - Log detected ATS and final_score to console during development - Manually evaluated jobs in My Shortlist tab are not affected

What I'm Actually Doing All Day

On any given day I am doing some mix of identifying what isn't working and figuring out why, writing prompts that specify what I want with enough precision that the output is actually usable, reviewing what got built, testing it, and deciding what to change next. I am also making product decisions about what to build, what to cut, and what the right UX pattern is. And I am writing copy — app UI, landing page, legal docs, LinkedIn posts. And designing two full visual themes.

This is obviously not how most software has historically been built at scale. But it is teaching me a lot about what clear thinking looks like in practice, and how much leverage you can get from communication skills when the tools are this capable.

03 — Product Decisions

Some things I got right. Some things I'm still fixing.

What I Got Right

The Coach conversation model was right from day one. The whole product rests on the idea that you should be able to describe yourself in natural language and the tool does the translation. Every feature decision downstream flows from that.

Making the profile fully editable was the most important trust decision I made. AI synthesizes well but it also overstates and occasionally invents details that sound plausible. A job seeker's profile is not the place to let that slide. Keeping the user in control of their own representation is how a tool earns trust.

The three-tab structure on the Evaluations page took several iterations to get right. Longlist, My Finds, My Shortlist. That progression reflects a real mental model and it matters.

The Tracker was a late addition and it might be the most useful feature in the app. Moving from passive matching to active pipeline management, with interview tracking, interviewer profiles, a salary negotiation workspace, and a Lessons Learned field per role, closes a gap that no other tool I know of addresses cleanly.

What I'm Still Fixing

Coach interactions are being refined for pacing and precision. Early versions were verbose in ways that felt more like being talked at than talked to. Current work involves tuning streaming speed, tightening response length, and adding contextual quick reply buttons so the conversation feels like a smart collaborator who knows where you are in the process.

Longlist search reliability is an open problem. JSearch pulls from a broad index of job listings, but getting it to return strong, relevant results consistently has proven harder than expected. Diagnosing why is active work.

Getting the Longlist right matters. It is the feature most people will reach for first, and an empty result screen is a bad first impression for a tool built around finding you the right job.

Decisions I Deferred Deliberately

Windows support, PyInstaller packaging, and the Gumroad listing are all on the roadmap but not done. I made a deliberate call to keep building features rather than packaging for distribution while the product was still evolving. The product I'd have shipped right away was nowhere near as robust as what I have now. The fact that it took me just a few weeks to get the product to the point it is at now speaks not only to the power of the tools both to brainstorm and build, but to the power of imagination and determination. I haven't had this much fun in years.

04 — What I Learned

The things I'd tell myself at the start.

01

Precision is the skill.

Vague prompts produce vague output. Specificity is the skill, not vocabulary or technical knowledge. You can't be precise about something you haven't thought through clearly.

02

Systems break at the seams.

Almost every bug happened at the boundary between two features. Keeping a mental model of how the whole thing fit together turned out to matter as much as writing good individual prompts.

03

Ship the thing that's wrong and then fix it.

A thing that exists and is wrong is more useful than a perfect spec for something that doesn't. I iterated fastest on the features I could actually look at, react to honestly, and fix specifically.

04

Good UX writing is a product decision.

Every label, every empty state, every error message is a decision about what the user believes the product does. "Have you applied?" is a better label than "Update status" because it meets the user where they are. These decisions compound.

05

Design for the failure modes, not just the happy path.

The most important AI design decision I made was making the profile fully editable. Coach synthesizes well but it also overstates and occasionally invents details that sound plausible. For something as consequential as how a person represents themselves to a potential employer, that's not acceptable. Putting the user in control of the final output was the only right answer.

Nan Doud

Operations and communications professional based in Peoria, IL. I've worked in product and operations teams at organizations ranging from SaaS startups to healthcare. I think clearly about systems, communicate effectively, and build things that work for real people.

Shortlist is the clearest demonstration I have of how I actually work.

Skills demonstrated in this build

Product strategy AI prompt engineering UX design UX writing Systems thinking HTML / CSS / JS Claude API Cross-functional thinking Debugging Iterative development Go-to-market planning
Build Log

Every day, documented.

A running record of what got built, what broke, and what was learned. Most recent first.

  • Wrapped column overhauled: outcome selector, HIRED! celebration with start date prompt, rotating affirmations, and card archiving with a settings widget
  • Ethics & AI Philosophy page with deliberate choices around Coach's emotional honesty and crisis resource handoff
  • Coach streaming speed and brevity tuned to feel like a real conversation at roughly 200 WPM
  • One apostrophe in "I've" took the whole app down — all Phase 2 work is now committed for the first time. That was scary.
  • Full Tracker kanban built: Applied, Interviewing, Offer, and Wrapped columns with drag-and-drop, card numbering, and persistent data
  • Interview round tracker added with date fields, interviewer profiles, .ics calendar export, and round-by-round notes
  • Offer column built with salary comparison against floor, Levels.fyi integration (in progress), negotiation tools, and a negotiation log
  • Electric Youth light mode launched with a full alternate color palette, toggle in nav and Preferences, and theme-aware animations
  • Accessibility pass: Reduce Motion, Font Size, and High Contrast toggles fixed across both themes
  • Applied to a role using the app itself as the answer to the ambiguity question
  • Evaluations tab built with three input modes: paste a job link, scan a careers page, and run a full Longlist search — all scoring against the active profile
  • ATS quality weighting added: Greenhouse 1.15x, Lever and Ashby 1.10x, Workday 0.85x, detected from job_apply_link URLs
  • Careers page scanner built with Ashby, Lever, and Greenhouse API detection for structured job feeds instead of scraping
  • Landing page designed and built with illustrated graphics, accessibility improvements, and copy
  • My Shortlist tab added with persistent starred cards, applied tracking, and stage management
  • Phase 2 built on top of Phase 1: JSearch via RapidAPI integrated, profile-based AI scoring, and a results Longlist with explained match cards
  • Coach rebuilt as a persistent bottom-right chat widget, detached from the nav, with profile-specific context and conversation history
  • Search performance optimized: parallel remote and local queries, batched scoring, hard cap at 20 results before AI scoring
  • Match card design finalized with score tier badges, green strengths, deal-breaker flags, salary notes, and ATS platform labels
  • Geographic bias fixed, remote-only filter tightened, and loading messages written to make the wait feel intentional
  • Phase 1 built from scratch in one evening: multi-document upload, AI interview conversation, profile synthesis to JSON, and named profile saving
  • First time in a terminal, first time using Claude Code — got a working app running on localhost in about an hour
  • Neon aesthetic designed and built: dark background, gliding light streaks, Space Mono headings, SHORTLIST wordmark with glow
  • Coach branding established, PDF download working, progress indicator and celebration animation added — Phase 1 committed