feat: add deep-merge utility for profile-based metadata overrides#166
Conversation
Replace the full-copy-per-profile approach with a deep-merge function that recursively merges sparse profile TOML files on top of a shared root metadata.toml. This lets users vary any field (personal.info, layout, inject, etc.) per language or target role without duplicating the entire configuration. - Add src/utils/merge.typ with recursive deep-merge function - Export deep-merge from src/lib.typ - Update template/cv.typ and letter.typ with profile-aware loading - Restore root metadata.toml and modules_<lang>/ directories - Remove profile_<lang>/ directories (replaced by profiles/ overrides) - Add example profile files (en, fr, zh) in template/profiles/ - Update docs: recipes, getting-started, migration guide, API reference Closes #142 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09d3dfb515
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
template/cv.typ
Outdated
| #let metadata = toml("profile_" + profile + "/metadata.toml") | ||
| #let cv-profile = sys.inputs.at("profile", default: metadata.at("profile", default: none)) | ||
| #let metadata = if cv-profile != none { | ||
| deep-merge(metadata, toml("./profiles/" + cv-profile + ".toml")) |
There was a problem hiding this comment.
Preserve de/it profile compatibility
The new profile loader now always reads ./profiles/<profile>.toml, but this commit removed profile_de/metadata.toml and profile_it/metadata.toml without adding profiles/de.toml or profiles/it.toml. As a result, existing commands like typst compile cv.typ --input profile=de (and profile=it) now fail with a missing-file error instead of building those variants, which is a regression from the previous profile workflow.
Useful? React with 👍 / 👎.
template/profiles/fr.toml
Outdated
| [personal.info.custom-2] | ||
| awesomeIcon = "car" | ||
| text = "Permis B" |
There was a problem hiding this comment.
Clear inherited link for French custom-2 entry
Because deep-merge recursively preserves unspecified nested keys, defining personal.info.custom-2 here without a link keeps the base metadata.toml link (https://aws.amazon.com/certification/). That makes the French Permis B label point to the AWS certification URL, which is incorrect output for this profile.
Useful? React with 👍 / 👎.
Restore profile_<lang>/ directory structure from PR #155 for content modules. Slim down each profile's metadata.toml to sparse overrides that are deep-merged with root metadata.toml. Remove the separate profiles/ directory — overrides now live inside profile_<lang>/. Update cv.typ and letter.typ to deep-merge root + profile metadata. Update all docs to reflect profile_<lang>/ paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d3cf6d94d6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #import "@preview/brilliant-cv:3.0.0": deep-merge | ||
|
|
||
| #let base = toml("./metadata.toml") | ||
| #let profile = toml("./profiles/fr.toml") |
There was a problem hiding this comment.
Fix deep-merge example to use a valid import and file path
The API generator now documents deep-merge, but this snippet imports it from @preview/brilliant-cv:3.0.0 and loads ./profiles/fr.toml; both are inconsistent with this change set (which introduces deep-merge now and uses profile_<name>/metadata.toml). A repo-wide file search only finds template/profile_fr/metadata.toml and no profiles/ directory, so users copying this example will get symbol-not-found or missing-file errors.
Useful? React with 👍 / 👎.
…ance bugs profile_fr/metadata.toml defined custom-2 with text="Permis B" but no link, causing deep-merge to inherit root's AWS certification URL. Rename numbered custom entries (custom-1, custom-2) to descriptive names (custom-degree, custom-cert, custom-car) across root and all profiles. Document deep-merge inheritance behavior and naming convention in recipes.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8fac5333eb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| [personal.info.custom-1] | ||
| awesomeIcon = "car" | ||
| text = "Permis B" |
There was a problem hiding this comment.
Avoid inherited links in migration profile example
Step 2 tells users to keep their existing v3 metadata as the new root file, and in that layout [personal.info.custom-1] commonly already contains a link; redefining custom-1 here without link causes deep-merge to retain the old URL, so the new Permis B label can become clickable to an unrelated inherited target. Use a new key (for example custom-car) or explicitly set the link value in this example to prevent wrong output for migrated projects.
Useful? React with 👍 / 👎.
Summary
deep-mergerecursive dictionary merge utility (src/utils/merge.typ) exported from the package, replacing the full-copy-per-profile approach with sparse overridesmetadata.tomlandmodules_<lang>/directories; profile TOML files inprofiles/only contain fields that differCloses #142
Test plan
just build— compiles without profile (backward compat)--input profile=fr— French profile overrides apply correctly--input profile=fr --input language=en— language override takes precedencejust docs-generate-api— API reference includesdeep-merge()--input profile=zh— blocked by pre-existing non-Latin font insertion bug (out of scope)🤖 Generated with Claude Code