Skip to content

RFC: profile-based metadata system#155

Draft
yunanwg wants to merge 9 commits intomainfrom
feat/profile-based-metadata-rfc
Draft

RFC: profile-based metadata system#155
yunanwg wants to merge 9 commits intomainfrom
feat/profile-based-metadata-rfc

Conversation

@yunanwg
Copy link
Owner

@yunanwg yunanwg commented Mar 2, 2026

Why this change?

In v3, all CV variants share a single metadata.toml. The [lang.<code>] sections only allow varying header_quote, cv_footer, and letter_footer per language — but fields like [personal.info], [layout], and [inject] are global and cannot differ between languages or target roles (#142).

This makes it impossible to, for example, use a different phone number for your French CV, tailor keywords per target industry, or adjust layout for different paper sizes by region.

The new profile-based architecture solves this by making each variant a self-contained folder (profile_en/, profile_fr/, profile_swe/) with its own complete metadata.toml and module files. Users can now vary everything per profile — not just language-specific text.

Summary

  • Introduce a profile system where each profile folder (profile_<name>/) contains its own metadata.toml and module files
  • Replaces the --input language=xx pattern with --input profile=xx in the template
  • Package code (src/) is backward compatible — accepts both old and new metadata formats

Backward compatibility

Package (src/) — fully backward compatible:

  • Old metadata.toml with [lang.en] sections → works (fallback path for 5 fields: header_quote, cv_footer, letter_footer, non_latin_name, non_latin_font)
  • New flat profile metadata.toml → works (top-level path)
  • All legacy fallback code is marked with comments for future cleanup

Template (template/) — breaking change:

  • modules_<lang>/ renamed to profile_<name>/
  • --input language=xx replaced by --input profile=xx
  • Top-level metadata.toml replaced by per-profile metadata.toml
  • Existing users who update the template need to migrate their folder structure

Upgrade paths for existing users:

  1. Zero-effort: just bump the version number — old structure continues to work
  2. Full migration: see the Migration Guide for step-by-step instructions

Proposed design

User-facing (template/)

// template/cv.typ
#let profile = sys.inputs.at("profile", default: "en")
#let metadata = toml("profile_" + profile + "/metadata.toml")

#let import-modules(modules) = {
  for module in modules {
    include { "profile_" + profile + "/" + module + ".typ" }
  }
}

#show: cv.with(metadata, profile-photo: image("assets/avatar.jpg"))
#import-modules(("professional", "education", "skills"))

Profile metadata.toml (flat, no [lang] nesting)

language = "en"                        # Rendering only: fonts, non-Latin, date width
header_quote = "Experienced Data Analyst..."
cv_footer = "Curriculum vitae"
letter_footer = "Cover letter"

# For non-Latin profiles:
# non_latin_name = "王道尔"
# non_latin_font = "Heiti SC"

[layout]
awesome_color = "skyblue"
# ...

[personal]
first_name = "John"
last_name = "Doe"
[personal.info]
location = "San Francisco, CA"
email = "john@example.com"

What stays unchanged

  • metadata.language — still used by _is-non-latin(), _default-date-width()
  • metadata.personal.*, metadata.layout.*, metadata.inject.* — same structure
  • All CV component functions (cv-section, cv-entry, etc.) — untouched

Replace modules_<lang>/ with profile_<name>/ folders, each containing
its own complete metadata.toml. This enables per-profile customization
of personal info, header quotes, keywords, and all other metadata.

- Rename modules_* folders to profile_* in template
- Add per-profile metadata.toml with flat structure (no [lang] nesting)
- Add fallback logic in src/ for backward compatibility with old format
- Replace --input language=xx with --input profile=xx in template
- Remove top-level metadata.toml (each profile is self-contained)

Closes #142

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pull-request-size pull-request-size bot added size/L and removed size/XS labels Mar 2, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a profile-based template layout where each profile folder contains its own metadata.toml and module .typ files, and updates the package code to resolve certain metadata fields from either the new flat schema or the legacy [lang.*] schema.

Changes:

  • Update template/cv.typ and template/letter.typ to load profile_<profile>/metadata.toml and include modules from the selected profile folder.
  • Add new per-profile template module files and per-profile metadata.toml for en, fr, de, it, zh.
  • Implement fallback resolution in src/ for header_quote, cv_footer, letter_footer, non_latin_name, non_latin_font (top-level first, legacy paths second).

Reviewed changes

Copilot reviewed 10 out of 40 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
template/cv.typ Switches template entrypoint to profile-based metadata/modules loading.
template/letter.typ Switches letter template entrypoint to profile-based metadata loading.
template/profile_en/metadata.toml Moves footer/quote fields to top-level for the English profile.
template/profile_en/education.typ Adds English profile education module.
template/profile_en/professional.typ Adds English profile professional module.
template/profile_en/projects.typ Adds English profile projects module.
template/profile_en/publications.typ Adds English profile publications module.
template/profile_en/skills.typ Adds English profile skills module.
template/profile_en/certificates.typ Adds English profile certificates module.
template/profile_fr/metadata.toml Adds French profile metadata.
template/profile_fr/education.typ Adds French profile education module.
template/profile_fr/professional.typ Adds French profile professional module.
template/profile_fr/projects.typ Adds French profile projects module.
template/profile_fr/publications.typ Adds French profile publications module.
template/profile_fr/skills.typ Adds French profile skills module.
template/profile_fr/certificates.typ Adds French profile certificates module.
template/profile_de/metadata.toml Adds German profile metadata.
template/profile_de/education.typ Adds German profile education module (currently includes local metadata loading).
template/profile_de/professional.typ Adds German profile professional module (currently includes local metadata loading).
template/profile_de/projects.typ Adds German profile projects module (currently includes local metadata loading).
template/profile_de/publications.typ Adds German profile publications module (currently includes local metadata loading).
template/profile_de/skills.typ Adds German profile skills module (currently includes local metadata loading).
template/profile_de/certificates.typ Adds German profile certificates module (currently includes local metadata loading).
template/profile_it/metadata.toml Adds Italian profile metadata.
template/profile_it/education.typ Adds Italian profile education module.
template/profile_it/professional.typ Adds Italian profile professional module.
template/profile_it/projects.typ Adds Italian profile projects module.
template/profile_it/publications.typ Adds Italian profile publications module.
template/profile_it/skills.typ Adds Italian profile skills module.
template/profile_it/certificates.typ Adds Italian profile certificates module.
template/profile_zh/metadata.toml Adds Chinese profile metadata (including non-Latin name/font fields).
template/profile_zh/education.typ Adds Chinese profile education module.
template/profile_zh/professional.typ Adds Chinese profile professional module.
template/profile_zh/projects.typ Adds Chinese profile projects module.
template/profile_zh/publications.typ Adds Chinese profile publications module.
template/profile_zh/skills.typ Adds Chinese profile skills module.
template/profile_zh/certificates.typ Adds Chinese profile certificates module.
src/lib.typ Adds fallback logic for non-Latin font resolution in both CV + letter.
src/cv.typ Adds fallback logic for header_quote, cv_footer, non_latin_name.
src/letter.typ Adds fallback logic for letter_footer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix camelCase nonLatinFont -> kebab-case non-latin-font in src/lib.typ
- Add backward compat comments to all legacy fallback code in src/
- Add inline documentation comments to template/cv.typ and letter.typ

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yunanwg yunanwg changed the title feat: profile-based metadata system (RFC) feat: profile-based metadata system Mar 2, 2026
@spark-by-yunan spark-by-yunan bot added this to the v4.0.0 milestone Mar 15, 2026
yunanwg and others added 2 commits March 15, 2026 17:42
Resolve conflicts in template files, keeping profile-based metadata
approach while incorporating main's updates (v3.2.0 version bump,
new metadata comments for researchgate/extraInfo/linebreak, fontawesome
link in custom icon comment).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… migration guide

Profile metadata files (de/fr/it/zh) contained inject_ai_prompt and
inject_keywords which trigger panics in src/cv.typ. Removed to match
profile_en and allow compilation.

Added "Migration from v3" section to docs with motivation, zero-effort
upgrade path, and step-by-step migration instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yunanwg yunanwg changed the title feat: profile-based metadata system RFC: profile-based metadata system Mar 15, 2026
@yunanwg
Copy link
Owner Author

yunanwg commented Mar 15, 2026

@codex Review this PR.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc5e4e393b

ℹ️ 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
//
// Override via CLI: typst compile cv.typ --input profile=fr
#let profile = sys.inputs.at("profile", default: "en")
#let metadata = toml("profile_" + profile + "/metadata.toml")

Choose a reason for hiding this comment

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

P1 Badge Use a package version that supports profile metadata

template/cv.typ now reads per-profile metadata.toml files where values like header_quote and cv_footer are top-level, but the template is still pinned to @preview/brilliant-cv:3.2.0, which expects these fields under metadata.lang.<code>. In this state, users following the new profile layout will hit runtime errors because metadata.lang is absent; template/letter.typ has the same version pin, so both document entrypoints are affected until the import is updated to the release that includes this commit's metadata fallback changes.

Useful? React with 👍 / 👎.

yunanwg added a commit that referenced this pull request Mar 17, 2026
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>
yunanwg and others added 3 commits March 17, 2026 13:33
* feat: add deep-merge utility for profile-based metadata overrides

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>

* fix: use profile_<lang>/ structure with sparse metadata overrides

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>

* fix: use descriptive custom entry names to prevent deep-merge inheritance 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>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflict in src/lib.typ: keep backward-compat fallback for
non_latin_font (profile-based) with calc.min safe insertion (from main).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-Latin fonts

Replace fonts.insert(calc.min(2, fonts.len()), ...) with fonts.push(...).
The non-Latin font is a fallback appended to the end of the font list,
so push expresses the intent directly without a fragile index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants