Commit Graph
809 Commits
Author SHA1 Message Date
Amruth Pillai 0fbeeeb4c4 feat(builder): explain authored page overflow (#3467)
* feat(web): explain authored page overflow

* docs: record plan 23A verification

* chore: remove plan 23A evidence report

* test(pdf): assert authored continuation placement
2026-09-05 19:26:50 -07:00
Amruth Pillai b85d285b69 feat(builder): add one-shot section date sorting (#3465)
* feat(builder): add one-shot section date sorting

* docs: record plan 32 implementation evidence

* chore: remove plan 32 evidence report
2026-09-05 19:06:21 -07:00
Amruth Pillai 999cd618cb feat(web): preserve editable imported tables (#3464)
* fix(web): preserve imported rich-text tables

* fix(web): harden imported table preservation

* fix(web): fail closed on lossy table markup

* chore: remove plan 16 evidence reports

* fix(web): close imported table preservation gaps
2026-09-05 18:41:38 -07:00
Amruth Pillai ab67831e4b feat: add cover and contain picture fitting (#3461)
* feat: add picture fit options

* fix: harden picture fit regressions

* fix: address picture fit review findings
2026-09-05 18:33:35 -07:00
Amruth Pillai 5850230f89 feat(builder): add hidden section recovery (#3462)
* feat(builder): recover hidden sections

* fix(builder): reopen hidden section recovery
2026-09-05 18:33:11 -07:00
Amruth Pillai 2e711fd14c fix(web): render thumbnails at displayed pixel density (#3454)
* fix(web): render thumbnails at displayed pixel density

* fix: cancel obsolete thumbnail raster work
2026-09-05 18:31:21 -07:00
Amruth Pillai a4bdc54b2c fix(builder): save pending drafts before navigation (#3453)
* fix(builder): save pending drafts before navigation

* fix(builder): bound navigation waits for slow saves
2026-09-05 18:31:10 -07:00
Amruth Pillai f468651c79 feat(editor): support whole-paragraph indentation (#3448)
* feat(editor): support whole-paragraph indentation

* fix(docx): retain indentation in quotes and RTL documents

* fix(exports): bound paragraph insets in narrow PDF columns

* fix(pdf): type bounded paragraph rendering consistently

* test(editor): use explicit list conversion commands

* fix(docx): preserve quote inset on list items
2026-09-05 10:41:30 -07:00
Amruth Pillai 5c8338c175 fix(builder): preserve PDF glyph positions in RTL previews (#3447) 2026-09-05 10:31:39 -07:00
Amruth Pillai 14c7c06516 feat: add per-resume public download button preference (#3419) 2026-09-05 10:10:00 -07:00
Amruth Pillai 9fdcec2eca fix(builder): center preview in RTL interfaces (#3446) 2026-09-05 10:09:02 -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
Syed Ali Abbas ZaidiandAmruth Pillai cce6d64afa feat(import): parse a PDF resume without an AI provider (#3400)
* feat(import): parse a PDF resume without an AI provider

Importing a PDF required a connected AI provider, so anyone without a
paid API key could only import the three JSON formats. Almost nobody
arrives with one of those files; they arrive with a PDF. The first thing
a new user tries to do was blocked behind bringing their own key.

Adds a deterministic parser that reads the text out of the PDF in the
browser and prefills the builder. It pulls the contact block, segments
the body on conventional headings, and maps entries to real items,
reusing the ATS period parser for dates so a date range is not mistaken
for a phone number.

Nothing is thrown away: header parts that do not map to a field go into
the description, and unrecognized headings become custom sections. The
imported sections are placed on the page so the result renders straight
away. Output is validated against the resume schema before it is
returned.

Text extraction groups items by baseline rather than trusting hasEOL,
and turns wide column gaps into a double space, which is what lets a
row split into company, position and location.

The AI path still runs when a provider is connected. Word import is
unchanged and still requires one.

Closes #3334

* fix(import): keep every section and entry the PDF actually contains

Review found three ways the parser lost or mangled content, all of them
reproducible.

A document whose first heading was not one of the known aliases never
started a section, because unknown-heading detection was gated on a
section already being open. Everything after it was swallowed as contact
header text. The header block is now bounded by where the contact
details stop, so a heading is recognized wherever it appears.

An entry spreading company, position and dates over three lines was
imported as two malformed items. A line that introduces an entry now
merges into the open entry instead of starting a second one.

An uppercase company such as ACME CORPORATION was read as a section
heading and fragmented the entry. A heading candidate followed by a date
line is now treated as an entry header, which is what it is.

Also escape single quotes, and construct the PDF worker inside the try
so the nested worker is terminated even if construction throws.

Title-case headings are deliberately still not treated as headings:
company and school names are title case too, and splitting on them would
fragment real entries. Such a section stays in the preceding one with its
text intact rather than risking loss.

* fix(import): look past a multi-line preamble before calling a line a heading

The previous guard only inspected the next line, so an uppercase company
followed by a separate role line and then the dates was still read as a
section heading. The experience or education entry was moved into a
custom section and lost.

Heading detection now scans a two-line window for the date that marks an
entry, and stops early at a bullet so a genuine heading whose section
opens with bullet points is still recognized.

The window can suppress a real heading whose first entry puts a bare date
two lines below it. That is the deliberate direction to fail in: a missed
heading leaves the text in the preceding section, while a misread entry
fragments structured content.

* fix(import): collect an entry preamble until its dates appear

An entry that spread company, role, location and dates over four lines
was imported as two broken items: the company with no dates, and the
location carrying the period.

The cause was in entry grouping rather than heading detection. Lines
before a date were only folded into the entry header when the date sat
on the very next line; anything earlier fell through to the description.
Preamble lines are now collected into the entry header until the dates
turn up, bounded by the same lookahead and stopping at a bullet, so an
undated section cannot swallow itself.

The heading lookahead widens to four lines to match, which is the
realistic maximum for company, role, location and dates.

* fix(import): harden local PDF resume parsing

* chore(import): document audited HTML construction

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-09-05 09:53:37 -07:00
Emanuele Tonello ef47baf243 fix(applications): handle cover letter copy failures (#3394)
* fix(applications): handle cover letter copy failures

* style(applications): format clipboard error toast

* refactor(applications): memoize copy draft handler
2026-09-05 09:51:24 -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
Diego Vega CentenoandAmruth Pillai 8df1b25550 feat(skills): add inline layout option for skill items (#3358)
* feat(skills): add inline layout option for skill items

* fix: restore default skills layout (regressed by inline feature)

- Restore metrics rowGap style for default layout
- Only render LevelDisplay inside the row for inline layout, not default

* refactor(pdf):  Extract inline skills style logic from JSX to reusable function

* test(pdf): add test coverage for inline skills item layout

- Add test suite SkillsSectionInlineFormat to verify isInlineSkillsItem and getSkillsItemStyle behavior

* test(pdf): add comprehensive test coverage for inline skills item style logic

- Test combinations of proficiency, level, and keywords fields (0, 1, 3 fields)

* test(schema): add test coverage for column equals 1 when layout is inline

* test(web): add component-level tests for inline and columns layouts

* test(import): add v4 parser-level test for missing skills layout

* docs: regenerate skills layout references

* test(docx): include skills layout in section fixtures

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-09-05 09:51:14 -07:00
Santhi PrakashandAmruth Pillai ea3980cba0 fix(components/form): resolve FormControl label target regressions (#3369) (#3387)
* 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>
2026-09-05 09:35:00 -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
Amruth Pillai fe9b59e111 fix: restore MCP OAuth registration and authorization (#3421)
* fix: align MCP OAuth provider schema and authorization flow

* test: isolate OpenAPI generation from OAuth initialization

* fix: accept auth routes without a callback query

* fix: require explicit OAuth consent and preserve signed requests

* test: verify OAuth audiences through real MCP initialization

* test(e2e): isolate OAuth token audience validation
2026-09-05 09:33:15 -07:00
github-actions[bot]andCrowdin Bot 0207e5dfcc [skip ci] chore(i18n): sync translations from crowdin (#3441)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-09-05 09:02:30 -07:00
Amruth Pillai fa41150723 fix: preserve photo compression during cropping and show upload limits (#3420)
* fix: preserve photo compression during cropping and show upload limits

* fix: bound cropped image size before upload
2026-09-05 08:59:13 -07:00
Amruth Pillai 779ea5cb4a fix(resume): reject invalid submitted write values (#3413) 2026-09-05 08:51:55 -07:00
Amruth Pillai 5a6f5d4d68 fix: label remaining website and picture inputs (#3424)
* fix: connect remaining website and picture labels to inputs

* test(builder): use realistic website input events
2026-09-05 08:51:22 -07:00
Amruth Pillai 0878b256a9 fix(sharing): use neutral social preview image (#3410)
* fix(sharing): use neutral social preview image

* fix(sharing): use neutral server social preview
2026-09-05 08:51:19 -07:00
Emanuele Tonello bf27792ca0 feat(applications): attach generated cover letter PDFs (#3395)
* feat(applications): attach generated cover letter PDFs

* fix(applications): isolate generated cover letter PDFs
2026-09-05 08:51:03 -07:00
Amruth Pillai cd1c597ff0 fix(server): expose build version in health endpoint (#3404)
* fix(server): expose build version in health endpoint

* fix(server): redact public health failure details
2026-09-05 08:50:17 -07:00
Amruth Pillai a3585a24e0 feat(applications): export filtered applications as CSV (#3426)
* 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
2026-09-05 08:46:52 -07:00
github-actions[bot]andCrowdin Bot a12e32ddac [skip ci] chore(i18n): sync translations from crowdin (#3439)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-09-05 08:46:45 -07:00
Amruth Pillai 7d809da6f8 feat(pdf): add opt-in German hyphenation (#3435) 2026-09-05 08:29:22 -07:00
Amruth Pillai 57fee67d2d fix(pdf): render picture borders and soft shadows (#3427)
* fix(pdf): render picture borders and soft shadows

* fix(pdf): preserve picture padding and bound shadow rendering
2026-09-05 08:29:17 -07:00
github-actions[bot]andCrowdin Bot 47fc16d806 [skip ci] chore(i18n): sync translations from crowdin (#3436)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-09-05 08:28:43 -07:00
Amruth Pillai 1f308af728 feat: add compact resume view with session preferences (#3425)
* feat: add compact resume view and session preferences

* test: match resume cards by literal names
2026-09-05 07:32:14 -07:00
Amruth Pillai 321f2fb43f fix(builder): validate and confirm resume passwords (#3407)
* fix(builder): validate and confirm resume passwords

* test(sharing): exercise password confirmation in browser flow
2026-09-05 07:32:10 -07:00
Amruth Pillai 18b5aa4745 fix(sharing): hide signup link when registration is disabled (#3409) 2026-09-05 07:32:07 -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 735e700929 fix(stylesheet): keep color picker state aligned with source (#3431)
* fix(stylesheet): keep color picker state aligned with source

* fix(stylesheet): serialize picker edits as hex with alpha

* fix: preserve contextual colors in stylesheet editor
2026-09-05 07:29:39 -07:00
Amruth Pillai 97ccb4ba06 fix(builder): remove sections emptied by item moves (#3417) 2026-09-05 07:29:32 -07:00
Amruth Pillai 2cbb0f63e7 fix(builder): preserve explicit HTTP URLs (#3403) 2026-09-05 07:29:20 -07:00
Amruth Pillai 00a1357deb fix(applications): show saved notes in detail view (#3402) 2026-09-05 13:26:52 +02:00
Amruth Pillai 84645f122b chore: update dependencies 2026-09-04 11:05:22 +02:00
Amruth Pillai f29b92e2fb chore(copy): rewrite marketing, app, and docs copy to read less AI-generated
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.
2026-08-28 22:18:29 +02:00
Amruth Pillai 3fa9de140c chore: update translations 2026-08-27 07:41:45 +00: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
github-actions[bot]andCrowdin Bot 7fac6f29c0 Sync Translations from Crowdin (#3381)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-08-26 16:25:56 +02:00
Amruth Pillai 3c195dc3f8 Release v5.2.8 (#3375)
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
2026-08-24 21:44:16 +02:00
github-actions[bot]andCrowdin Bot 3221afda9d Sync Translations from Crowdin (#3365)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-08-20 10:38:31 +02:00
Amruth Pillai 8ce899a04b feat(agent): omit resume documents from the copied conversation json 2026-08-20 09:40:57 +02:00
Amruth Pillai 39590eaff6 fix(auth): allow unlinking providers after the session ages past a day (#3364)
Better Auth guards `/unlink-account` with `freshSessionMiddleware`, which
rejects any session whose `createdAt` is older than `freshAge` (one day by
default). Sessions here last a week and there is no re-authentication flow to
refresh that timestamp, so disconnecting a provider failed with
`SESSION_NOT_FRESH` for every user who signed in more than a day ago.

Disable the freshness gate, and teach `getReadableErrorMessage` to read plain
error objects: Better Auth client errors are `{ code, message, status }`
objects rather than `Error` instances, so every auth toast was collapsing to
its generic fallback instead of showing the real reason.
2026-08-20 08:20:18 +02:00
Amruth Pillai c8081ac2fe feat(agent): adopt AI SDK v7 — crash safety, context pruning, HITL approvals (#3362)
* docs(adr): propose agent AI SDK v7 adoption plan

* fix(ai): bind analyzeResume through aiService in service test

The test destructured analyzeResume as a named export that does not exist; main was red.

* test(agent): keep pure ai helpers real via spread-actual mock factory

* feat(agent): add run guards, patch version guard, run wall-clock timeout

* feat(agent): validate UI messages at the send boundary

* feat(agent): crash-safe draft-row persistence and server-side cancellation

* feat(agent): reap stale run claims at boot, on send, and on thread open

* feat(agent): fresh-document patch output and tiered context pruning

* feat(ai): shared agent tool contracts and message metadata schema

* feat(agent): add per-thread review-patches setting with update endpoint

* feat(agent): gate resume patches behind hmac-signed tool approval

* feat(agent): merge question answers and approval decisions before run claim

* feat(agent): approval ui with composed auto-send and fixture-driven tests

* feat(agent): usage metadata, tool activity cards, smoother streaming

* feat(agent): tool-call repair, input examples, structured step logging

* chore(i18n): translate new agent workspace strings across all locales

* fix(agent): gate stale-run draft cancellation on winning the claim clear

Snapshot streaming drafts before the conditional clear and skip the flip entirely when another reaper or a replacement run already cleared the claim. Also address review nits in eleven locale catalogs.

* fix(agent): flip reaped drafts only when their snapshotted state is unchanged

* fix(agent): address review findings across run lifecycle, context budget, and approval flow

- bind patches to the revision the model read via signed baseUpdatedAt
- claim the run before consuming a continuation; recorded-but-unexecuted approvals retry as pending continuations
- keep run ownership on stop() until cancellation persists; preserve the claim for the reaper when final persistence fails
- estimate tokens without serializing binary attachments (tokenx) and enforce the budget by dropping oldest whole turns
- mark crash-recovered patch results as snapshot boundaries; strip /data prefixes at execution time
- retry failed continuations without regenerate; mount a single AgentChat; disable response controls on read-only threads; freeze review toggle during runs (client+server)
- accumulate usage across continuations and match the SDK's nested usage shape; label-form token strings; reorderable source label; accessible note field; state-neutral web-search label

* chore(i18n): translate revised agent strings across all locales

* fix(agent): harden baseUpdatedAt validation and address review follow-ups

- bundle tokenx in the server runtime dependencies (e2e boot failure)
- strict ISO schema for baseUpdatedAt plus loud executor rejection of unparseable values
- it-IT source label consistency (Fonte)
- prove penultimate-turn retention in the context pruning test

* chore(deps): exempt tokenx from knip for the externalized server bundle
2026-08-20 08:06:53 +02:00
github-actions[bot]andCrowdin Bot 128916b9a0 [skip ci] chore(i18n): sync translations from crowdin (#3346)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-08-18 03:50:35 +02:00