Skip to content

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

Open
smur89 wants to merge 1 commit into
mainfrom
feat/issue-55-anonymous-mode-v2
Open

feat: add preferences.anonymous for blind-review mode#122
smur89 wants to merge 1 commit into
mainfrom
feat/issue-55-anonymous-mode-v2

Conversation

@smur89

@smur89 smur89 commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Closes #55. Supersedes #68 (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).

Summary

New preferences.anonymous: bool (default false). One toggle, same data dict, same compile command — no need to fork or maintain a stripped copy of basics.

When true:

  • Header drops basics.name, basics.image, and the contact bar entirely. Only basics.label (e.g. "Senior Software Engineer") survives — a role title is the only header field that conveys candidate fit without identity. Per-channel opt-out for contact info already exists via linkContactInfo, so anonymous mode suppresses the bar wholesale rather than re-implementing per-channel gating.
  • PDF metadata title and author collapse to the placeholder "Candidate" so file properties can't leak the name through an OS file-info panel or a search index. description (from basics.summary) and keywords (from skills[].keywords) stay populated — neither is identity-bearing in the same way and the issue spec only called out title/author.
  • Validation still runs: a malformed basics.image or linkContactInfo dict still panics under anonymous: true, so flipping the toggle later doesn't surface latent bugs.

Implementation notes

The contact-bar entry build (~75 lines) was extracted from _header into a module-level _build_contact_entries helper. Without that extraction the anonymity guard would be a pure-indentation wrap of the existing block, drowning the actual logic in diff noise. Net effect: _header keeps a simple two-line if not anonymous { ... contact bar ... }, and the entry-collection logic gets a clean home.

The has-image logic now reads let has-image = image-present and not anonymous — the validation branch (let image-present = ...) runs unconditionally so a bad basics.image value still panics, but the rendering branch is gated on the anonymity flag.

Fixture

tests/anonymous.typ (3 pages):

  1. anonymous: false, full basics — regression guard against perturbations to the canonical render.
  2. anonymous: true, no label — header collapses cleanly (no orphan vertical space between the suppressed name and the section grid).
  3. anonymous: true, full basics — canonical blind-review render. Rendered last so the document's set document(...) wins and pdfinfo examples/tests/anonymous.pdf reports Title: Candidate and Author: Candidate, confirming identity doesn't leak through file properties either.

Test plan

  • make test — all 38 fixtures + 4 examples compile
  • pdfinfo examples/tests/anonymous.pdf reports Title: Candidate and Author: Candidate
  • Page 3 PNG: only "Senior Software Engineer" in the header, no name, no photo, no contact bar
  • Page 2 PNG: header strip is empty, summary starts at the top of the page (no phantom vertical space)
  • Page 1 PNG: full canonical render unchanged (name + photo + contact bar + profiles)

Summary by CodeRabbit

  • New Features
    • Added anonymous mode preference to support blind-review CV submissions. When enabled, the header omits the candidate's name, photo, and contact information, whilst PDF metadata is anonymised to "Candidate" to prevent identity leakage through file properties.

Closes #55. New boolean preference (default false). When true:

- Header drops basics.name, basics.image, and the contact bar entirely.
  Only basics.label (e.g. "Senior Software Engineer") survives — a role
  title is the only header field that conveys candidate fit without
  identity. Per-channel opt-out for contact info already exists via
  linkContactInfo, so anonymous mode suppresses the bar wholesale rather
  than re-implementing per-channel gating.
- PDF metadata title and author collapse to the placeholder "Candidate"
  so file properties can't leak the name through an OS file-info panel
  or a search index. Description (basics.summary) and keywords
  (skills[].keywords) stay populated — neither is identity-bearing in
  the same way and the issue spec only called out title/author.
- Validation still runs in anonymous mode: a malformed basics.image or
  linkContactInfo dict still panics, so flipping the toggle later
  doesn't surface latent bugs.

The contact-bar entry build (~75 lines) was extracted from _header into
a module-level _build_contact_entries helper. Without that extraction
the anonymity guard would be a pure-indentation wrap of the existing
block, drowning the actual logic in diff noise.

Fixture (tests/anonymous.typ, 3 pages):
1. anonymous off, full basics — regression guard against perturbations
   to the canonical render.
2. anonymous on, no label — header collapses cleanly (no orphan
   spacing).
3. anonymous on, full basics — canonical blind-review render. Rendered
   last so the document's set document(...) wins and pdfinfo on the
   tracked PDF reports title/author = "Candidate".
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b62c71a-b509-4df0-b51a-6c864af10dc1

📥 Commits

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

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

📝 Walkthrough

Walkthrough

Adds a preferences.anonymous boolean flag (default false). When enabled, the header suppresses basics.name, basics.image, and the contact bar (retaining only basics.label), PDF metadata title and author collapse to "Candidate", and a new _build_contact_entries helper centralises contact-bar construction in internal/header.typ. Three test render scenarios and README documentation are also added.

Changes

Anonymous / blind-review mode

Layer / File(s) Summary
Preference declaration and validation
internal/layout.typ, lib.typ
Adds anonymous: false to _default_preferences with inline documentation, and registers the new field as a validated boolean inside alta().
Header: contact-entries helper and anonymous rendering
internal/header.typ
Introduces _build_contact_entries(basics, maps-provider) to produce ordered {channel, icon, value, url} entries for the contact bar. Adds an anonymous parameter to _header; gates the name block and entire contact bar behind if not anonymous, and suppresses portrait rendering when anonymous is true while continuing to validate basics.image unconditionally.
alta(): PDF metadata anonymisation and header passthrough
lib.typ
Derives doc-title and doc-author (collapsing to "Candidate" when preferences.anonymous is true) and updates set document(...) accordingly; passes anonymous: preferences.anonymous through to _header(...).
Test scenarios and README documentation
tests/anonymous.typ, README.md
Adds three page-isolated render cases (non-anonymous baseline, anonymous without basics.label, anonymous with full basics). Updates the README preferences table and PDF metadata rows to document the anonymous flag and its "Candidate" placeholder behaviour.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A toggle flicks, the name slips away,
No photo, no email — just "Candidate" today.
The label stands proud, the rest goes to sleep,
The PDF too has no secrets to keep.
Blind-review ready — one flag, one hop,
This rabbit approves it: clean code, full stop! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarises the main feature: adding a preferences.anonymous setting for blind-review mode.
Description check ✅ Passed The description comprehensively covers the summary, implementation details, fixture testing, and test plan, addressing all key aspects of the change.
Linked Issues check ✅ Passed All requirements from issue #55 are met: anonymous preference added with header redaction (name, photo, contact bar removed; label-only rendering), PDF metadata anonymisation implemented, and single toggle approach confirmed without separate dict maintenance.
Out of Scope Changes check ✅ Passed All changes align with the anonymous/blind-review feature scope: README documentation updated, internal header/layout modules enhanced, PDF metadata handling modified, and test fixture added—no unrelated modifications detected.
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-v2

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

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