Skip to content

feat: add cover-letter companion entrypoint#123

Open
smur89 wants to merge 2 commits into
mainfrom
feat/issue-53-cover-letter-v2
Open

feat: add cover-letter companion entrypoint#123
smur89 wants to merge 2 commits into
mainfrom
feat/issue-53-cover-letter-v2

Conversation

@smur89

@smur89 smur89 commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a cover-letter(cv, body, ...) public entrypoint that renders a single-page cover letter sharing the masthead, theme, and contact bar with alta(). Closes #53.

Same cv dict (only basics is consumed), the same labels / preferences shape, 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 the example_full demo).

API

#cover-letter(
  cv,
  body,
  recipient:   none,
  date:        auto,
  salutation:  none,
  closing:     auto,
  labels:      (:),
  preferences: (:),
)
  • cv (positional) — same dict as alta(). Only basics is 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's dateFormat + labels.months (so the cover-letter date localises with the rest of the document). none suppresses; string/content overrides.
  • salutation — no defensible default across languages and registers, so opt-in.
  • closing: auto — resolves to labels.closing ("Sincerely,"); none suppresses closing+signature; inline string/content overrides. Mirrors the date: auto / none sentinel pair.
  • labels / preferences — same shape as alta(). CV-only preferences keys (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 from alta() — same checks, same panic messages, no behavioural change. columnRatio stays inline in alta() because cover-letter is single-column.

New label

closing"Sincerely," in internal/labels-en.toml. Consumed only by cover-letter; ignored by alta().

Test plan

  • make test green — every example + every fixture (including tests/cover_letter.typ) compiles.
  • make test-pdfs regenerates examples/tests/cover_letter.pdf cleanly.
  • make test-template green — template/cv.typ starter still compiles.
  • Fixture exercises: date: auto path through _format_date, labels.closing override (Irish), explicit salutation, multi-line recipient.
  • Worked README example renders with the import alias cover-letter and // x-release-please-version marker so release-please tracks it.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added cover-letter generation feature that reuses CV data (basics section, preferences, and custom labels)
    • Support for auto-formatted dates, custom recipients, salutations, and closing signatures with configurable defaults ("Sincerely," by default)

Shane Murphy added 2 commits June 14, 2026 21:01
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.
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a public cover-letter() entrypoint to lib.typ that reuses the CV masthead, accent palette, and contact-bar from the same basics dict. Shared preference/label validation previously inlined in alta() is extracted into _validate_shared_preferences in validation.typ. A closing label default is added to labels-en.toml, with a test fixture and README documentation completing the feature.

Changes

Cover Letter Entrypoint

Layer / File(s) Summary
Shared preference validator
internal/validation.typ, lib.typ
Adds _validate_shared_preferences(preferences, labels) covering mapsProvider, boolean flags, maxRating, pageFooter, dateFormat, and labels.months. Updates lib.typ imports to consume the new helper.
alta() validation refactor and page-footer binding
lib.typ
Replaces the inlined validation block in alta() with a single _validate_shared_preferences call, reorders state updates, and introduces a local page-footer binding before resolved-footer computation.
closing label default
internal/labels-en.toml
Adds closing = "Sincerely," as the default cover-letter valediction label.
cover-letter() renderer and test
lib.typ, tests/cover_letter.typ
Adds the cover-letter() public entrypoint: merges/validates labels and preferences, configures document layout, renders masthead via _header, conditionally resolves date, recipient, salutation, and closing/signature. Test fixture exercises date: auto, labels.closing override, explicit salutation, and multi-line recipient.
README documentation
README.md
Adds feature bullet, labels.closing table entry, and the full Cover letter section with usage example and argument reference table.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • smur89/alta-typst#76: The new cover-letter entrypoint reuses the dateFormat + labels.months date-rendering path introduced by this earlier feature PR.
  • smur89/alta-typst#103: Both PRs touch the uppercaseName/lastModifiedFooter preference validation, this PR now routing both through _check_bool inside the shared validator.
  • smur89/alta-typst#112: Both PRs extend internal/labels-en.toml; this PR adds labels.closing alongside the English defaults established by that earlier refactor.

Poem

🐇 A letter to write, with style and flair,
The masthead is shared, the accent is there,
cover-letter() hops into view,
"Sincerely," it closes, perfectly true,
One basics dict rules them all — what a pair! ✉️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main change: adding a cover-letter companion entrypoint, which is the primary objective of the PR.
Description check ✅ Passed The PR description covers the API, implementation details, and test plan comprehensively, though the rendered output section is not explicitly included.
Linked Issues check ✅ Passed The PR successfully implements all requirements from #53: a cover-letter entrypoint reusing header/basics, shared preferences and labels, date/recipient/salutation/closing parameters, and signature rendering.
Out of Scope Changes check ✅ Passed All changes directly support the cover-letter feature: README documentation, new label, validation extraction, test fixture, and entrypoint implementation are all in scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-53-cover-letter-v2

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 200dc34 and b5d4e52.

⛔ Files ignored due to path filters (1)
  • examples/tests/cover_letter.pdf is excluded by !**/*.pdf
📒 Files selected for processing (5)
  • README.md
  • internal/labels-en.toml
  • internal/validation.typ
  • lib.typ
  • tests/cover_letter.typ

Comment thread tests/cover_letter.typ
Comment on lines +9 to +38
#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`.
],
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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.

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.

feat: cover-letter companion entrypoint

1 participant