Fix authentication recovery, account imports, application tracking, resume
editing and exports, sharing, API contracts, provider selection, and private
local attachments. Preserve authored content during PDF pagination.
Update guides, generated OpenAPI output, and translation catalogs to match
verified behavior and documented constraints.
Validation: 1,019 tests passed; 12 database/OAuth integration tests skipped.
Ten affected package typechecks, production build, Biome, and package
boundaries passed.
`outline-none` on the item beat the base-layer `:focus-visible` ring and
nothing replaced it, so keyboard focus was invisible. Dropping it lets the
design system's 2px accent ring show again.
Prune the unit and e2e suites to what protects security, user data, public
contracts and past regressions. Drop what slowed development without catching
bugs: markup and label assertions, wrappers that only proved Base UI works,
copied inventories and snapshots, mock call-shape checks, permutation matrices
across templates, fonts and locales, and env-gated suites that never ran in CI.
- Unit: 4749 tests in 421 files become 1212 in 227. PDF tests that rendered or
rasterized every template, font and locale combination go; one render per
template stays and now checks that every visible section reaches a page,
which a blank page used to pass.
- E2E: 55 tests in 35 specs become 11 in 9, one journey per severe area: sign-up
and sign-in, autosave, a failed save during navigation, JSON export and import,
public and password-protected sharing, slug redirects, OAuth consent for MCP
clients, and the assistant.
- Tests a coverage review found to be the only guard of a contract were kept or
restored, each checked by breaking the code it guards.
- Remove exports, tooling and dev dependencies that only the pruned tests used.
- The Semantic CSS guide check now reads indented code fences, so the guide
example it skipped is compiled too.
These controls set outline-none, which overrides the global :focus-visible
accent ring, so keyboard users saw no focus indicator. Toast buttons also
set focus-visible:outline-accent, which only changes the colour and left
the outline style at none. The radio now transitions only border-color so
the ring doesn't fade in from the text colour.
The drawer took a mouse press anywhere outside Drawer.Content as the
start of a swipe and captured the pointer, so switches and buttons in a
bottom sheet never received the click. The sheet's children now sit in
Drawer.Content; touch swipes still dismiss from anywhere.
The phone entry screen pushes in from the end edge and slides back out on
Back, reversing mid-push. The Design sheet keeps its full height and moves
by translate instead of animating height, and rises from the tab bar when
Design opens. The selection bar rises and fades in.
Sheet is now backed by Base UI Drawer: bottom sheets follow a downward
swipe, dismiss on a flick or a drag past half their height, and settle back
otherwise. Side and top sheets are unchanged.
On phones, Settings rows push the section in from the end and the back link
returns the list from the start, layered on the page view transition.
Views that replace each other (new-document steps, auth post-submit
screens, ATS checker states, consent, social sign-in, new API key) now
fade up 4px as they arrive. The ATS lenses both stay mounted so switching
back never re-reads the PDF, and the 1024-1279 assistant swap crossfades
in the panel's cell (instant on Cmd+J).
Things that used to push layout no longer move what the user is reading:
the bulk-selection bar floats at the bottom of the list, the rich text
toolbar sits under the text, and the letter draft bar takes the Stop
button's place under the draft. The drop-to-import frame, follow-up
nudge, job-match panel, public-link options, and rare save states fade in.
Adds a Swap primitive so copy buttons crossfade Copy -> Copied at a fixed
width, and POP_CLASS for status icons (progress checks, tool status,
proposal Applied, the public download icon).
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.
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.
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.
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.
On coarse pointers, buttons, switches, checkboxes and tabs get an invisible hit area of at least 44x44 centred on them, without changing how they look. A checked switch row's description steps up to ink-2, since ink-3 falls short of 4.5:1 on the accent tint. Adds contrastOnWhite to the colour utilities.
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.
Attachment, bubble, empty, marker, message, message scroller, questionnaire, resizable and sidebar had no users after the assistant moved into the editor, and neither did react-resizable-panels or @shadcn/react.
Icons now draw their ligature from data-icon in a pseudo-element, so icon
names stay out of copied text, find-in-page and text queries. useBreakpoint
reports the design system's mobile, tablet, desktop and wide ranges.
Replace the achromatic shadcn palette with the Desk & Paper tokens (warm
neutrals, one moss accent, danger, warn and info), exposed to Tailwind
under their spec names; the previous names resolve to the new tokens
until every screen is rebuilt. Hard-coded palette classes become semantic
tokens and stage colors follow the spec.
Fonts move to Newsreader, Hanken Grotesk and JetBrains Mono. The theme
gains a System option that follows the operating system live, applied
before first paint, and Base UI now receives the locale's direction.
Primitives follow the component spec: button variants primary,
secondary, ghost, danger and link with a loading state, accent focus
rings on inputs, SwitchRow, semantic badges, segmented and underline
tabs, restyled menus, dialogs, sheets, tooltips and command bar, and a
single bottom-center toast with an Undo action. New: IconButton,
SegmentedControl, RadioGroup and NativeSelect.
Icon renders Material Symbols Rounded at weight 300 from a subset font
that holds only the glyphs listed in packages/ui/src/icons/names.ts.
pnpm icons:build checks every name against Google's codepoints and
regenerates the font; a test keeps the manifest and the list in sync.
Icons are aria-hidden and untranslated, and directional ones mirror in
right-to-left layouts.
Audit every animation in the app and shared UI primitives against a
frequency-first motion bar: keyboard and high-frequency actions no longer
animate, remaining motion uses interruptible CSS transitions with shared
easing tokens, and redundant animation code is removed.
UI primitives (@reactive-resume/ui)
- Dialog, alert dialog, popover and tooltip move from tw-animate keyframes
to Base UI data-starting/ending-style transitions; menus, popovers and
tooltips skip motion when opened from the keyboard (data-instant).
- Dialog gains an `instant` prop; the command palette uses it.
- Accordion animates its real panel height; caret rotates instead of
swapping icons.
- Menu backdrop blur moves onto the popup so it no longer snaps in after
the fade; context menus and comboboxes fade only.
- Sidebar collapse uses the strong ease-out curve and snaps on Cmd+B.
- Toast, sheet, checkbox, tabs, toggle, inputs and message scroller get
tokenised easing, correct transition properties and press feedback.
- Tabs no longer squeeze a trigger narrower than its label.
- Spinners keep spinning under prefers-reduced-motion.
Web app
- Remove the default route view transition and page-entrance slides.
- Add EASE_OUT_STRONG for Motion; replace built-in "easeOut" everywhere.
- Switch LazyMotion to domMax so layout and Reorder animations run.
- Builder: consolidate 12 section list files into one ItemsSection,
opacity-only list rows with popLayout, instant Cmd+0, faster dock zoom,
crossfade that no longer dips, transform-based progress bars.
- Dashboard: keep previous results while sorting/filtering, no empty-state
flash, uncontrolled sidebar (no network round trip on collapse), calmer
resume card tilt, no stacked hover wrappers.
- Settings: drop entrance/stagger wrappers and ActionButton.
- Agent: CSS marquee paused on hover; thread switches keep the layout.
- Homepage: fix invalid transition declarations, CSS spotlight drift,
scroll-hiding header without a JS spring, tokenised curves.
- Theme switches change every color at once.
- Remove SSR-only useIsClient guards from the SPA.
Docs: rewrite the DESIGN.md animation section around the new tokens.
* fix(components/form): resolve FormControl label target regressions (#3369)
- Expose FormControlContext and wrap FormControl children in Base UI's
LabelableProvider so the generated control id reaches the actual
labelable element.
- Update InputGroup/InputGroupInput to consume the context and place
the id on the real input instead of the fieldset.
- Update Slider to discard the wrapper id and use the context via
LabelableProvider so the thumb input receives the id and
aria-labelledby.
- Update ChipInput to consume the context, set id and aria-labelledby
on the inner input, and only fall back to aria-label when not inside
a FormItem.
- Restructure the sidebar layout so a single FormControl labels the
numeric input and the visible FormLabel is referenced by id for the
sibling Slider, removing the duplicate-id defect.
- Add a dev-time warning when the generated id lands on a non-labelable
or missing element.
- Extend form.test.tsx with regression coverage.
* test(form): add regression coverage for chip-input and dual-control layout
* fix(ui): surface FormControl error state as aria-invalid on the Slider control
- Problem: FormControl injects aria-invalid={hasError} onto its rendered
element, but Slider stripped it without re-applying it anywhere, so the
error state never reached the DOM (flagged by Codacy/Greptile/CodeRabbit).
- Fix: bridge aria-invalid onto Base UI's native range input via the Thumb's
public inputRef prop; Base UI v1.7 has no prop path for it (its validation
props only apply through Base UI Field context). id stays stripped since
LabelableProvider already delivers it to the input.
- Verification: new regression test in form.test.tsx fails on the pre-fix
head (aria-invalid null) and passes post-fix; packages/ui 363/363 tests
green; tsc --noEmit on packages/ui clean.
* fix(ui): let a caller-supplied data-slot override the Slider default
- Problem: the FormControl label-target fix moved data-slot="slider" after
{...props} on SliderPrimitive.Root, so a caller's data-slot was silently
overwritten with the default — a prop-ordering regression against both the
prior file and the repo-wide convention (FormItem, FormLabel, InputGroup all
place data-slot before the spread).
- Fix: restore data-slot="slider" before {...props} so caller values win.
- Verification: packages/ui — vitest src/components/slider.test.tsx
src/components/form.test.tsx = 30/30 passing; new regression test
("lets a caller-supplied data-slot override the default") fails on the
pre-fix head (data-slot="slider" wins) and passes with the fix; tsc
--noEmit clean.
* fix(ui): preserve standalone Slider and InputGroup identity props
- Problem: the FormControl prop strip dropped a standalone caller's id on
Slider and id/aria-describedby/aria-invalid on InputGroup, so standalone
compositions rendered no element carrying those attributes (regression
vs main, flagged by maintainer review on this PR).
- Fix: strip the FormControl-generated props only when a FormControl
ancestor is present (useFormControl context); preserve explicit caller
props for standalone usage in both components.
- Verification: new standalone + FormControl-wrapped tests fail on the
prior head and pass after the fix; packages/ui 367/367, apps/web
595/595, tsgo --noEmit clean.
* fix(ui): remove internal label provider dependency
---------
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
* feat(ats): add ATS checker and replace resume analysis
Adds a public, browser-only ATS checker at /ats-checker and an ATS Check
section in the builder's right sidebar. PDFs are parsed locally: text
extraction, reading order, contact and date recovery, section detection,
and file-level readability are scored deterministically, with evidence
cited per finding and skipped checks reported rather than counted as
passes.
Removes the AI-scored resume analysis it supersedes: the resume_analysis
table (dropped via migration), the get_resume_analysis MCP tool, and
POST /ai/analyze-resume. The replacement, POST /ai/ats-review, reviews
extracted resume text and returns qualitative feedback with no score.
Also bumps the version to 5.2.9 and adds the changelog entry.
* chore(deps): bump workspace dependencies
* fix(ats-checker): keep negation inside each 'what this does not do' bullet
The three bullets were bare fragments whose negation came from the
section heading, which translators never see. A dozen locales rendered
them as affirmative assertions or imperatives, so the page claimed the
checker enforces a one-page rule and predicts rejection -- the opposite
of the source, and directly contradicted by the sentence beside it.
Each bullet now carries its own negation, so the polarity cannot be
lost in translation. Re-extracted and refilled across all 53 target
locales.
Adds the questionnaire and empty-state primitives and renders the
ask_user_question tool call inline in the chat, so the agent can offer choices
instead of guessing when a request is ambiguous.
* feat(applications): job application tracker with AI copilot
Add an Applications module at /dashboard/applications: pipeline board
(dnd-kit), table view with bulk actions, Insights (fit tiles, funnel,
sources, shareable funnel-flow SVG), campaigns, tags, CSV import, and
Add/Edit/Detail slide-overs. Each application links a live Reactive
Resume.
AI "Application Copilot" (applications.ai.*): job-posting autofill,
resume↔job match score (fit ring), resume tailoring, and cover-letter /
follow-up drafting — via the user's configured provider.
Board cards + table rows get context menus (edit / move / archive /
delete). Charts are CSS/SVG (no new chart dep); adds a UI Checkbox.
Also includes local TanStack devtools setup and toolchain bumps.
Claude-Session: https://claude.ai/code/session_01TEeRHnEayw2MFCShFRyL5f
* feat(applications): close follow-up gaps + squash migrations
Finish the deferred/open items on the applications tracker:
- Cover-letter upload re-enabled. Fix the storage blocker by deriving the
key extension from content type (buildFileKey/EXTENSION_BY_CONTENT_TYPE)
instead of hardcoding .jpeg, so PDFs serve correctly and non-JPEG image
avatars keep working under FLAG_DISABLE_IMAGE_PROCESSING. Add
coverLetterUrl/coverLetterName columns + Documents-section upload/remove.
- Contacts editor in the detail sheet (add/edit/remove, keyed per app).
- Board caps rendered cards per column (COLUMN_PAGE_SIZE=50 + "Show more").
- Extract new Lingui messages across locales.
- Guard coverLetterUrl to http(s)/relative at the API boundary.
Squash the five branch-only application-table migrations (create -> +tags
-> +cover-letter -> drop -> re-add) into a single clean CREATE TABLE via
drizzle-kit generate.
Claude-Session: https://claude.ai/code/session_01TEeRHnEayw2MFCShFRyL5f
* chore: update dependencies
* fix(web): address React Doctor findings — compiler, purity, query, component structure
prefer-module-scope-pure-function: hoist buildSubtitle, getDecimalPlaces,
handleLocaleChange, onLocaleChange, stop, listContent/groupedListContent to
module scope so they aren't rebuilt on every render.
react-compiler-todo (??=): rewrite draft.metadata.styleRules ??= [] to the
non-assignment form to unblock auto-memoization.
set-state-in-effect: derive updatedAtLabel at render time instead of syncing
it through useState + useEffect.
query-destructure-result: destructure useQuery results at call site in
resume-analysis and resume-thumbnail to follow TanStack Query v5 convention.
only-export-components: extract non-component exports to sibling .ts files so
Fast Refresh can preserve component state:
- getNextWeights → typography/get-next-weights.ts
- detectJsonImportType + ImportType → dialogs/resume/import.utils.ts
- getLocaleOptions → features/locale/locale-options.tsx
- preview helpers + DEFAULT_PDF_PAGE_SIZE → preview.shared.utils.ts
- resolveHighlightToolbarState + defaultHighlightColor → rich-input.utils.ts
- computeDelta + getSparklinePoints → statistics.utils.ts
no-multi-comp: split multi-component files into focused companions:
- ResumePane + ToolbarButton → routes/agent/-components/resume-pane.tsx
- DesktopBuilderShell → builder/$resumeId/-components/desktop-builder-shell.tsx
- MobileBuilderShell + helpers → builder/$resumeId/-components/mobile-builder-shell.tsx
- setBuilderLayout/getBuilderLayout moved to -store/sidebar.ts
fix(tests): add Resume type import to section-builder mocks and cast partial
mock data as unknown as Resume to satisfy stricter type checking; fix
noExplicitAny Biome errors in the same mocks.
* feat(applications): improve performance
* chore: fix knip issues
* perf(builder): halve per-keystroke render cost
Section-form fields called `form.handleSubmit()` on every keystroke, which
re-validated the whole form and toggled submit state — firing the render
cascade twice per character (~6809 renders/keystroke, FPS dropping to 9).
Persist via a form-level `listeners.onChange` instead and drop the per-field
`handleSubmit()` (basics, custom-fields, design). Narrow header/dock resume
subscriptions to metadata slices so they no longer re-render on content edits.
Cuts renders 6809 -> 3403 per keystroke (50%), 0 frame drops. Save, preview,
and design controls verified working; 449/449 web tests pass.
* perf(home): eliminate hero CLS from unreserved video box
The hero <section> is `flex items-center` (shrink-to-fit), so the video
wrapper's width depended on the video's intrinsic size, which only resolves
after the media loads. aspect-ratio couldn't reserve height without a definite
width, so the video grew from ~190px to ~563px after first paint and shoved the
centered hero text down ~373px (CLS ~0.095).
Give the wrapper a definite width (w-full + mx-auto on the CometCard) and set an
explicit aspect ratio + width/height on the video so its box is reserved before
load. CLS 0.095 -> 0; hero stays visually centered at max-w-4xl.
* docs: add application tracker guides
* chore(db): squash application migrations
* fix(email): import React in auth template for server-side rendering compatibility
* chore(release): v5.2.1
* Refactor resume rendering and builder workflows
* fix: address application tracker review findings