Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[parser] A comma in a role title corrupts our own export → re-parse round trip: the title is cleaved and company/team slide one slot

Open
#934 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript

Research direction

Start with splitRoleComma in src/lib/heuristics/extract/experience-disambiguate.ts and its use from mapTitleFirst, then inspect composeRoleHeader and the Kilo case in role-header-cases.ts. Run src/lib/pdf/role-header-production-domain.test.ts and the #466 cases first; done means the Kilo row is invertible, the empty-company dialect still passes, snapshots are reviewed, and npm run verify is green.

Written by the indexing model from the issue text.

Description

bug
Problem

A role title containing a comma — Director, Marketing / Engineer, Sr., one of the most
common real résumé header shapes — is corrupted by our own export → re-parse round trip.
The title is cleaved at the comma and every later field slides one slot over.

Measured on the real production leg (buildAtsResumeModel → renderAtsResumePdf →
runCascade):

in out
{ title: "Kilo Engineer, Sr.", company: "Globex" } { title: "Kilo Engineer", company: "Sr.", team: "Globex" }

The company is gone, replaced by the title's own tail; the real company becomes the team.

Why it happens

The exporter composes the default dialect as Title · Company, Location · Team
(src/lib/resume-format/role-header.ts → composeRoleHeader; on main this is inline in
src/lib/pdf/ats-resume-model.ts, the empty-company branch at :790 and the default join at
:811). ORG_COMMA is load-bearing: it is what marks the company↔location boundary for the
re-parser.

On the way back, splitRoleComma
(src/lib/heuristics/extract/experience-disambiguate.ts:386, used from mapTitleFirst
at :780) cleaves segment 0 at its comma — it cannot tell a Title, Team empty-company
header (#466, where the comma IS the separator) from a title that simply contains a comma.
Both are <text>, <text> in segment 0.

So the two dialects are genuinely ambiguous as composed: the format has no way to say
"this comma is user text, not a separator."

Scope
  • Round trip only. A first parse of a third-party PDF is not affected — this is the
    re-parse of a résumé our own Download-PDF drew.
  • Affects the reconstructed-résumé round trip (corpus-roundtrip.test.ts lane) and
    anything that re-ingests an exported file (saved-library re-parse, the render-hop gate).
Where it is already pinned

PR #922 added src/lib/pdf/role-header-production-domain.test.ts, which runs the real leg.
The corruption is recorded as measured data in
src/lib/resume-format/__test-utils__/role-header-cases.ts →
PRODUCTION_DIVERGENT_CASES, row Kilo, and stated as clause 4 of the invertible domain
in role-header.ts's docblock. It is pinned as current behaviour, not as desired behaviour
— so a fix must move that row out of PRODUCTION_DIVERGENT_CASES and into
INVERTIBLE_CASES, which is exactly the regression signal.

The same table records two further divergences, both far narrower and both arguably
acceptable losses:

  • a team containing a middot loses everything after the first middot (row India);
  • untrimmed field padding does not survive (row Lima).

This issue is about the comma case, because that one is a common shape and loses a whole
field.

Directions for a fix (not yet decided)
  1. Disambiguate at compose time. Only the empty-company dialect uses a bare
    Title, Team; the default dialect already has a middot before the org run. splitRoleComma
    could be gated on the absence of a middot segment, which is information the parser
    already has.
  2. Escape or avoid the comma at compose time for the empty-company dialect — pick a
    separator user text cannot collide with. Changes rendered bytes, so it needs its own
    reviewed snapshot sweep (same constraint as the date-range dialects, #649 step 3).
  3. Prefer the longer title. When segment 0 splits at a comma AND a later segment exists,
    the tail is much more likely to be title text than a company.

Option 1 looks cheapest and touches no rendered byte; it needs checking against the #466
regression the comma dialect exists to fix, and against real fixtures, not just the
synthetic rows.

Acceptance criteria
  • { title: "Kilo Engineer, Sr.", company: "Globex" } round-trips exactly through the
    real buildAtsResumeModel → renderAtsResumePdf → runCascade leg.
  • The Kilo row moves from PRODUCTION_DIVERGENT_CASES to INVERTIBLE_CASES, and
    clause 4 of role-header.ts's domain docblock is narrowed or dropped accordingly.
  • The #466 empty-company Title, Team dialect still round-trips (its own rows stay in
    INVERTIBLE_CASES).
  • Corpus snapshots reviewed: any byte change is deliberate and re-baselined in the same
    PR.
  • npm run verify green.

Found during the PR review of #922, which discovered and documented the divergence but
correctly left the fix out of scope.

Dominant language
TypeScript
Stars
11
Forks
4
Avg merge
1d 5h
Merged PRs (30d)
66

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from offlinecv/OfflineCV

All issues in offlinecv/OfflineCV

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.