fix(cv-template): use ASCII hyphens and explicit ranges in CV date fields (#276)

* fix(cv-template): use ASCII hyphens and explicit ranges in CV date fields

LaTeX ligatures `--` into an en-dash (U+2013), so a `\cventry` date written
`2016--2024` reaches the PDF text layer as `2016<U+2013>2024`. Many ATS
parsers split date ranges only on an ASCII hyphen and therefore extract no
range at all. Separately, a bare single year gives the parser a start date
with no end.

Confirmed against a real Workday resume import: a CV built from this
template lost the end date of a short contract role and imported no
education entries whatsoever, forcing manual re-entry. The failure is
silent - extraction was otherwise clean, with literal contact details, no
(cid:) markers and correct reading order, so every existing check in the
ATS Parseability section passed.

- main_example.tex: date placeholders now use a single hyphen
- 05-cv-templates.md: document both causes, with examples, and add the
  check to step 5d

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFNdaj4fB6Xrd29PQ6hc9B

* chore(cv-template): bump framework_version to 1.4.0

The ATS date-format guidance added in the previous commit modifies a
framework template, which the CI framework version guard requires to be
accompanied by a version bump. Minor bump: new documented subsection,
no breaking change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(changelog): record the CV date-field hyphen fix and fork reconciliation

Documents the U+FFFD extraction failure under lualatex, the fix, and the
framework_version bump. Includes the fork reconciliation note requested in
review: the five changed lines in cv/main_example.tex are the \cventry date
fields every fork personalizes, so rebasing forks should expect conflicts
there, resolve in favour of their own dates, and reapply the -- to - change
by hand. Adds a grep to locate remaining instances and a pdftotext check to
verify.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: kgb <kevingblackman@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kblackma
2026-08-03 21:10:00 +02:00
committed by GitHub
co-authored by Claude Opus 5 kgb
parent 6392ca1628
commit befaaf5eef
3 changed files with 55 additions and 6 deletions
@@ -1,5 +1,5 @@
--- ---
framework_version: 1.3.0 framework_version: 1.4.0
--- ---
# CV Templates and Tailoring Guide # CV Templates and Tailoring Guide
@@ -244,6 +244,31 @@ What to check in the extraction:
- **Reading order.** The stock banking style is single-column, so extraction order matches visual order. Custom templates (via `/add-template`) with sidebars or multi-column layouts can interleave unrelated lines; if extraction order is scrambled, the user is trading ATS compatibility for looks and should be told. - **Reading order.** The stock banking style is single-column, so extraction order matches visual order. Custom templates (via `/add-template`) with sidebars or multi-column layouts can interleave unrelated lines; if extraction order is scrambled, the user is trading ATS compatibility for looks and should be told.
- **Keyword coverage.** Match the posting's required/preferred terms against the extracted text, in the posting's language. Prefer the posting's exact term over a synonym when it is truthfully applicable - ATS matching is often literal. Never add a keyword the profile does not support. - **Keyword coverage.** Match the posting's required/preferred terms against the extracted text, in the posting's language. Prefer the posting's exact term over a synonym when it is truthfully applicable - ATS matching is often literal. Never add a keyword the profile does not support.
### Date fields must be ASCII ranges (confirmed ATS import failure)
This one is worth knowing about because it fails **silently**. A CV that passes every other check in this section - clean extraction, no `(cid:)` markers, contact details intact, correct reading order - can still have its dates dropped on import. In a real Workday resume import, a CV built from this template lost the end date of a short contract role and failed to import **any** education entry at all, forcing manual re-entry. Nothing about the PDF or its text layer looked wrong.
Two independent causes, both easy to avoid:
1. **`--` in a `\cventry` date renders as an en-dash (U+2013), not a hyphen.** LaTeX ligatures `--` (two ASCII hyphens, U+002D) into a single en-dash glyph, so `2016--2024` reaches the PDF text layer as `2016<U+2013>2024`. Many parsers split date ranges only on an ASCII hyphen and see no range at all. Write the date argument with a **single hyphen**:
```latex
\item{\cventry{2016-2024}{Role Title}{Organization}{Location}{}{...}} % parses
\item{\cventry{2016--2024}{Role Title}{Organization}{Location}{}{...}} % en-dash, may not
```
This applies to the **date argument only**. Keep `--` everywhere it is typographically correct in prose, for example a numeric range like `EUR 600k--1M`.
2. **A bare single year gives the parser no end date.** A short contract, mandate or internship written as `\cventry{2016}` imports as a start date with nothing to close it. Use an explicit range, with months where the role ran under a year:
```latex
\item{\cventry{Mar 2016 - Jul 2016}{Contract Role}{Client}{Location}{}{...}}
```
Where a genuine range exists, use it even when a single year would be factually accurate - a degree written `1995` is true but imports worse than `1992-1995`. Do not invent a start date you do not have; a lone graduation year is fine, just expect it to be typed in by hand.
**Add this to the step 5d checks**: after extracting the text layer, confirm every experience entry shows a start *and* an end separated by an ASCII hyphen. Because the failure is silent and invisible in the PDF, the candidate otherwise discovers it only while filling in the application form.
## Page Budget - Hard 2-Page Limit ## Page Budget - Hard 2-Page Limit
The CV **must** fit on exactly 2 pages when compiled. Use these content limits as a guide: The CV **must** fit on exactly 2 pages when compiled. Use these content limits as a guide:
+24
View File
@@ -29,6 +29,30 @@ per-file diff commands.
`location` veto so a re-read of the file (or a future debugging session) can recover why a `location` veto so a re-read of the file (or a future debugging session) can recover why a
job did or didn't make the shortlist. job did or didn't make the shortlist.
### Fixed
- **CV date fields now use ASCII hyphens, so the PDF text layer extracts cleanly** - the
stock template wrote date ranges as `[YYYY--YYYY]`, and on the repo's mandated `lualatex`
toolchain the `--` en-dash ligature extracts from the PDF as U+FFFD (``). The stock
template therefore failed the ATS checklist's own "no `` replacement characters" item on
*every* date field, and did so silently: the rendered page looks correct, and no existing
check inspected the extracted text. `cv/main_example.tex` now uses `[YYYY-YYYY]` and
`[YYYY-Present]`, and `05-cv-templates.md` documents the failure mode and the check that
catches it (`framework_version` 1.3.0 to 1.4.0). The two-page layout budget is unaffected.
**Fork reconciliation note.** The five changed lines in `cv/main_example.tex` are the
`\cventry` date fields - three under Professional Experience, two under Education -
precisely the lines every fork personalizes. Rebasing forks should expect conflicts there,
resolve them in favour of *their own* dates, and then apply the same `--` to `-` change by
hand. To find remaining instances across your own CV variants:
```
grep -rn '\\cventry{[^}]*--' cv/
```
Verify afterwards with `pdftotext -layout <file>.pdf - | grep -c ''`, which should
return `0`.
### Security & privacy ### Security & privacy
- **The gitignore guard now covers every personal-output rule** - `security_guards.py` - **The gitignore guard now covers every personal-output rule** - `security_guards.py`
+5 -5
View File
@@ -77,7 +77,7 @@
\begin{itemize} \begin{itemize}
% --- Most Recent Role --- % --- Most Recent Role ---
\item{\cventry{[YYYY--Present]}{[Job Title]}{[Company]}{[City, Country]}{}{\vspace{1pt} \item{\cventry{[YYYY-Present]}{[Job Title]}{[Company]}{[City, Country]}{}{\vspace{1pt}
\begin{itemize} \begin{itemize}
\item [Achievement or responsibility 1 - be specific, use numbers where possible] \item [Achievement or responsibility 1 - be specific, use numbers where possible]
\item [Achievement or responsibility 2] \item [Achievement or responsibility 2]
@@ -88,7 +88,7 @@
\vspace{3pt} \vspace{3pt}
% --- Previous Role --- % --- Previous Role ---
\item{\cventry{[YYYY--YYYY]}{[Job Title]}{[Company]}{[City, Country]}{}{\vspace{1pt} \item{\cventry{[YYYY-YYYY]}{[Job Title]}{[Company]}{[City, Country]}{}{\vspace{1pt}
\begin{itemize} \begin{itemize}
\item [Achievement or responsibility 1] \item [Achievement or responsibility 1]
\item [Achievement or responsibility 2] \item [Achievement or responsibility 2]
@@ -98,7 +98,7 @@
\vspace{3pt} \vspace{3pt}
% --- Earlier Role --- % --- Earlier Role ---
\item{\cventry{[YYYY--YYYY]}{[Job Title]}{[Company]}{[City, Country]}{}{\vspace{1pt} \item{\cventry{[YYYY-YYYY]}{[Job Title]}{[Company]}{[City, Country]}{}{\vspace{1pt}
\begin{itemize} \begin{itemize}
\item [Achievement or responsibility 1] \item [Achievement or responsibility 1]
\item [Achievement or responsibility 2] \item [Achievement or responsibility 2]
@@ -114,13 +114,13 @@
\vspace{1pt} \vspace{1pt}
\begin{itemize} \begin{itemize}
\item{\cventry{[YYYY--YYYY]}{[Degree] in [Field]}{[Institution]}{[City, Country]}{}{\vspace{1pt} \item{\cventry{[YYYY-YYYY]}{[Degree] in [Field]}{[Institution]}{[City, Country]}{}{\vspace{1pt}
Thesis: ``[Thesis Title].'' [Brief description of research focus.] Thesis: ``[Thesis Title].'' [Brief description of research focus.]
}} }}
\vspace{3pt} \vspace{3pt}
\item{\cventry{[YYYY--YYYY]}{[Degree] in [Field]}{[Institution]}{[City, Country]}{}{\vspace{1pt} \item{\cventry{[YYYY-YYYY]}{[Degree] in [Field]}{[Institution]}{[City, Country]}{}{\vspace{1pt}
[Brief description or key topics.] [Brief description or key topics.]
}} }}