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 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).
Crowdin shipped an empty Central Kurdish (ckb-IR) catalog with no translated
strings. The locale is not registered in the Lingui config or locale schema,
so remove the file and its PDF section title entry.
* feat(applications): schedule interviews and view them on a calendar
Interviews (screening, technical, behavioral, onsite, other) are stored as
"interview" entries on an application's activity timeline, so an application
can have any number of them and they show in its timeline without a
migration. Each interview has a start date-time (timezone-aware), duration,
and optional location and notes.
- schema: interview timeline entry type, interviewDetailsSchema, INTERVIEW_KINDS
- api: addInterview / updateInterview procedures (delete via timeline entry);
generic timeline updates now only edit text on note entries
- web: Calendar view on the Applications page (month grid, type legend,
upcoming list grouped by day, schedule button with application picker,
per-day "+" and "+N more" popover), Interviews section and interview
dialog in the application detail panel, interview rows in the timeline
and CSV export
- mcp: add_application_interview / update_application_interview tools
- i18n: extract new strings into all locale catalogs (English fallback)
- docs: MCP tool table, scheduling guide section, resume-builder skill
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(applications): keep interview dates in local time and guard generic timeline edits
- Format the timeline date chip for interview entries in the viewer's local
timezone so it matches the interview's date-time label; stage and note
entries still render in UTC.
- Reject interview entries in the generic updateTimelineEntry path. A
day-granular date edit kept the UTC time of day and could move an interview
to the wrong local day; callers are pointed to updateInterview
(update_application_interview). The MCP tool description now says so, and
a service test covers the rejection.
- Associate each interview dialog label with its control via useId/htmlFor.
- Drop the duplicate onInput handler on the date-time input; onChange covers
controlled inputs.
- Give the calendar's per-day schedule button an accessible name that
includes the date, and update the extracted locale catalogs for the new
message.
* [autofix.ci] apply automated fixes
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat(applications): export applications as CSV
* fix(applications): strip export CSV formula guard on import and resort catalogs
Re-importing an exported CSV kept the apostrophe that csvCell prepends to
formula-triggering cells, so a note starting with "- " came back as "'- ".
mapCsvToApplications now drops a leading apostrophe when the remainder would
have been guarded, sharing the predicate with csvCell so both sides stay in
sync.
Also runs pnpm lingui:extract: the new msgids were hand-appended to en-US.po
and missing from the other 54 catalogs.
* fix(applications): preserve CSV import values
Rewrites the landing page, in-app microcopy, and public docs, then fixes what the rewrite exposed: stale template counts, a broken quickstart anchor, out-of-sync FAQ structured data, dead error-hint branches in the MCP tools, and wrong-sense translations across all 53 locales. Adds GLOSSARY.md so translators get the right sense of the ambiguous UI terms.
* 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.
Upgrades to Better Auth 1.7, expands Custom Styles coverage of item headers, and adds a human-approval step to the AI agent's resume edits.
Breaking for self-hosters using a custom OAuth provider: the callback path changes from /api/auth/oauth2/callback/custom to /api/auth/callback/custom, and installs using OAUTH_DISCOVERY_URL need one additional UPDATE after upgrading. Both are documented in docs/self-hosting/sso.mdx.
- Better Auth 1.7, with the account issuer migration and the jwks alg/crv columns the 1.7 jwt plugin requires
- Agent edits gated behind an approval step, with crash-safe runs and context pruning
- item-header now covers every section header row on every template; adds the item-header-row part
- Fixes provider unlinking, auth error messages, and version conflicts on freshly created resumes
- New /auth/error page, translated across all 53 target locales
- DeepSeek Harness plugin moved into packages/dsh-plugin
- Dependency bumps across the workspace