feat: add cover-letter companion entrypoint#123
Conversation
Renders a single-column letter that shares the masthead, accent, and contact bar with `alta()`. Same `cv` dict (only `basics` is consumed — other top-level keys are ignored), same `labels` / `preferences` shape, so callers keep one data file and one set of theme overrides for both documents. Cross-entrypoint validation is now `_validate_shared_preferences` in `internal/validation.typ` (pure extraction from `alta()` with no behaviour change; `columnRatio` stays inline in `alta` because cover-letter is single-column). The `auto` date is routed through `_format_date`, so the user's `dateFormat` preference and `labels.months` translation apply to the cover-letter date too — set once, follows everywhere. New label key `closing` (default "Sincerely,") sources the default valediction; `closing: auto` (default) reads it, `none` suppresses the closing + signature block entirely, and a string / content overrides inline. Mirrors the `date: auto / none` sentinel pair. Closes #53.
Reviewer-pass clarification: the original "single-column only" note in the `preferences` row of the cover-letter table glossed over `pageFooter`, `lastModifiedFooter`, `groupCertificates`, and `maxRating`, which are also accepted-but-inert here. Also call out that `dateFormat` + `labels.months` still apply to `date: auto` so the substitution localises with the rest of the document. Also inline the `let today` temp binding in the auto-date path — single use, more readable as one expression.
📝 WalkthroughWalkthroughAdds a public ChangesCover Letter Entrypoint
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/cover_letter.typ`:
- Around line 9-38: Add a new fixture test case to the cover_letter.typ file
that exercises the explicit suppression of the closing by setting closing: none
in the labels parameter, in addition to the existing fixture that uses closing:
"Le meas,". This new test case should verify that when closing is set to none,
both the closing text and signature are properly omitted from the output,
ensuring the API contract for the closing: none suppression branch is locked in
the tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c8103c00-c07d-4e8d-9074-48129e1349bc
⛔ Files ignored due to path filters (1)
examples/tests/cover_letter.pdfis excluded by!**/*.pdf
📒 Files selected for processing (5)
README.mdinternal/labels-en.tomlinternal/validation.typlib.typtests/cover_letter.typ
| #cover-letter( | ||
| ( | ||
| basics: ( | ||
| name: "Oisín Mac Cárthaigh", | ||
| label: "Innealtóir Bogearraí", | ||
| email: "oisin@example.com", | ||
| phone: "+353 1 555 0100", | ||
| location: "Baile Átha Cliath", | ||
| ), | ||
| ), | ||
| // `auto` substitutes today's date at compile time. tests/*.typ | ||
| // outputs aren't byte-pinned in CI, so the floating value is fine. | ||
| date: auto, | ||
| recipient: [ | ||
| Forge Liffey \ | ||
| Cé Bhaile Átha Cliath \ | ||
| Baile Átha Cliath 2 | ||
| ], | ||
| salutation: [A chara,], | ||
| labels: ( | ||
| closing: "Le meas,", | ||
| ), | ||
| [ | ||
| Litir bheag thástála. Ní gá gur foirfe an leagan amach — is é an | ||
| aidhm ná an cosán a chuir trí gach craobh den fheidhm | ||
| `cover-letter` chun deimhniú go n-oibríonn an ceanntásc roinnte, | ||
| an dáta `auto`, an seoladh, an beannú, agus an chríoch faoi | ||
| `labels.closing`. | ||
| ], | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Add a fixture case for closing: none suppression.
The current fixture exercises closing: auto/override but not the explicit suppression branch. Adding one compile fixture for closing: none will lock the API contract that both closing and signature are omitted.
Suggested diff
`#cover-letter`(
(
basics: (
name: "Oisín Mac Cárthaigh",
@@
],
)
+
+#cover-letter(
+ (
+ basics: (
+ name: "Oisín Mac Cárthaigh",
+ label: "Innealtóir Bogearraí",
+ email: "oisin@example.com",
+ phone: "+353 1 555 0100",
+ location: "Baile Átha Cliath",
+ ),
+ ),
+ date: none,
+ closing: none,
+ [
+ Cás tástála don chosán `closing: none`.
+ ],
+)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/cover_letter.typ` around lines 9 - 38, Add a new fixture test case to
the cover_letter.typ file that exercises the explicit suppression of the closing
by setting closing: none in the labels parameter, in addition to the existing
fixture that uses closing: "Le meas,". This new test case should verify that
when closing is set to none, both the closing text and signature are properly
omitted from the output, ensuring the API contract for the closing: none
suppression branch is locked in the tests.
Summary
Adds a
cover-letter(cv, body, ...)public entrypoint that renders a single-page cover letter sharing the masthead, theme, and contact bar withalta(). Closes #53.Same
cvdict (onlybasicsis consumed), the samelabels/preferencesshape, and the same shared-validation path — so a caller keeps one data file and one set of theme overrides to drive both documents.Supersedes #86 (which predates the
internal/split,internal/labels-en.toml, and theexample_fulldemo).API
cv(positional) — same dict asalta(). Onlybasicsis consumed; other keys are ignored.body(positional) — letter body. Trailing-content sugar works:#cover-letter(cv)[Letter body…].recipient— optional content block.\line breaks for multi-line "Name / Company / Address" stacks.date: auto— substitutes today's date routed through the user'sdateFormat+labels.months(so the cover-letter date localises with the rest of the document).nonesuppresses; string/content overrides.salutation— no defensible default across languages and registers, so opt-in.closing: auto— resolves tolabels.closing("Sincerely,");nonesuppresses closing+signature; inline string/content overrides. Mirrors thedate: auto / nonesentinel pair.labels/preferences— same shape asalta(). CV-onlypreferenceskeys (columnRatio, column sections,pageFooter,lastModifiedFooter,groupCertificates,maxRating) are accepted but inert here.Refactor
Cross-entrypoint validation extracted to
internal/validation.typ#_validate_shared_preferences. Pure extraction fromalta()— same checks, same panic messages, no behavioural change.columnRatiostays inline inalta()because cover-letter is single-column.New label
closing→"Sincerely,"ininternal/labels-en.toml. Consumed only bycover-letter; ignored byalta().Test plan
make testgreen — every example + every fixture (includingtests/cover_letter.typ) compiles.make test-pdfsregeneratesexamples/tests/cover_letter.pdfcleanly.make test-templategreen —template/cv.typstarter still compiles.date: autopath through_format_date,labels.closingoverride (Irish), explicitsalutation, multi-linerecipient.cover-letterand// x-release-please-versionmarker so release-please tracks it.Summary by CodeRabbit
Release Notes