Idle is a drop zone with "Check a sample file" and an optional posting; busy
shows three labelled steps; the result is a 440px column with the score ring,
the categories that could cost a match (and the posting's missing terms), and
"Fix these in the editor", beside the file shown two ways: the original page
and the text as software reads it. Phones get one column with the fix pinned.
"Fix these in the editor" imports the same file, read in the browser as the
check was, and opens it in Check. Visitors sign up first; the file waits in
IndexedDB and is imported when they're back.
The public checker's AI review is dropped (Check -> Writing covers it), with
its locked card, the marketing parse preview and the old uploader. The PDF
viewer also renders a given file, for the original page.
Adds a phone visit that checks the reflow, tap-to-mail contacts and the pinned Download and Share, and an unknown link's message. The download preference spec expects the page's single Download button, and the root spec the new footer credit.
Desktop and tablets get a 64px bar with the owner's name, headline and city,
Copy link and Download PDF, the page on the sunken canvas, and the footer
credit. Phones get the resume as readable text in the template's colour and
body font, in the order the PDF prints it (read from the semantic tree), with
contact details as tap targets and Download and Share pinned.
The owner's rich text is parsed into an allowlist of formatting elements and
plain links, never injected. With downloads off, Download is hidden and
printing shows a note instead of the page. Off, unknown and trashed links all
read "This resume isn't shared right now." with nothing about the owner.
Old addresses redirect, the name saves on blur, Export everything downloads a zip, the theme applies at once, and a new key is shown once and revoked with Undo.
Six settings pages become Account, Preferences and AI & developer, with an
in-page nav beside a 680px column (tabs on tablets, a three-row root on
phones, which the Account tab opens).
- Account: photo upload, name, username with the instance host, and email,
each saving on blur; password, a two-step verification switch, passkeys and
connected sign-in; Export everything as one zip of documents and
applications; Delete account with the counts of what goes and typing
"delete"; Sign out.
- Preferences: Light, Dark and System tiles, the interface language and the
motion note.
- AI & developer: provider rows with Test ("Connected · 420 ms" or the exact
error) and an Edit dialog holding the switch and delete; Add provider with
all sixteen; API keys in a table with a New key dialog (30 days, 90 days,
Never), the key shown once, and Revoke with Undo; the MCP address with Copy.
The old settings addresses redirect, and links across the app, the command
palette and the user menu point at the new pages.
A local OpenAI-compatible stub plays a short conversation: read the document, propose an edit, ask a question when asked, and stream slowly enough to stop. The spec connects it in place, accepts an edit and checks it was saved, answers a question, stops a reply, improves a line, asks from the command palette and follows the old /agent links. The E2E workflow allows the loopback base URL the stub needs.
The assistant opens beside the resume or letter it works on: a third column
at 1280px and wider, in place of the left panel from 1024px, a drawer below
that, and full screen on phones. The bar's button and Mod+J toggle it.
The panel sets up a provider in place, suggests what to ask, streams replies
with Stop and Continue, asks clarifying questions, and shows proposed edits as
change sets with page marks, the outline's "n proposed" pill and the page
caption. Context chips decide whether the next message shares the document and
the posting. Errors keep the message with Retry and Switch model, past
conversations are grouped by document with their outcomes, and the
conversation can be copied as a transcript.
Improve in the rich-text toolbar suggests a stronger verb, a result, a shorter
line or the user's own request for the line holding the caret, and replaces it
only on Replace.
The Agents pages, the builder's assistant sheet and the application copilot
panel are removed; /agent links redirect to the document with the assistant
open. Command palette Ask, Job match's missing terms, Applications' Prepare for
next step and Copy for a job all open the assistant on the right document.
Removes the unused react-resizable-panels and @shadcn/helpers dependencies.
The published spec had fallen behind the runtime routes, including the cover-letter draft and version routes, and the schema guide was missing Check metadata. The OpenAPI test now lists the new letter routes and the Trash wording.
Threads belong to a resume or a cover letter (agent_threads.cover_letter_id)
and count the edits they proposed and the user accepted. The propose_edits
tool rewrites or adds passages by id, resolved against the document as it is
now, and the edit statuses are stored with the message. read_letter joins
read_resume; apply_resume_patch, approvals, revert and archiving are removed.
Message context lets the user leave the document or the linked posting out of
a message; the posting now includes the application's notes. Redis is
optional: without it replies stream directly.
The proposal core (passages, additions, states) moves to
@reactive-resume/resume/proposals so the server and the web app share it.
Adds ai.improve, which suggests a rewrite of one line (stronger verb, a
result, shorter, or the user's own request) and says when it states
something new.
- The page: CSV import/export behind one icon, Add application, a dismissible
nudge for the application waiting longest without a reply (10+ days),
List · Board · Insights · Calendar, search across role, company, contacts
and tags, and Show closed.
- List (the default): grouped by stage in the order that needs you first,
with collapsible groups, the next step (warn when overdue), what was sent,
sorting from the headers and row checkboxes for bulk moves, tags, closing
and deleting. Phones get two-line rows and no board.
- Board: a column per stage; drops and Move to… show the same toast.
- Detail sheet (480 px, full screen on phones): the stage stepper with Move to
next, the next step (the next interview or follow-up) with Edit and Add to
calendar (.ics), what was sent (opening the version sent, read-only, in
History), Tailor a resume and Write a letter, editable salary and source,
contacts, tags, autosaved notes and the activity timeline. Close
application… takes a reason; Delete is in ⋯ and asks first.
- Add dialog: paste a link or posting; its role, company and requirements are
read into editable fields and the posting is saved with the application.
Add, or Add and tailor a resume.
- Insights: how far applications get (from their stage history), how many
heard back and how fast, and tailored against base resumes, above the
existing charts.
- CSV import shows how columns were matched before saving and lets you
download the rows it skips; export adds the closed reason.
- The builder opens History on a version from ?version=.
- Applications end in a `closed` stage with a reason (not selected, withdrew,
accepted another offer, no response). The migration moves `rejected` to
closed + not selected and archived applications to closed, rewrites
`rejected` in their stage history, and ships rollback.sql for older
versions. `archived` stays, deprecated; `rejected` is still accepted as
input and means closed.
- Once an application with a linked resume reaches Applied, the resume is
saved as a "sent" version named after the company, and the application keeps
its id and the resume's Check score then.
- New columns: closed_reason, cover_letter_id (backfilled where exactly one
letter was written for the application), sent_resume_version_id,
sent_check_score and requirements.
- applications.ai.parsePosting reads a pasted link or posting. Links are
fetched on the server: https only, public addresses checked at connect time,
three redirects, 2 MB and 10 s at most. A page's JobPosting data fills the
fields without AI; with a provider, the model reads role, company,
location, salary and requirements.
- MCP application tools take closedReason and coverLetterId.
- The score ring (live checks only, easing to each new score), the verdict,
and Issues · Job match · Writing tabs.
- Issues: numbered cards with category, explanation and fix, pinned to their
lines on the page with warn pins and wavy underlines. One-step fixes apply
with undo; the rest open the field in Write. Show on page, and Ignore (Keep
for the two-column issue), stored with the resume. Category rows below, open
when they need attention. "Also check the exported PDF" reports in a toast.
- Job match reads the linked application's posting, or a pasted one that can
be saved as an application. Missing terms ask where they belong (Add to
Skills) or can be hidden; covered terms light up their entries on the page.
- Writing: an opt-in AI review that says what it sends. Rewrites of bullets
and paragraphs arrive as proposals: struck-through old text and highlighted
new text on the page, numbered markers, Accept, Reject, Accept all, A/R and
arrow keys, one undo step. Out-of-date proposals can't be applied.
- "What a person sees / What a parser reads": the parser view extracts the
text of the PDF on the page, in reading order, flagging issue lines.
- Phones step through issues on the page; tablet pins open the drawer.
- The builder's ATS section and its deep-check UI are gone.
- ai.atsReview takes optional passages (id, where, text). A suggestion that
rewrites one names it in passageId with the whole new passage, which the
editor offers as a proposal to accept or reject.
- The review prompt fills its placeholders in one pass, so resume text that
looks like a placeholder is sent as it is.
- resume.getById returns applicationId, the job application a resume was made
for, so Check's job match can read its posting.
- Every live check has a category (contact details, dates, layout, section
headings, writing). Reports score the applicable rules, per category too.
- Findings carry a key that uses entry ids instead of array indexes, so it
survives reordering. Keys in the new metadata.check.ignored set findings
aside without counting them against the score.
- TWO_COLUMN_LAYOUT flags a two-column template that prints a sidebar.
- Full-width pages print no sidebar, so sections placed only there are now
reported as never printing instead of passing as main-column content.
- metadata.check also holds job-posting terms hidden as not true; public
viewers don't receive it.
- ats-pdf exports the job-description matcher, spelling variants and the
semantics reader for the editor's job match and parser view.
The shared fixture creates its sample resume through the API and opens
it; documents-new covers Start blank (named after the headline) and Copy
for a job (linked and searchable by the application). dashboard-lifecycle
renames inline, duplicates, trashes with undo, restores and deletes now;
the letter, lock, view, import, auth and direction specs follow the new
library and menus.
The dashboard becomes Documents: resumes and saved letters in one
library, in a new app shell (a 240px sidebar, an icon rail on tablets and
bottom tabs on phones) with Documents, Applications, Trash (when it has
items), New (N) and the account row.
- Documents: All / Resumes / Letters with counts, search (/) across
titles, tags and linked applications, sort, grid or list (remembered on
the device), tag chips once tags exist, cards with the real first page,
"Resume · Edited 2h ago", the linked application, lock and "New" badges,
and the first-run screen. A file dropped anywhere on the page imports.
- One menu on cards, rows and right-click/long-press: Open, Rename
(inline), Duplicate, Copy for a job… (resumes) or Link to application…
(letters), Tags…, Lock editing, and Move to Trash with Undo.
- Trash lists days left, with Restore and Delete now (asks once).
- New: import a file in three labelled steps with the result and flagged
fields (resumes, and saved letters' JSON), copy a resume for a job,
start blank (named after its headline until renamed), a new letter, or
a sample resume.
Also:
- /dashboard/resumes and /dashboard/cover-letters redirect to Documents.
- Settings pages sit behind a tab strip until they're regrouped, and the
account menu gains Settings.
- A letter written inside a resume can be copied to Documents from its
entry menu (Q3k), replacing the library's copy action.
- The builder's document menu and the letter editor move documents to
Trash instead of deleting them.
- Dialogs reopened right after closing start fresh.
- Removed: the resume and letter libraries and the old create and import
dialogs.
An imported resume is named from its content (the person's name, else the
headline) instead of a random name. Renaming, tagging or linking a locked
document is refused, as moving it to Trash already was.
Schema: resume gains application_id (the job a copy was made for),
trashed_at and auto_name; cover_letter gains tags, is_locked and
trashed_at.
A new documents router treats resumes and saved letters as one library:
list (live or in Trash, with the linked application), counts, rename,
setTags, setLocked, linkApplication, trash, restore, purge (only from
Trash) and copyForJob, which duplicates a resume, links the copy to the
application and gives the application the copy when it has none.
- resume.delete and coverLetters.delete now move to Trash. Documents stay
there for 30 days and are purged when their owner next lists documents,
so no scheduler is needed.
- Documents in Trash are left out of resume and letter lists, and a resume
in Trash isn't shared: getBySlug and verifyPassword skip it.
- Locked documents can't be moved to Trash; locked letters can't be edited.
- A blank resume created with autoName takes its headline as its name
until someone renames it.
The sharing specs use Share → Link (public switch, visitor downloads, the
password switch and the public address from Open public page), and the
export specs pick a format in the Download tab. share-history covers
renaming the address with the old one redirecting, and naming, previewing
and restoring versions.
Share & export is one 440px sheet (a full-height bottom sheet on phones)
with Link, Download and History tabs. Share opens Link, the ▾ beside
Download PDF opens Download, the clock opens History, and ⌘⇧S / ⌘⇧E open
their tabs. On desktop the page moves 120px aside so it stays visible.
- Link: the public switch card; the address with a live check (300 ms),
the reason it can't be used and a suggestion, saved only once valid, so
the old address stays live; Copy ("Copied" for 2 s); visitor downloads;
the password (Q3a); Open public page; a QR code; Share via… where the
platform offers it; and views, downloads and time since the last view
over 30 days with a daily chart, or the explanation while the link is
off.
- Download: format cards explained by when to use them, Resume or Cover
letter (with the resume header option) when the resume has a letter,
the file name recruiters see (First-Last-Resume by default, unsafe
characters stripped), a non-blocking note about open Check issues, and
progress inside the button; a failure offers Try again and PDF.
- History: name the current state; a timeline of Now, sessions, named
versions, restores and where the document came from. Picking a version
shows it on the page, read-only and outlined, with its banner; Restore
saves "Before restore" first. Named versions can be renamed or deleted.
Also:
- Autosaves send this visit's session id, and naming or restoring a
version saves pending edits first.
- The one-click PDF and every export use the First-Last-Resume name.
- Resume dialogs stop asking for a slug (it's edited in Share), which also
stops Rename from overwriting a custom slug with one made from the name.
- The public route redirects a renamed resume's old address.
- Removed: the download dialog, the version-history menu and the sharing,
statistics and export sections.
Versions:
- resume_version gains kind (created, import, auto, named, before-restore,
restored, ai, sent), name and session_id. The migration backfills kind
from the old English labels; label stays for API clients.
- Each editor visit sends a session id with resume.update, and its saves
keep one autosave version, refreshed at most every two minutes. Calls
without a session keep the old throttled autosave.
- Creating a resume writes a "created" version and importing an "import"
one, so history is never empty.
- New procedures: getVersion (for previews), createVersion (name the
current state), renameVersion and deleteVersion (named versions only).
- Retention replaces the 30-row cap: autosaves, AI edits and restores
expire after 90 days, pruned when a resume gets a new version (there is
no scheduler, and the Vercel entry skips startup hooks); a cap of 500
autosaves per resume bounds storage.
Slugs:
- resume.checkSlug validates ^[a-z0-9]+(-[a-z0-9]+)*$, reports which of the
user's resumes uses a taken slug, and suggests a free one.
- A changed slug must match the pattern (existing ones keep working until
changed). The old slug is kept in resume_slug_redirect for 30 days, and
getBySlug and verifyPassword resolve it; the response carries the current
slug so clients can redirect.
- create and duplicate make the slug optional and generate a unique one
from the name; duplicate no longer falls back to the original's slug,
which always collided.
The migration also applies the pending drop of the redundant
resume_user_id_index removed from the schema in b953435f2.
The entry dialogs removed with the Write panel carried "Import from
library", which copied a saved letter's recipient and text into a new
cover-letter entry. A new, empty cover-letter entry now offers the same
picker inline. The copy stays independent of the library letter.
The cover-letter-library spec drives the inline picker and waits for the
renamed JSON copy before checking the original, which removes a race with
the list refetch.
template-switch now previews a template on hover, applies it from its card,
checks it after a reload and undoes a switch from the toast. The section
helper opens Design groups and the exact-value sections inside Advanced.