Skip to content

fix(cv): print course-table column headers as written in the record - #21

Merged
eduardstan merged 3 commits into
mainfrom
fm/lp-column-keys-verbatim
Jul 30, 2026
Merged

eduardstan merged 3 commits into
mainfrom
fm/lp-column-keys-verbatim

Conversation

@eduardstan

Copy link
Copy Markdown
Owner

Intent

Stop deriving casing of course-table column headers so row keys print verbatim

What Changed

  • Course-table column headers now print row keys verbatim in both outputs: tableHeader in scripts/build-cv-data.mjs takes a displayKey that defaults to identity, and web/src/pages/cv.astro drops its headingCase label wrapper from <th> and data-label. The per-section \cv<Key>Header macros keep title case by passing headingCase explicitly, so section headings and the printed layout are unchanged.
  • content/cv.yaml and the website fixture record carry the capitals themselves (Course, Programme, Topics, Points), since casing is now the adopter's to write.
  • Doc comments on both headingCase copies and content/README.md were rewritten to state the narrower rule — derived section/announcement keys are recased, record keys are not — and the tests in scripts/build-cv-data.test.mjs and web/src/lib/cv.test.ts were updated to match.

Risk Assessment

✅ Low: The follow-up commit restores cv/generated/cv-data.tex byte-for-byte to the base commit while keeping course-table headers verbatim, pins both callers of tableHeader with tests, and leaves only a one-line stale comment.

Testing

Ran the generator unit suite and the website record self-checks (cv, live-record, announcements) plus the generated-data freshness gate — all green. Because both the bundled record and the fixture record now use already-capitalised course keys, unit passes alone cannot show verbatim behaviour, so I built two PDFs: the real record (course table and small-caps section headers unchanged) and a scratch repo copy whose course keys are deliberately mixed-case, where the printed table shows "Programme / level", "Key topics" and "points" exactly as written instead of the old derived "Programme / Level", "Key Topics", "Points". The same mixed-case record served through astro dev renders those keys verbatim in the table markup, in the mobile data-label, and in the visible "keys read" provenance line; the site's own CSS uppercases table headers, so on-screen header glyphs look identical either way and the casing evidence there is the provenance line and markup rather than the header cells. Screenshots of both surfaces are attached; PDF and staged web build outputs were removed afterwards, leaving the worktree clean.

  • Evidence: Printed CV course table from a mixed-case record — keys print verbatim (local file: /tmp/no-mistakes-evidence/01KYTDGGBAG1JJRXT834TTPZZF/pdf-verbatim-course-table-1.png)
  • Evidence: Printed CV course table from the bundled record — unchanged (local file: /tmp/no-mistakes-evidence/01KYTDGGBAG1JJRXT834TTPZZF/pdf-real-course-table-1.png)
  • Evidence: /cv/ with inspect sources on, mixed-case record — provenance line reads the keys verbatim (local file: /tmp/no-mistakes-evidence/01KYTDGGBAG1JJRXT834TTPZZF/site-cv-verbatim-keys.png)
  • Evidence: /cv/ on the bundled record — teaching table unchanged (local file: /tmp/no-mistakes-evidence/01KYTDGGBAG1JJRXT834TTPZZF/site-cv-courses-real-record.png)
  • Evidence: Full CV PDF page 1 rendered from the bundled record (local file: /tmp/no-mistakes-evidence/01KYTDGGBAG1JJRXT834TTPZZF/page-1.png)
Evidence: Generated LaTeX and website markup for the mixed-case record
$ grep 'cvcourses{' cv/generated/cv-data.tex
\cvcourses{Y Y Y Y}{\textbf{Course} & \textbf{Programme / level} & \textbf{Key topics} & \textbf{points} \\}{

$ curl -s http://127.0.0.1:4401/ledgerpress/cv/ | grep -o '<th scope="col">[^<]*</th>\|data-label="[^"]*"'
<th scope="col">Course</th>
<th scope="col">Programme / level</th>
<th scope="col">Key topics</th>
<th scope="col">points</th>
data-label="Course"
data-label="Programme / level"
data-label="Key topics"
data-label="points"

$ # section-header macros still title-case their schema fields
\textbf{Title} & \textbf{Org} & \textbf{Place} & \textbf{Dates} & \textbf{Detail} & \textbf{Url} & \textbf{Items} & \textbf{Announced} \\
- Evidence: Scratch mixed-case build (record + generated tex + PDF) (local file: /tmp/no-mistakes-evidence/01KYTDGGBAG1JJRXT834TTPZZF/verbatim-build)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ scripts/build-cv-data.mjs:586 - tableHeader is used by two callers, and only one is a course table. Line 586 builds the per-section \cv&lt;Key&gt;Header macros from entry field names, which are schema-fixed (title, org, place, dates, items, announced) and always lowercase — an adopter cannot re-case them in the record without breaking readCv. Dropping headingCase there regressed the committed output: cv/generated/cv-data.tex:119 now reads \textbf{title} &amp; \textbf{org} &amp; \textbf{place} &amp; \textbf{dates} &amp; \textbf{detail} &amp; \textbf{url} &amp; \textbf{items} &amp; \textbf{announced} \\ where it read \textbf{Title} &amp; \textbf{Org} &amp; …. The bundled layout supplies its own headings (cv/preamble.tex:195), so no PDF or baseline moves, but the generator's own header comment (scripts/build-cv-data.mjs:17) documents \cv&lt;Key&gt;Header as one of the six macros an adopter gets for free, and a fork printing \cvAppointmentsRows under \cvAppointmentsHeader now gets lowercase column headings with no record-side fix. The intent covers course-table column headers only; keep headingCase(k) on the line-586 path and print verbatim only from entry().
  • ℹ️ web/src/lib/cv-schema.ts:611 - The rewritten doc comments and the deleted cv.test.ts note removed the "two copies of one rule across the build boundary, they must agree" warning from both headingCase definitions. That invariant is narrower now but still live: section keys go through sectionHeading (scripts/build-cv-data.mjs:277) for the printed heading and through singular (web/src/lib/announcements.ts:264) for the /lately/ chip, so field_work must read "Field Work" in both, and nothing crosses the plain-node/Vite boundary to enforce it. The new comment says only "Used for announcement kind chips on /lately/", which no longer warns an editor of the other copy. Restore a one-line cross-reference on both sides scoped to section keys.
  • ℹ️ web/src/pages/cv.astro:75 - const label = (key: string) =&gt; key; is now an identity function, called at cv.astro:202 and cv.astro:210. Inline key at both call sites and delete the binding.
  • ℹ️ web/src/lib/cv.test.ts:200 - The retained headingCase assertions still use course-column examples (programme / level, key topics, kōwhai level) that no caller produces any more — announcement kinds are section keys such as field_work/fieldwork. The assertions are still valid for the function, but they no longer anchor to a real consumer; re-anchoring one case to a section key would keep the test meaningful.

🔧 Fix: Restore section header casing without altering course labels
1 info still open:

  • ℹ️ scripts/build-cv-data.mjs:223 - /** One word, capitalised. The generator&#39;s only casing rule; every heading uses it. */ is no longer true after this branch: course-table column headers now bypass all casing (tableHeader's default displayKey is identity, used by entry() at line 248). The very next comment block draws the distinction correctly, so this one line is the only stale claim left in the neighbourhood — trim it to "every derived heading uses it" or drop the second clause.
✅ **Test** - passed

✅ No issues found.

  • node --test scripts/build-cv-data.test.mjs (45 tests, includes new verbatim tableHeader and \cvAppointmentsHeader title-case cases)
  • node --experimental-strip-types web/src/lib/cv.test.ts (fixture record now uses capitalised course keys; headingCase assertions retained)
  • node --experimental-strip-types web/src/lib/live-record.test.ts
  • LEDGERPRESS_RECORD_ROOT=src/lib/fixtures/record node --experimental-strip-types web/src/lib/announcements.test.ts (headingCase's remaining consumer)
  • node scripts/build-cv-data.mjs --check — committed cv/generated/cv-data.tex is fresh against content/cv.yaml
  • latexmk -xelatex -cd cv/cv.tex on the real record, page 1 rendered with pdftoppm to confirm the printed course table and section headers are unchanged
  • Manual verbatim proof: git archive HEAD into /tmp, rewrote course row keys to Programme / level / Key topics / points, ran node scripts/build-cv-data.mjs + latexmk -xelatex -cd cv/cv.tex, cropped the course table from the PDF
  • Manual website check: astro dev on that mixed-case copy, read &lt;th scope=&#34;col&#34;&gt; / data-label markup via curl and screenshotted /cv/ with "inspect sources" on
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@eduardstan
eduardstan merged commit 7294b10 into main Jul 30, 2026
3 checks passed
eduardstan added a commit that referenced this pull request Jul 31, 2026
)

* Use course-table column row keys verbatim without deriving casing

* no-mistakes(review): Restore section header casing without altering course labels

* no-mistakes(document): Align headingCase doc comments with verbatim column keys
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant