Loaded content now settles in: library cards and rows, checker issue rows,
assistant suggestions and past conversations rise in with a short stagger on
first appearance only; new assistant messages and "Thinking…" rise in while
history stays put; a newly made document fades up from 96% with its accent
ring fading in.
Resume thumbnails keep the last drawn page of the same resume up while a new
one renders, so edits and return visits no longer drop to a placeholder. The
builder's first preview render paints hidden under the placeholder pages and
fades in; the PDF viewer reserves an A4 slot and holds its overlay until a
page has painted. The ATS checker ring fills on first reveal, ring colour
blends across the threshold, and report and insights bars grow from their
baseline.
Hand-rolled pulsing placeholders become the static Skeleton, shaped like the
view that is loading (documents grid or list, applications views, insights,
API keys). ENTER_CLASS and stagger() live in libs/motion.ts.
Menus, popovers and combobox lists now slide in from their trigger on
whichever side they open (data-side aware, RTL-correct), and submenus
open in 120ms and close in 84ms. The combobox list exits at 0.7x like
every other popup, and tooltips scale from their trigger.
Toasts keep travelling in the swipe direction when dismissed, ease back
on a cancelled swipe, and a replaced toast drops out before the new one
has finished rising.
The tabs indicator uses the movement curve and now also draws the line
variant's underline, so it slides instead of teleporting. Hotkey mode
switches in the resume and letter builders land instantly. Radio dots
and checkbox ticks grow in instead of popping or only fading.
Add a Collapsible primitive to @reactive-resume/ui wrapping Base UI's
collapsible, styled like the accordion panel: height grows over 200ms and
folds over 140ms on ease-enter, reversing mid-animation.
Use it for entry cards, section rows, Basics, Check categories,
Design > Advanced, More options, AI provider rows and ATS checker findings,
so content no longer teleports while the chevron rotates. Chevrons at those
sites now share duration-standard ease-enter.
Opening an entry scrolls it back into view once the previous one has folded
away if it slid above the panel, and the page-to-panel reveal re-aims its
scroll after the panels settle. Advanced's open state moves up to the
Design panel so its nav pill can open it; the e2e fixture drives the new
trigger via aria-expanded.
Builder sections and entries, keyword chips and layout pages now shuffle and
drop with the shared DRAG_SETTLE timing (200ms, ease-in-out-strong). Dragged
builder rows lift on a raised surface with a shadow instead of fading to 40%.
Chips get a drop animation instead of vanishing, and both drag overlays skip
the drop flight under reduced motion.
The board now shows a dropped card in its target column in the same
render the drag ends, via a one-card override that clears once the list
query catches up or rolls back, so the card no longer blinks back to its
old column. The overlay then settles into the card's new slot (or back
home) with a 200ms on-screen-movement drop animation, skipped under
reduced motion. The floating card lifts in on pickup, flattens while it
lands, and shows a grabbing cursor.
Adds a shared DRAG_SETTLE dnd-kit timing to libs/motion.
The PDF canvas now stretches its last bitmap while the page's size changes
and redraws once the new scale has held for 150ms, rendering off-screen and
copying over in one step so the page never blanks during the assistant
column's resize, zoom clicks or window resizes. New documents and pages still
render at once.
The assistant column closes in 70% of its open time, keeps its content at the
open width so it travels in with the column edge, and fades that content in
and out instead of unmounting it at once. The tablet drawer slides in from the
end edge and the phone screen rises from the bottom. Cmd+J switches all of
them without a transition via a new assistantInstant store flag.
Sheets and dialogs were closed by nulling the data they render, so they
went blank, flipped titles or tabs, or dropped rows on the first frame
of their exit. Add useClosingValue, which holds the last value until
Base UI's onOpenChangeComplete(false), and move same-tick resets into
onOpenChangeComplete. The edit-provider dialog and stylesheet color
picker stay mounted so their exit transition runs.
Buttons (every variant except link, skipped while loading), chips, tiles,
nav items, rail and tab-bar icons now dip to 0.97 on press; document,
template, application and suggestion cards dip to 0.98. Full-width settings
and conversation rows darken to the press colour instead of shrinking.
Joined button groups cancel the scale so their seams stay closed, and the
switch thumb stretches toward its travel while held.
Document and template cards transitioned transform, but Tailwind v4 lifts
with the translate property, so their hover lift jumped; they now list
translate and scale. A new document card keeps its accent ring while
hovered, and letter template cards lift like resume template cards.
Pathname changes after the first load now run a typed `page` view transition: the old page fades out over 140ms and the new one fades up 6px over 200ms on the house tokens. Search-param and hash changes, the first load and browsers without view-transition types stay instant. The app nav, settings nav and auth logo are named so they hold still.
The auth card's load-time entrance is removed. Applications views, the Documents grid/list switch and Calendar month changes now enter with a short fade (months slide from the direction of travel), only after the user switches.
Root beforeLoad now reads the session and flags through the query cache
instead of refetching them on every navigation and preload. A signed-in
session is reused for a minute, a signed-out one is always re-checked,
and every sign-in, profile and 2FA change invalidates the cached session
before re-running route guards. The active Lingui locale is no longer
re-activated per navigation.
Links now preload on intent with TanStack Query deciding freshness, the
pending loader shows after 300ms and holds for 300ms, and it fades in on
in-app navigations while still taking over the HTML loader without a
blink on first load.
Set Tailwind's default transition duration and curve to --d1/--ease so
bare transition-* utilities run quick on the house curve. Mirror the
motion tokens in libs/motion.ts (EASE, EASE_MOVE, D1-D3, EXIT) and move
app UI off ease-out-strong and hand-typed 150/300ms durations; the
landing page keeps --ease-out-strong.
Give snapping hover and focus states a transition, slide progress fills
with translate instead of animating width, keep selection outlines'
width and style constant so they fade from transparent instead of
flashing dark, and drop the unused agent-prompt-marquee keyframes.
Removes useCallback, useMemo and memo from the 27 files the compiler compiles without bailing. Files it bails on (destructuring defaults, try/finally, refs read during render) keep their manual memoization.
RichInput, the full-toolbar editor only notes used, is gone with its tests; the editor extensions it defined move to rich-text-extensions.ts, which the rich text editor already used.
The preview cross-fades each new render with motion, which animates in JavaScript and ignores the CSS reduced-motion rule. A render landing from the PDF worker while a sheet was open could leave a page half-faded, so its caption read at low contrast (and moved for users who asked for less motion).
Resumes no longer hold cover-letter sections. A migration saves every
letter a resume carried as a letter linked to that resume's details and
design, hands a resume's only letter to its only letter-less application,
then removes the sections from resumes and their layouts. rollback.sql
puts them back.
Every resume write on the server moves any letter it still carries into
a saved letter in the same transaction, so stale tabs, older files, API
clients and restored versions keep working without duplicating letters.
The resume editor no longer adds or imports letters, the resume download
drops its Cover letter tab, and resume PDF downloads (API, signed links,
MCP) no longer take a cover-letter target. copy_embedded_cover_letter and
POST /cover-letters/from-resume are removed.
The expand steps of the redesign kept older app versions and API clients
working. This removes those compatibility paths:
- Entry date text (period/date) is written from the structured dates on
every save; an edit to the text alone is overwritten.
- application.archived is dropped; archived rows close first. CSV import
still reads the flag from older exports.
- resume_version.label is dropped; versions are named by kind and name.
- rejected is no longer accepted as a stage; remaining rows and history
close first. CSV import still maps it to closed.
BREAKING CHANGE: older app versions and API clients that read or write
archived, version labels, the rejected stage or date text alone no
longer work. migrations/20260929063245_contract_redesign_legacy_fields
has a rollback.sql that restores the dropped columns.
The file-level check places each finding's evidence on its page: the
rule's own box, or the line holding its snippet. Check keeps the report
with the resume it ran on and, while the resume is unchanged, outlines
each finding on the page with a pin that opens the full report.
Two-column templates take a sidebarSide from the layout (left or right).
Without one, each template keeps its own side and right-to-left pages
mirror it. The PDF templates reverse only their columns, header band
and sidebar background, and the DOCX export follows the same choice.
Design's Sidebar panel offers Left and Right.
Opening an imported resume shows a note at the top of Write: the file,
the sections and entries found, and how many fields still need a look.
The counts follow the resume as flags are cleared, and the note stays
until it is dismissed.
On phones the formatting toolbar follows the visual viewport and sits on
the keyboard, 44px tall, with Improve and Done. Done closes the keyboard.
Larger screens keep the toolbar inside the field.
The web preview, downloads, template gallery, server export and public
PDF render through Forme. react-pdf, react-pdf-html, the react-pdf
hyphenation package, the Phosphor react-pdf icons and the four patches
are gone. Hyphenation now follows the page language for every language
Forme has patterns for.
Semantic CSS keeps its language; declarations Forme can't draw raise an
ENGINE_UNSUPPORTED warning in the editor. The page map rebuilds blocks
Forme leaves out of its layout when they break across pages, and a
render that misplaces a box is repeated with nested rows kept whole.
Tests move to a small shim with the react-pdf calls they were written
against; tests of react-pdf internals are dropped. Forme limits are
recorded as expected failures (RTL line order, characters above
U+FFFF).
- The resume and letter canvases are focusable, labelled regions, so the page
scrolls from the keyboard even before its lines load.
- Closing the assistant returns focus to the ✦ button.
- The ✦ button shows on tablets and phones too, where the assistant opens as
a drawer or full screen.
- On tablets, Download PDF drops its label so the bar fits beside the mode
switch.
- The phone reflow darkens a template colour that's too faint to read on
white, and the checked download format's extension steps up to ink-2.
Auth pages, dialogs, the command palette, the user menu, Applications, the rich-text toolbar, the stylesheet editor and the error screens use the Material Symbols set instead of Phosphor; the icon subset gains the 35 glyphs they need. Phosphor stays for brand logos, the landing page and the icons printed on resumes. Also removes the form field and test mock that only wrapped the old rich input.
Every class that used the old names (muted, primary, card, border, input and the rest) now uses the Desk & Paper token it resolved to, with text colours on their text tokens (accent-text, danger-text, ink-3). The compatibility block in the theme is gone.
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.
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.
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.
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 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=.
- 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.