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.
The Design mode is a single panel with a sticky group nav: Template, Type,
Color, Page and a collapsed Advanced.
- Template: filter chips, and thumbnails rendered from the user's own
content at idle time, cached by template and content. Hovering or
focusing a card (holding it, on touch) previews the template on the page
with a "Previewing" chip; leaving the cards or Esc restores it; a click
applies it with an Undo toast. Two-column templates get a sidebar
sub-panel for its width and sections.
- Type: five font pairings, text size 9-12.5 pt and density. Color: eight
accents and a hex field with its contrast on white, offering a darker
shade below 4.5:1. Page: paper, language, margins, icons and link
underlines.
- Advanced: the date format, every exact-value editor, custom CSS and
Reset to template defaults.
- When content runs past the authored pages the canvas says by how many
lines and offers Fit, which tightens density, then margins, then size
(never below 9 pt), re-rendering after each step, as one undo step.
- Phones get the groups in a half-height sheet over the live page.
The template gallery dialog, the Template section, the collapsible section
chrome and its collapse store are gone. ATS design findings now open the
Design group that fixes them. The desktop panel is a containing block, so
screen-reader text deep in a long panel no longer stretches the document.
Columns, sidebar side, header placement and ATS safety for every template
now live in templateLayouts. The template gallery metadata and the layout
editor read it, and a DOCX test checks the two-column configurations
against it.
Specs follow the outline rows, the eye on each row, the photo row, the
structured dates and the Full name field, and a new spec checks that a
click on the page opens its entry and focusing a field outlines its block.
The download-preference spec waits for the share address after a reload.
Write becomes the Basics card, the outline of sections in print order and
Add section. Entries open in place (one at a time, the editor selection),
save as you type and show drafts as "Draft · not printed". Dates use a
structured field with a Present switch and the review note for text that
wasn't read exactly; descriptions use a restricted rich-text toolbar shown
on focus. Sections and entries reorder by drag or Alt+Up/Down, rows carry
the eye, count and every section option, and deleting shows Undo.
Clicking the page opens the entry and scrolls it into view; focusing a field
outlines its block. Phones push an open entry full screen. While a field
has focus the preview waits for a pause in typing.
The left section sidebar, the entry dialogs and the hidden-sections list are
removed.
Dated entries and roles carry dates (start, end, present, and raw when the
text couldn't be read exactly). The parser moves to the schema package and
reports how each date was written; parseResumeData fills dates, infers the
date format from how dates were typed and rewrites the legacy period/date
text from them, so older clients and API readers keep working. API writes
sync against the stored data, so an edit to the text alone is read back
into dates.
getById and getBySlug return upgraded data, ATS date rules and sorting read
dates, JSON Resume and LinkedIn imports map their dates directly, entry
titles may be empty (drafts aren't printed), and the MCP schema resource is
generated live in place of the stale schema.json.