Commit Graph
136 Commits
Author SHA1 Message Date
Amruth Pillai 92459122c5 feat(server): convert legacy style rules with a manual script instead of at startup
The conversion of stored legacy style rules to Semantic CSS no longer runs
when the server starts. The image now ships
apps/server/dist/migrate-legacy-styles.mjs, run by hand against
DATABASE_URL:

- without flags it's a dry run that converts in memory and reports counts
- --apply --backup <file> converts, appending every replaced stylesheet to
  the backup file before its row is written
- --restore <file> puts those stylesheets back, except on rows edited since

Each table is scanned once for the rows that need converting, then they're
converted in batches with progress logged. Only metadata.stylesheet is
rewritten, a row whose stylesheet changed after it was read is left alone,
and running it again skips what's converted. The data_migration table that
recorded the startup run is gone. The self-hosting guide explains the
one-time run.
2026-09-29 16:57:21 +02:00
Amruth Pillai c8aead4ff0 feat(server): convert stored legacy style rules once at startup instead of on every read
Stored resumes, resume versions, letters and letter versions still in
the old editor's legacy mode, or carrying legacy style rules with no
stylesheet, are converted to Semantic CSS once, right after the SQL
migrations, and the result is recorded in a new data_migration table so
later starts skip it. Only metadata.stylesheet is rewritten (the rules
stay for rollback), a row edited meanwhile is retried on the next start,
and a failure leaves the data as it was without stopping the server.

The API no longer converts on every read and save. Imports of old
Reactive Resume JSON exports convert their legacy rules on the way in.
2026-09-29 16:25:50 +02:00
Amruth Pillai db97381b3d feat(api): convert legacy style rules to semantic css on every read and save
Resumes from before Semantic CSS carry legacy style rules that only the
old renderer understood. The API now converts them (with the existing,
parity-tested converter) whenever resume data is read or written, so
everything it hands out uses a Semantic CSS stylesheet and the database
catches up on the next save. The rules stay stored for rollback, and a
draft typed in the old editor but never activated is kept, commented
out, after the conversion. New resumes start with an empty stylesheet.
2026-09-29 15:09:57 +02:00
Amruth Pillai 7c33ebae11 chore(api): keep the version summary type private 2026-09-29 10:50:48 +02:00
Amruth Pillai 6e412c2f6b refactor(api): share one version history between resumes and letters 2026-09-29 10:44:03 +02:00
Amruth Pillai 29647ec634 refactor(api): queue resume update notifications with events.on 2026-09-29 10:42:33 +02:00
Amruth Pillai a42cacc057 refactor(api): share cookie, signature and AI error handling instead of copying them 2026-09-29 10:15:56 +02:00
Amruth Pillai 8951f45a3a refactor(api): drop hand-rolled helpers the platform or one caller already covers 2026-09-29 10:13:19 +02:00
Amruth Pillai 2506509538 feat: make cover letters documents of their own
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.
2026-09-29 09:30:07 +02:00
Amruth Pillai cbc76b03b1 refactor!: contract the redesign's legacy fields
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.
2026-09-29 08:34:27 +02:00
Amruth Pillai 49422e98f2 feat: give a letter its own type, colors and page 2026-09-29 08:14:37 +02:00
Amruth Pillai c0bf1aebaf feat(pdf): remove react-pdf and move every consumer to forme
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).
2026-09-29 07:11:40 +02:00
Amruth Pillai 55db11aea8 feat(api): include applications in the account export
Export everything downloads documents and applications together, so the account export now returns the user's applications, without the owner id.
2026-09-29 00:33:29 +02:00
Amruth Pillai 6463a9e9c1 feat(api): an assistant bound to a resume or letter that proposes edits
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.
2026-09-29 00:11:00 +02:00
Amruth Pillai fdad39c632 feat(api): structured letters with live links, versions and a streaming draft 2026-09-28 23:08:04 +02:00
Amruth Pillai ce0b4c606a feat(api): a closed stage with reasons, what was sent, posting requirements and a posting reader
- 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.
2026-09-28 21:44:55 +02:00
Amruth Pillai 1608d56903 feat(api): passage rewrites in the writing review, and a resume's linked application
- 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.
2026-09-28 21:33:18 +02:00
Amruth Pillai e26ce08fc5 feat(resume): check categories, stable issue keys, ignores and a two-column rule
- 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.
2026-09-28 21:33:11 +02:00
Amruth Pillai d4406c729b feat(api): name imports after their person and keep locked documents' details
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.
2026-09-28 20:38:32 +02:00
Amruth Pillai a568f64b43 feat(api): one documents library with Trash, job links and automatic names
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.
2026-09-28 19:59:03 +02:00
Amruth Pillai 060750a868 feat(api): version kinds, session autosaves, named versions and slug redirects
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.
2026-09-28 19:46:41 +02:00
Amruth Pillai bda01febd5 feat(schema): structured resume dates with a read-time upgrade and dual write
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.
2026-09-28 18:20:18 +02:00
FalconSpyClaude Opus 5.5Amruth Pillaiautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1b78e546e2 feat(applications): schedule interviews and view them on a calendar (#3539)
* 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>
2026-09-28 08:57:25 +02:00
Amruth Pillai d0d20ce0fd chore(deps): upgrade dependencies
Bump workspace dependencies to their latest versions and dedupe the lockfile.

The upgrade left stale duplicates in pnpm-lock.yaml that broke the build and tests:

- @deepseek-ai/schemastery resolved to both 3.18.2 and 3.18.4. Both copies declare
  the global Schemastery namespace, so dsh-plugin's declaration emit failed with
  TS2883 on `Config`. `pnpm dedupe` collapses it to 3.18.4.
- vite's optional tsx peer resolved to 4.23.13 for importers without a direct tsx
  dependency and 4.23.15 elsewhere, producing two vitest 5.0.2 instances. Loading
  both in one run broke `expect(...).rejects`. Re-resolving tsx unifies the graph.
2026-09-28 00:23:21 +02:00
Amruth Pillai 8c40313980 feat(deploy): support Vercel Hobby alongside Docker (#3541)
* feat(deploy): support Vercel Hobby alongside Docker

* fix(deploy): include PDFKit runtime font assets

* docs(deploy): document Vercel and Docker setup

* docs(deploy): record storage persistence checks

* refactor(deploy): drop scheduled staging cleanup

Staging uploads are deleted after finalization and expired ones are swept
on each new upload, so the Vercel cron job, its route, and CRON_SECRET are
no longer needed. The Deploy with Vercel wizard now asks for two secrets.

* docs(deploy): restructure Vercel guides

Split the Vercel page into a how-to with its environment reference, move the
large RPC staging protocol to an API reference page, and move CI deployment
checks to the contributing section. Point Deploy with Vercel buttons at main.

* chore: remove agent planning records and fix web app description

Delete superpowers plans/specs, ADRs, issue plans, execution briefs, domain
context maps, and Europass research. Describe apps/web as a TanStack Router
SPA served by apps/server.

* refactor(deploy): simplify Vercel support code

- Share one Redis client and key namespace through @reactive-resume/db/redis
  for API and auth instead of a second auth-only client.
- Drop the auth seeding retry; the provider already treats concurrent inserts
  as no-ops and deployment preparation seeds before runtime.
- Detect staging support from POST /api/storage/stage (404 on Docker) instead
  of a separate GET probe.
- Read staged bodies directly; the signed upload already caps their size.
- Close per-subscription Redis connections with disconnect() alone.
- Check Blob health with one list call instead of write/read/delete.
- Remove redundant tsdown onlyBundle list, dead namespace fallbacks, and the
  conditional spread in the health status.

* fix(deploy): heal stopped runs with dead owners and keep auth up without Redis

- Run owners refresh a Redis heartbeat until they release their claim. Stop
  requests reap the run immediately when the owner has stopped heartbeating,
  instead of leaving the thread blocked until the 15-minute TTL reaper.
- Auth and oRPC rate limiters fall back to per-instance memory limits when
  Redis errors, instead of rejecting every login or failing requests.

* ci: allow esbuild build for Vercel CLI and register deployment deps with knip

pnpm 12 fails dlx installs with ignored build scripts, so allow esbuild
explicitly. The server bundle keeps @vercel/blob, ioredis, and jose external,
and api/index.mjs is the Vercel Function entry.

* fix(web): send buffered RPC bodies instead of teed streams

Reading a request clone turned the original body into a stream, which
browsers send without inspectable request data and which needs duplex
mode. Send the already buffered Blob for direct requests.

* fix(web): send direct RPC bodies as bytes

Blob request bodies are sent as data pipes, so browser tooling cannot
inspect them. Buffer the original request as an ArrayBuffer and send those
bytes; this restores the e2e save assertions that match on request data.
2026-09-26 02:37:22 +02:00
Amruth Pillai 4fde62df6d chore: update dependencies 2026-09-19 22:05:55 +02:00
Amruth Pillai b953435f2c fix: audit code for reduction 2026-09-17 22:16:44 +02:00
Emanuele TonelloandAmruth Pillai fbf1f8fbac docs(api): describe cover letter endpoints (#3509)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-09-17 00:13:21 +02:00
Amruth Pillai 96c7142fbc chore: update dependencies 2026-09-16 18:36:50 +02:00
Amruth Pillai f89acb4368 chore: migrate repository links to reactive-resume/reactive-resume 2026-09-12 11:18:32 +02:00
Amruth Pillai d77cb93494 fix: complete repository links and container publishing migration 2026-09-11 11:09:55 +02:00
Amruth Pillai 9550910f17 revert: remove repository migration changes from main 2026-09-11 03:23:27 +02:00
Amruth Pillai 31d6ee6251 chore: prepare repository migration and Docker Build Cloud publishing 2026-09-11 02:55:52 +02:00
Amruth Pillai dc8f9787a4 chore: update dependencies 2026-09-09 23:57:37 +02:00
Amruth Pillai 607eafd3e8 chore(deps): bump ai-sdk, aws-sdk, react-email and tooling dependencies
Patch and minor bumps across the AI provider SDKs (@ai-sdk/*, ai),
@aws-sdk/client-s3, react-email/@react-email/ui, knip and jszip, with
pnpm-lock.yaml regenerated to match.

Also records the audit overrides and patched dependencies in the root
package.json alongside the existing pnpm-workspace.yaml entries.
2026-09-09 12:16:20 +02:00
Amruth Pillai 6416da28a4 feat(homepage): rebuild landing page and fix untranslated homepage strings in 16 locales (#3496) 2026-09-08 18:01:59 +02:00
Amruth Pillai d915ba3670 chore: remove Atlas Cloud sponsor placement 2026-09-06 16:59:14 +02:00
Amruth Pillai df2e21ef9e fix: link cover letters to templates and tidy builder UI
- Fix Grid/Compact/List tab overlap on the resumes dashboard: the fixed
  three-column grid forced cells narrower than their labels, so tab content
  spilled into neighboring cells.
- Replace the "Resume styling" resume picker with a template picker in the
  cover-letter create form and editor. The API accepts a `template` on create
  and update, and refreshing style from a resume no longer overwrites it. The
  resume control remains in the editor as "Sender details" since it is the
  only source for the letter header.
- Remove the cover-letter library button from the builder sidebar and add an
  "Import from library" option to the create-cover-letter dialog. Resume to
  library copying stays in the library with its own resume picker.
- Remove the authored-pages/PDF-overflow note from the layout sidebar.
2026-09-06 10:26:12 +02:00
Amruth Pillai 7fef84078d chore: update dependencies 2026-09-06 08:46:54 +02:00
Amruth Pillai 744eaa902e feat(sharing): serve a configured public resume at root (#3470) 2026-09-05 19:42:44 -07:00
Amruth Pillai 14c7c06516 feat: add per-resume public download button preference (#3419) 2026-09-05 10:10:00 -07:00
Amruth Pillai 1d4194a207 feat: manage cover letters in a shared library (#3423)
* feat: add shared cover-letter library with resume styling

* fix: retain required sanitizer dependency in CI

* fix(cover-letters): prevent concurrent AI draft requests

* chore(codacy): exclude generated migrations

* fix(applications): keep Lingui macro out of callback dependencies
2026-09-05 10:01:22 -07:00
Emanuele TonelloandAmruth Pillai 1f0844b39c feat(applications): add contact email and phone (#3396)
* feat(applications): add contact email and phone

* fix(applications): validate imported contact emails

* fix(applications): validate all imported contact fields

* fix(applications): preserve data when contact validation fails

* test(applications): complete contact export fixture

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-09-05 09:51:21 -07:00
Santhi PrakashandAmruth Pillai 861ba8bf60 fix(ai): make provider test timeout configurable via AI_TEST_TIMEOUT_MS (#3384)
* fix(ai): make provider test timeout configurable via AI_TEST_TIMEOUT_MS

- Problem: the 30s hardcoded timeout is too short for self-hosted
  deployments with cold-start models (e.g. Ollama). Makes it impossible
  to pass the provider test (issue #3374).
- Fix: read AI_TEST_TIMEOUT_MS from the environment, defaulting to 30_000.
  Zero behaviour change when the env var is absent.
- Verification: existing test asserts "30 seconds" in the timeout
  message; default is unchanged so the test continues to pass.
  (CI needs Node 22+ — not available on this host.)

* fix(ai): add AI_TEST_TIMEOUT_MS to turbo globalEnv so it reaches the API process

- Problem: Turborepo filters env vars not listed in globalEnv, so
  AI_TEST_TIMEOUT_MS would always be undefined at runtime under
  turbo dev/start, making the override dead code.
- Fix: add AI_TEST_TIMEOUT_MS to the globalEnv array.
- Verification: turbo.json validates as valid JSON.

* fix(ai): validate AI_TEST_TIMEOUT_MS as a finite non-negative integer

* docs(ai): add JSDoc to timeout parser and test helper

* test(ai): restore AI_TEST_TIMEOUT_MS after timeout tests

- Problem: loadWithTimeout() mutates process.env.AI_TEST_TIMEOUT_MS but nothing restores it, so the last value tested ("999999999999") leaked to every test that runs after this describe block in the same file.
- Fix: save the pre-test value and restore it in an afterEach hook.
- Verification: pnpm exec vitest run src/features/ai/service.test.ts in packages/api — 18/18 passed.

* test(api): isolate AI timeout environment cases

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-09-05 09:35:14 -07:00
Amruth Pillai cddb01f037 fix(sharing): record public PDF download statistics (#3414)
* fix(sharing): record public PDF download statistics

* docs(api): explain download statistics access cookie
2026-09-05 09:34:52 -07:00
Santhi Prakash c4eb9d860b fix(api): translate copilot AI provider failures to BAD_GATEWAY (#3333)
* fix(api): translate copilot AI provider failures to BAD_GATEWAY

- Problem: AI provider errors (bad key, unknown model, quota, 5xx) from the
  AI SDK bubble out as opaque 500 INTERNAL_SERVER_ERROR from copilot
  endpoints (autofill, match-score, draft-message, tailor-resume).
- Fix: catch AISDKError in generatePlainText and a local generateJson
  wrapper that delegates to the shared generate-json module, translating
  both to BAD_GATEWAY (502) with the original error preserved as cause.
  Mirrors the existing pattern in features/ai/router.ts.
- Verification: vitest (CI — requires Node 22+). Test file unchanged in
  assertion logic from the original PR; the local generateJson now
  wraps the shared module instead of duplicating it.

Rebased onto main after v5.2.9 AI-layer refactor (generateJson extracted
into features/ai/generate-json.ts).

* fix(api): align generateJson prompt shape with callers and shared module

- Problem: local generateJson wrapper accepted (model, prompt: string,
  schema) but all callers pass (model, { prompt: string }, schema).
  Caught by CodeRabbit review.
- Fix: match the shared generate-json module signature — accept
  { system?, prompt } as the second argument and pass it through.
  Updated test calls to match.

* fix(test): remove stray leading dots from mock object property names

- Problem: rebase onto v5.2.9 introduced `.use`, `.output`, `.errors`
  as property names in the chain mock object, which is invalid JS
  syntax and would cause a parse error when tests run.
- Fix: remove the leading dots to restore valid property names.
- Verification: cat -A confirms tabs-only indentation, no leading dots.

* fix(docs): correct 'a actionable' to 'an actionable' in comment

- Problem: Grammar typo in inline comment.
- Fix: 'a actionable' → 'an actionable'.
- Verification: grep confirms no remaining instances.

* fix(api): narrow copilot AI BAD_GATEWAY predicate to APICallError and exhausted RetryError
2026-09-05 09:33:18 -07:00
Amruth Pillai 779ea5cb4a fix(resume): reject invalid submitted write values (#3413) 2026-09-05 08:51:55 -07:00
Amruth Pillai 35cecf9c91 fix(storage): support S3 buckets with object ACLs disabled (#3432) 2026-09-05 07:29:42 -07:00
Amruth Pillai 84645f122b chore: update dependencies 2026-09-04 11:05:22 +02:00
Amruth Pillai c288675b16 Release v5.2.9 (#3382)
* 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.
2026-08-27 03:37:01 +02:00