Skip to content

feat: add preferences.anonymous for blind-review mode#68

Closed
smur89 wants to merge 2 commits into
mainfrom
feat/issue-55-anonymous-mode
Closed

feat: add preferences.anonymous for blind-review mode#68
smur89 wants to merge 2 commits into
mainfrom
feat/issue-55-anonymous-mode

Conversation

@smur89

@smur89 smur89 commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

Adds preferences.anonymous: bool (default false). When true, the rendered header drops basics.name, basics.image, and the contact bar entirely; basics.label becomes the sole header line. PDF metadata title and author are swapped for the generic placeholder "Candidate" so the file itself cannot leak identity via document properties.

Closes #55

Rendered output

CI uploads the example-pdf artifact. A new tests/anonymous.typ fixture renders three pages exercising the toggle:

  1. Anonymous on with a fully-populated basics — name / photo / contact bar dropped, label remains.
  2. Anonymous on with no label — header collapses cleanly (summary + sections still render).
  3. Anonymous off (default, asserted) — every identifying field renders normally.

Test plan

  • typst compile --root . examples/example.typ examples/example.pdf succeeds
  • Each tests/*.typ fixture compiles
  • Added a fixture under tests/ (new tests/anonymous.typ)
  • Updated the README Preferences table with the new anonymous key

Notes for the reviewer

  • Single toggle, same data dict, same compile command — matches the issue's "single source of truth" requirement.
  • The contact bar is suppressed wholesale rather than channel-by-channel: every channel (email, phone, location, profiles) carries identifying signal, and per-channel opt-out is already available via linkContactInfo for non-blind cases.
  • basics.image validation still runs when anonymous: true so a malformed value cannot slip through unnoticed via the flag.
  • set document(title:, author:) placeholder is "Candidate" — keeps a friendly value over an empty string (which some PDF readers render as "Untitled").

JSON Resume alignment

NOT IN SPECpreferences.anonymous is a rendering/template preference for blind-review use, not part of the JSON Resume data schema (https://jsonresume.org/schema/). The schema fields it suppresses (basics.name, contact info, photo) remain spec-compliant in the data; only the rendered output is redacted.

Summary by CodeRabbit

New Features

  • Added anonymous/blind-review mode via preferences.anonymous (default off). When enabled, the resume header suppresses identifying details (name/photo and the contact bar, while still showing any label). PDF document metadata is also redacted by using generic placeholders and removing identity-revealing fields.

Documentation

  • Documented the new preferences.anonymous option and its effects on header rendering and PDF metadata.

Tests

  • Added coverage to validate anonymous mode on, off, and with missing label.

@smur89 smur89 force-pushed the feat/issue-55-anonymous-mode branch 8 times, most recently from a0f9abf to 6be4fee Compare June 14, 2026 11:00
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds preferences.anonymous boolean (default false) to the AltaCV Typst template. When enabled, _header() suppresses the name block, removes the contact bar, and forces the portrait off. alta() validates the flag and branches set document(...) to use generic "Candidate" placeholders, omitting keywords and description. A new test file and a README entry document the behavior.

Changes

Anonymous / blind-review mode

Layer / File(s) Summary
Preference default, validation, and PDF metadata redaction
lib.typ
Registers anonymous: false in _default_preferences, enforces a boolean type-check in alta(), and branches set document(...) to emit generic "Candidate" placeholders (suppressing name, keywords, and description) when preferences.anonymous is true.
_header() anonymous rendering logic
lib.typ
Adds anonymous parameter to _header(), conditionally omits the name block, gates the entire contact-bar build behind the flag, forces has-image to false when anonymous even if basics.image is set, and passes the flag from alta() into _header().
Test coverage and documentation
tests/anonymous.typ, README.md
New test file renders three pages: full basics anonymous, anonymous without label, and the non-anonymous baseline. README preferences table gains the preferences.anonymous row.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A name is a secret, a photo a clue,
So I've hidden them both with a toggle or two.
The contact bar vanished, the metadata bare—
Just "Candidate" sits in the PDF there.
Blind review, fair review, my work hops right through! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add preferences.anonymous for blind-review mode' clearly and concisely describes the main change: adding a new feature for anonymous/blind-review mode.
Description check ✅ Passed The description covers all required sections: a clear summary of changes with issue reference, rendered output with test fixture details, completed test plan checklist, and reviewer notes explaining design trade-offs.
Linked Issues check ✅ Passed All requirements from issue #55 are met: preferences.anonymous boolean toggle with default false is implemented, header suppresses name/photo/contact when enabled, label renders alone, PDF metadata author is replaced with placeholder, and a single data dict with one compile command is maintained.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to the linked issue: README update documents the new preference, lib.typ implements the toggle with validation and metadata redaction, and tests/anonymous.typ provides comprehensive test coverage.
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-55-anonymous-mode

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib.typ (1)

1264-1267: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Anonymous mode still leaks identity when pageFooter is "auto".

Line 1266 always passes cv.basics.name into _auto_page_footer(...), so multi-page output can expose the real name even with preferences.anonymous: true.

Proposed fix
-  let resolved-footer = if page-footer != none {
+  let footer-name = if preferences.anonymous { "Candidate" } else { cv.basics.name }
+  let resolved-footer = if page-footer != none {
     if page-footer == "auto" {
-      _auto_page_footer(cv.basics.name)
+      _auto_page_footer(footer-name)
     } else {
       page-footer
     }
🤖 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 `@lib.typ` around lines 1264 - 1267, The _auto_page_footer() function on line
1266 always receives the actual name from cv.basics.name, which exposes the
user's identity even when anonymous mode is enabled via preferences.anonymous.
Check if anonymous mode is active before calling _auto_page_footer(), and if it
is, pass an empty string or placeholder value instead of cv.basics.name to
prevent identity leakage in the footer.
🤖 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 `@lib.typ`:
- Around line 1245-1246: The anonymous mode document title on line 1245 is
inconsistent with the documented placeholder value. The doc-author is correctly
set to "Candidate" as documented, but doc-title uses "Candidate --- CV" instead
of just "Candidate". Update the doc-title assignment to use "Candidate" as the
anonymous title value (without the " --- CV" suffix) to align with the
documented placeholder and maintain consistency with the doc-author value on
line 1246.

In `@tests/anonymous.typ`:
- Around line 18-39: The test fixture in tests/anonymous.typ is incomplete at
two locations and does not validate all advertised anonymous-mode behaviors. At
the first location (lines 18-39) with the cv object containing basics and work,
add a basics.image field to properly exercise portrait suppression during
testing. At the second location (lines 45-52), add both a summary field to the
basics section and include a sections array with data so the fixture can
properly validate that summary and sections content still render correctly in
anonymous mode, matching what the test comments assert.

---

Outside diff comments:
In `@lib.typ`:
- Around line 1264-1267: The _auto_page_footer() function on line 1266 always
receives the actual name from cv.basics.name, which exposes the user's identity
even when anonymous mode is enabled via preferences.anonymous. Check if
anonymous mode is active before calling _auto_page_footer(), and if it is, pass
an empty string or placeholder value instead of cv.basics.name to prevent
identity leakage in the footer.
🪄 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: 2301e5af-ac9f-4e20-b9ee-1ce410ff71c0

📥 Commits

Reviewing files that changed from the base of the PR and between bb65c26 and 6be4fee.

⛔ Files ignored due to path filters (1)
  • examples/tests/anonymous.pdf is excluded by !**/*.pdf
📒 Files selected for processing (3)
  • README.md
  • lib.typ
  • tests/anonymous.typ

Comment thread lib.typ
Comment on lines +1245 to +1246
let doc-title = if preferences.anonymous { "Candidate --- CV" } else { cv.basics.name + " --- CV" }
let doc-author = if preferences.anonymous { "Candidate" } else { cv.basics.name }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Anonymous metadata title value is inconsistent with the stated "Candidate" placeholder.

Line 1245 sets title to "Candidate --- CV" while the feature docs describe "Candidate" as the placeholder. Please align implementation and documentation to one contract.

🤖 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 `@lib.typ` around lines 1245 - 1246, The anonymous mode document title on line
1245 is inconsistent with the documented placeholder value. The doc-author is
correctly set to "Candidate" as documented, but doc-title uses "Candidate ---
CV" instead of just "Candidate". Update the doc-title assignment to use
"Candidate" as the anonymous title value (without the " --- CV" suffix) to align
with the documented placeholder and maintain consistency with the doc-author
value on line 1246.

Comment thread tests/anonymous.typ
Comment on lines +18 to +39
#let cv = (
basics: (
name: "Jane Doe",
label: "Senior Software Engineer",
summary: [Backend engineer with eight years' experience.],
email: "jane@example.com",
phone: "+353 1 555 0100",
location: "Dublin, Ireland",
url: "https://janedoe.dev",
profiles: (
(network: "GitHub", username: "janedoe", url: "https://github.com/janedoe"),
),
),
work: (
(
name: "Acme Corp",
position: "Senior Software Engineer",
startDate: "Jan 2022",
highlights: ([Led the platform migration.],),
),
),
)

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

This fixture misses two advertised anonymous-mode behaviors.

Case 1 doesn’t provide basics.image, so portrait suppression isn’t exercised. Case 2 claims summary/sections still render, but includes neither. Please add those inputs so the fixture validates what its comments assert.

Suggested fixture adjustments
 `#let` cv = (
   basics: (
     name: "Jane Doe",
     label: "Senior Software Engineer",
+    image: read("../examples/avatar-placeholder.svg", encoding: none),
     summary: [Backend engineer with eight years' experience.],
@@
 `#alta`(
   (basics: (
     name: "Jane Doe",
+    summary: [Anonymous summary should still render.],
     email: "jane@example.com",
     phone: "+353 1 555 0100",
-  )),
+  ),
+  work: (
+    (
+      name: "Acme Corp",
+      position: "Engineer",
+      startDate: "2024",
+      highlights: ([Section rendering still works.],),
+    ),
+  )),
   preferences: (anonymous: true),
 )

Also applies to: 45-52

🤖 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/anonymous.typ` around lines 18 - 39, The test fixture in
tests/anonymous.typ is incomplete at two locations and does not validate all
advertised anonymous-mode behaviors. At the first location (lines 18-39) with
the cv object containing basics and work, add a basics.image field to properly
exercise portrait suppression during testing. At the second location (lines
45-52), add both a summary field to the basics section and include a sections
array with data so the fixture can properly validate that summary and sections
content still render correctly in anonymous mode, matching what the test
comments assert.

@smur89 smur89 force-pushed the feat/issue-55-anonymous-mode branch from 6be4fee to 1d1459e Compare June 14, 2026 11:08
Shane Murphy added 2 commits June 14, 2026 15:14
Enables a single-toggle blind-review render: the header drops
basics.name, basics.image, and the contact bar; basics.label (when
present) becomes the sole header line. PDF metadata title and
author are swapped for the placeholder "Candidate" so the file
itself cannot unmask the candidate via its document properties.
All other sections render normally — same data dict, same compile
command, single toggle.

Closes #55
@smur89 smur89 force-pushed the feat/issue-55-anonymous-mode branch from 2c10506 to adcc661 Compare June 14, 2026 13:17

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib.typ (1)

1523-1526: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Anonymous mode still leaks identity when pageFooter is "auto".

Line 1525 always passes cv.basics.name into _auto_page_footer(...). With preferences.anonymous: true, that reintroduces identifying data in rendered output.

🔧 Suggested fix
   let resolved-footer = if page-footer != none {
     if page-footer == "auto" {
-      _auto_page_footer(cv.basics.name)
+      _auto_page_footer(if preferences.anonymous { "Candidate" } else { cv.basics.name })
     } else {
       page-footer
     }
🤖 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 `@lib.typ` around lines 1523 - 1526, The _auto_page_footer function is being
called with cv.basics.name unconditionally when page-footer equals "auto", which
leaks the user's identity even when anonymous mode is enabled through
preferences.anonymous. Modify the condition where _auto_page_footer is called to
check the anonymous preference setting, and pass either cv.basics.name or none
(or an appropriate anonymous placeholder) based on whether preferences.anonymous
is true or false.
♻️ Duplicate comments (2)
lib.typ (1)

1504-1510: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align anonymous title metadata with the documented "Candidate" placeholder.

Line 1504 sets "Candidate --- CV" while the documented contract says anonymous title/author use "Candidate".

🔧 Suggested fix
-  let doc-title = if preferences.anonymous { "Candidate --- CV" } else { cv.basics.name + " --- CV" }
+  let doc-title = if preferences.anonymous { "Candidate" } else { cv.basics.name + " --- CV" }
🤖 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 `@lib.typ` around lines 1504 - 1510, The doc-title variable in the anonymous
preference block is set to "Candidate --- CV" but the documented contract
specifies that the anonymous title should be just "Candidate" to match the
author metadata. Update the doc-title assignment in the conditional expression
(where preferences.anonymous is true) to use "Candidate" instead of "Candidate
--- CV" to align with the documented behavior and maintain consistency with the
anonymous author value.
tests/anonymous.typ (1)

9-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fixture comments overstate coverage for anonymous mode.

The current data does not exercise two behaviors it claims:

  • Case 1 (Lines 9-10/18-39) never sets basics.image, so portrait suppression isn’t tested.
  • Case 2 (Lines 11-12/45-52) includes neither basics.summary nor any section data, so “summary + sections still render” is unverified.
🔧 Suggested fixture update
 `#let` cv = (
   basics: (
     name: "Jane Doe",
     label: "Senior Software Engineer",
+    image: read("../examples/avatar-placeholder.svg", encoding: none),
     summary: [Backend engineer with eight years' experience.],
@@
 `#alta`(
   (basics: (
     name: "Jane Doe",
+    summary: [Anonymous summary should still render.],
     email: "jane@example.com",
     phone: "+353 1 555 0100",
-  )),
+  ),
+  work: (
+    (
+      name: "Acme Corp",
+      position: "Engineer",
+      startDate: "2024",
+      highlights: ([Section rendering still works.],),
+    ),
+  )),
   preferences: (anonymous: true),
 )

Also applies to: 18-39, 45-52

🤖 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/anonymous.typ` around lines 9 - 14, The test fixture comments in the
anonymous mode test cases describe behaviors that aren't actually exercised by
the fixture data. Update Case 1 (the first anonymous-on test with
fully-populated basics around lines 18-39) to include a basics.image field so
that portrait suppression is properly tested as claimed in the comment. Update
Case 2 (the anonymous-on test with no label around lines 45-52) to include both
basics.summary content and at least one section with data so that the comment's
claim about "summary + sections still render" is actually verified by the
fixture rather than just asserted without evidence.
🤖 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.

Outside diff comments:
In `@lib.typ`:
- Around line 1523-1526: The _auto_page_footer function is being called with
cv.basics.name unconditionally when page-footer equals "auto", which leaks the
user's identity even when anonymous mode is enabled through
preferences.anonymous. Modify the condition where _auto_page_footer is called to
check the anonymous preference setting, and pass either cv.basics.name or none
(or an appropriate anonymous placeholder) based on whether preferences.anonymous
is true or false.

---

Duplicate comments:
In `@lib.typ`:
- Around line 1504-1510: The doc-title variable in the anonymous preference
block is set to "Candidate --- CV" but the documented contract specifies that
the anonymous title should be just "Candidate" to match the author metadata.
Update the doc-title assignment in the conditional expression (where
preferences.anonymous is true) to use "Candidate" instead of "Candidate --- CV"
to align with the documented behavior and maintain consistency with the
anonymous author value.

In `@tests/anonymous.typ`:
- Around line 9-14: The test fixture comments in the anonymous mode test cases
describe behaviors that aren't actually exercised by the fixture data. Update
Case 1 (the first anonymous-on test with fully-populated basics around lines
18-39) to include a basics.image field so that portrait suppression is properly
tested as claimed in the comment. Update Case 2 (the anonymous-on test with no
label around lines 45-52) to include both basics.summary content and at least
one section with data so that the comment's claim about "summary + sections
still render" is actually verified by the fixture rather than just asserted
without evidence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aa358486-f646-4558-a310-beb393250e5b

📥 Commits

Reviewing files that changed from the base of the PR and between 6be4fee and adcc661.

⛔ Files ignored due to path filters (1)
  • examples/tests/anonymous.pdf is excluded by !**/*.pdf
📒 Files selected for processing (3)
  • README.md
  • lib.typ
  • tests/anonymous.typ

@smur89

smur89 commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #122 — rewritten from scratch on top of the post-#68 refactorings (lib.typ split into internal/, labels moved to internal/labels-en.toml, example_full.typ demo). Same design contract (anonymous drops name/photo/contact bar wholesale; PDF title/author collapse to "Candidate"; validation still runs in anonymous mode; 3-page fixture). Leaving this one open for reference rather than closing — defer to maintainer.

@smur89 smur89 closed this Jun 14, 2026
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: anonymous / blind-review mode

1 participant