Files
Syed Ali Abbas ZaidiandAmruth Pillai 45303fb465 feat(resume): add a deterministic ATS parseability check (#3314)
* feat(resume): add a deterministic ATS parseability check

Adds an offline ATS linter that reports whether a parser can read a
resume, surfaced as an always-on panel in the builder.

The existing Resume Analysis panel needs a configured AI provider, so
users who never set one up get no feedback at all. These 22 rules run
as a pure function over ResumeData with no provider, no network and no
rendered PDF, so they work for everyone on every edit.

Rules cover contact details, date parseability, sections that hold
content but never render, column and sidebar placement, and typography
thresholds. The catalog mirrors the Semantic CSS diagnostic catalog:
stable codes carrying a severity, meaning and action, with no i18n
dependency so the web layer translates by code. Each finding carries a
JSON Pointer, which is what makes jump-to-field work.

Deliberately no second score. Resume Analysis owns overallScore, so
this reports "N of M checks passed" and counts by severity instead.

* fix(resume): accept localized ongoing periods and reject bare ones

Two period-parsing bugs found in review.

The ongoing-token set was English-only, so a German resume reading
"2020 - heute" was reported as unparseable and the panel told the user
to rewrite a perfectly valid range. Rather than guess translations for
55 locales, a range ending that carries no digits and is not a month
name in the resume's locale is now read as ongoing. That keeps a
genuinely incomplete ending such as "Jan 2020 - Feb" reported, since
"Feb" resolves as a month.

A bare "Present" also parsed as a valid period, so an experience entry
with no start date passed the check. A standalone ongoing token is now
rejected; ongoing tokens remain valid as the end of a range.

* feat(web): scroll ATS findings to the item they belong to

Findings for different items in one section all landed on the section
header, so a date problem on the third role gave no more help than
naming the section.

getAtsFindingTarget now resolves the offending item from the finding's
JSON Pointer against the resume, and SectionItem carries a matching DOM
id. The panel scrolls to that item and falls back to the section header
when the item is not mounted, which is what happens while its section
is collapsed.

* fix(resume): recognize ongoing periods by token, not by shape

The previous heuristic read any short, digit-free range ending as an
ongoing marker, so "2020 - unknown", "2020 - later" and "2020 - tbd"
parsed cleanly and suppressed the finding they should have raised.

Replaced with an explicit table of ongoing words keyed by language,
covering the locales the app ships. Matching is exact, so unrecognized
endings are reported again. A locale missing from the table falls back
to the earlier behaviour of reporting its ongoing periods, which is a
visible gap someone can close by adding a word rather than a silent
hole in detection.

Tests assert every listed token parses and that the table stays
lowercase, since lookups normalize that way.

* fix(ats): parse punctuated ongoing tokens

* fix(ats): parse Unicode punctuated ongoing tokens

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-08-13 23:07:55 +02:00
..
2026-05-07 15:12:33 +02:00
2026-05-07 15:12:33 +02:00
2026-05-19 13:14:21 +02:00
2026-05-07 15:12:33 +02:00