feat: add optional profile photo support for DACH CV market#291
feat: add optional profile photo support for DACH CV market#291andruwa13 wants to merge 25 commits intosantifer:mainfrom
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds optional resume photo support across config example, documentation, template, PDF generation, and a temporary CV generator: introduces Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as Generator/CLI
participant Config as Config Loader
participant FS as File System
participant Encoder as Base64 Encoder
participant Renderer as Template Renderer
participant PDF as PDF Generator
CLI->>Config: load `config/profile.yml`
Config-->>CLI: return config (photo path?)
alt photo present and valid
CLI->>FS: resolve & read photo (project-relative, resolve symlinks, ensure inside project)
FS-->>CLI: binary image (if ≤2MiB and allowed ext)
CLI->>Encoder: base64-encode → data URI
Encoder-->>Renderer: data URI
Renderer->>Renderer: replace {{PHOTO_BLOCK}} with <img class="cv-photo" src="data:...">
else photo absent/invalid/oversize
Renderer->>Renderer: replace {{PHOTO_BLOCK}} with ""
CLI->>CLI: console.warn (reason)
end
CLI->>Renderer: continue template processing (font-path rewriting, ATS normalization)
Renderer-->>PDF: rendered HTML
PDF-->>CLI: output PDF
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Welcome to career-ops, @andruwa13! Thanks for your first PR.
A few things to know:
- Tests will run automatically — check the status below
- Make sure you've linked a related issue (required for features)
- Read CONTRIBUTING.md if you haven't
We'll review your PR soon. Join our Discord if you have questions.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@modes/pdf.md`:
- Around line 186-190: The fenced HTML code block for the photo example around
the replacement for {{PHOTO_BLOCK}} is missing blank lines before and after
which violates MD031; update the markdown in the "Replace `{{PHOTO_BLOCK}}` in
the template with:" section so there is an empty line directly before the
opening ```html fence and an empty line directly after the closing ``` fence
(i.e., ensure the fenced block for the <img class="cv-photo"...> example is
separated by blank lines from surrounding list items and the subsequent step
about `photo:` being absent).
In `@templates/cv-template.html`:
- Around line 115-136: The header CSS is unused because the header markup
doesn't include the new classes or the photo placeholder; update the header
template to wrap the existing title/contact markup in a container with class
"header-inner", add a photo container element with class "cv-photo" that renders
the template variable "{{PHOTO_BLOCK}}" (or falls back to nothing when empty),
and move the textual parts into an element with class "header-text" so the
styles (.header-inner, .header-text, .cv-photo, and {{PHOTO_BLOCK}}) can apply
and allow photo injection to work.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: bd09c424-5e3b-482f-9058-6fa710a6ffb0
📒 Files selected for processing (3)
config/profile.example.ymlmodes/pdf.mdtemplates/cv-template.html
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@modes/pdf.md`:
- Around line 180-192: The Template HTML placeholder matrix is missing the new
PHOTO_BLOCK introduced for DACH profile photos; update the canonical placeholder
table (the Template HTML section) to add a row documenting {{PHOTO_BLOCK}} with
its semantics: when config/profile.yml has a photo: path it becomes an <img>
data URI (data:image/jpeg|png;base64,...) and when absent it is replaced with an
empty string. Ensure the table entry references {{PHOTO_BLOCK}} and mentions
config/profile.yml as the source and the data URI / empty-string behavior so
documentation and implementation remain in sync.
- Around line 182-192: Update the photo embedding logic that processes
config/profile.yml's photo field and the template placeholder {{PHOTO_BLOCK}} to
explicitly handle unsupported formats: detect the image MIME type (based on file
extension or inspecting bytes), allow only image/jpeg and image/png for base64
data URI embedding, and for any other format (e.g., HEIC, WebP, SVG) log a
deterministic warning and replace {{PHOTO_BLOCK}} with an empty string "";
ensure the code path that builds the data URI (the JPEG/PNG branch) and the
fallback branch are clearly named/located around the photo-processing routine
that populates {{PHOTO_BLOCK}} so reviewers can find the change easily.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c85710d8-1781-4058-9ab3-256519eec6cc
📒 Files selected for processing (2)
modes/pdf.mdtemplates/cv-template.html
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@modes/pdf.md`:
- Around line 183-197: The docs describe substituting {{PHOTO_BLOCK}} from
config/profile.yml.photo but no code implements it: update the HTML generation
pipeline (e.g., in the module that reads templates before invoking
generate-pdf.mjs or inside generate-pdf.mjs) to read config/profile.yml,
validate the photo file extension (allow .jpg, .jpeg, .png), and if valid read
and base64-encode the image into a data URI (image/jpeg or image/png); then
replace the {{PHOTO_BLOCK}} placeholder in templates/cv-template.html with <img
class="cv-photo" src="data:..."> before PDF conversion; if the extension is
unsupported or the file is missing/empty log a warning and replace
{{PHOTO_BLOCK}} with an empty string.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8853eed6-0868-469b-bec3-55fa8e67ba0e
📒 Files selected for processing (1)
modes/pdf.md
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@generate-pdf.mjs`:
- Around line 40-44: The current resolve(projectRoot, photoPath) allows absolute
or ../ traversal to escape the repo; before reading the file (using
existsSync/fullPath), validate the canonical path is contained in projectRoot by
resolving and then checking that path.relative(projectRoot, fullPath) does not
start with '..' and that fullPath is not absolute outside projectRoot (also
guard against null bytes); if the check fails, log a warning and return the
empty PHOTO_BLOCK replacement as you do now. Use the same variables
(projectRoot, photoPath, fullPath, existsSync) and perform this containment
check immediately after computing fullPath and before accessing the filesystem.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4052f488-c1e2-4fc0-b81a-55974aefcb05
📒 Files selected for processing (1)
generate-pdf.mjs
German-speaking market (DACH) expects a Bewerbungsfoto in the top-right
corner of the CV. This change adds opt-in photo support:
- templates/cv-template.html: wrap header in .header-inner flex container,
add .cv-photo styles, inject {{PHOTO_BLOCK}} placeholder on the right
- config/profile.example.yml: add commented-out 'photo:' field with DACH
context and instructions
- modes/pdf.md: add instructions for Claude to embed photo as base64 data URI
Behavior:
- photo field absent/empty → {{PHOTO_BLOCK}} replaced with '' (no change)
- photo field set → image embedded as base64, displayed top-right (90×115px)
References:
- https://www.lebenslauf.de/ratgeber/lebenslauf/bewerbungsfoto/
- https://karrierebibel.de/lebenslauf-ohne-foto/
- https://www.experteer.de/magazin/gehoert-ein-foto-auf-den-lebenslauf/
Reads photo from config/profile.yml, validates format (.jpg/.jpeg/.png),
base64-encodes and replaces {{PHOTO_BLOCK}} in the HTML template.
Unsupported formats produce a warning and clean fallback.
Reject absolute paths and ../ traversal sequences before reading photo from disk. Prevents arbitrary local file disclosure via config/profile.yml.
German-speaking market (DACH) expects a Bewerbungsfoto in the top-right
corner of the CV. This change adds opt-in photo support:
- templates/cv-template.html: wrap header in .header-inner flex container,
add .cv-photo styles, inject {{PHOTO_BLOCK}} placeholder on the right
- config/profile.example.yml: add commented-out 'photo:' field with DACH
context and instructions
- modes/pdf.md: add instructions for Claude to embed photo as base64 data URI
Behavior:
- photo field absent/empty → {{PHOTO_BLOCK}} replaced with '' (no change)
- photo field set → image embedded as base64, displayed top-right (90×115px)
References:
- https://www.lebenslauf.de/ratgeber/lebenslauf/bewerbungsfoto/
- https://karrierebibel.de/lebenslauf-ohne-foto/
- https://www.experteer.de/magazin/gehoert-ein-foto-auf-den-lebenslauf/
Reads photo from config/profile.yml, validates format (.jpg/.jpeg/.png),
base64-encodes and replaces {{PHOTO_BLOCK}} in the HTML template.
Unsupported formats produce a warning and clean fallback.
2f90166 to
11e09ac
Compare
|
@coderabbitai resume |
✅ Actions performedReviews resumed. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@generate-pdf.mjs`:
- Around line 47-52: The containment check using resolve(projectRoot,
trimmedPhotoPath) and relPath currently can be bypassed by symlinks; before
computing relPath or doing the isAbsolute check, call fs.realpathSync on the
resolved fullPath (and optionally on projectRoot) to get canonical paths, then
compute relative(realProjectRoot, realFullPath) (using the same symbols
fullPath, relPath, resolve, relative, isAbsolute, projectRoot, trimmedPhotoPath)
and perform the startsWith('..') / isAbsolute checks against the real paths so
symlinked files outside the project are correctly rejected.
In `@modes/pdf.md`:
- Around line 185-187: The file contains a duplicated sentence "`photo:` must be
a top-level key in `config/profile.yml` (not under `candidate:`)." — remove one
of the duplicates so the statement appears only once; search for that exact
sentence in modes/pdf.md and delete the redundant occurrence, leaving a single
instance.
In `@templates/cv-template.html`:
- Around line 124-146: The CSS rules for .header-inner, .header-text, and
.cv-photo are not indented consistently with the rest of the style block; update
the indentation to use 2-space indentation for each selector block and their
properties (aligning with existing file style) so .header-inner { ... },
.header-text { ... }, and .cv-photo { ... } are reformatted with two spaces
before each property and the opening/closing braces to match the file's CSS
indentation standard.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2cbb6276-57a3-49d8-be8c-4ad6f9de83ff
📒 Files selected for processing (6)
.release-please-manifest.jsonCHANGELOG.mdconfig/profile.example.ymlgenerate-pdf.mjsmodes/pdf.mdtemplates/cv-template.html
… and add symlink‑resolution safety for photo handling
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@generate-pdf.mjs`:
- Around line 47-55: The code currently resolves a symlink to realPath but then
continues validation and embedding using trimmedPhotoPath/fullPath, which allows
a symlinked file (e.g., secrets.txt) to bypass the .jpg whitelist; change all
subsequent path-based checks and reads to use realPath (and relPath derived from
realpathSync(projectRoot) as already computed) — specifically use realPath when
extracting the extension, checking whitelist, calling fs.stat/size, reading
bytes, and embedding the file instead of trimmedPhotoPath/fullPath so the
canonical target is validated and read.
In `@modes/pdf.md`:
- Around line 182-199: The doc for "Profile Photo (DACH market)" only covers
file type validation but must also document the runtime constraints enforced by
generate-pdf.mjs: state that photo: must be a relative in-repo path (no absolute
paths or paths that escape the repo) and that files >2 MB are rejected/skipped;
explain that when a file is rejected for being out-of-repo, absolute, or too
large the renderer logs a warning and replaces {{PHOTO_BLOCK}} with an empty
string, and update the bullets under `photo:` handling to include these checks
and the exact data-URI behavior (use image/jpeg or image/png) so the prompt
contract matches generate-pdf.mjs.
In `@templates/cv-template.html`:
- Around line 103-117: The separators (`|`) must not be separate flex children
and should be attached to each contact item; update the markup/templating to
stop emitting standalone separator nodes and instead render the separator as
part of each contact element (e.g. append the separator string inside each
contact link/span except the last), and adjust CSS selectors in .contact-row so
the separator is part of the item and cannot wrap (for example use .contact-row
a:not(:last-child)::after { content: " |"; white-space: nowrap; } or add
.contact-item { white-space: nowrap; display: inline-flex; } and remove any
standalone separator elements), replacing usages that relied on span.separator
and the rule .contact-row span:not(.separator) accordingly.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: a54887b0-ff26-4a86-957c-b36d00cd4ede
📒 Files selected for processing (3)
generate-pdf.mjsmodes/pdf.mdtemplates/cv-template.html
… and add symlink‑resolution safety for photo handling
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@generate-pdf.mjs`:
- Around line 61-64: Redundant existsSync check: remove the unreachable if
(!existsSync(fullPath)) { ... } block around
html.replace(/\{\{PHOTO_BLOCK\}\}/g, '') since realpathSync(fullPath) already
throws and the earlier error-handling path (the return at lines handling
realpathSync) covers the "file not found" case; also remove any now-unused
existsSync import/require and keep the replacement logic solely in the
realpathSync error branch, referencing fullPath, realpathSync, and the
html.replace('{{PHOTO_BLOCK}}', ...) usage to locate the code to change.
In `@templates/cv-template.html`:
- Line 398: Remove the stray literal "generate-pdf.mjs" text from the template
so it does not render in the output; specifically edit the line that contains
the separator span with class "separator" and the LinkedIn anchor (the anchor
using {{LINKEDIN_URL}} and {{LINKEDIN_DISPLAY}}) to eliminate the extraneous
text and ensure only the separator span and the anchor remain, preserving proper
spacing and markup.
- Line 147: The comment "/* === SECTIONS === */" is not indented consistently
with other CSS section comments; update the template in
templates/cv-template.html so that the comment string /* === SECTIONS === */ is
prefixed with two space characters to match the existing 2-space indentation
pattern used throughout the file (search for other section comments to copy
their indentation style).
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ea1f3d3f-abf0-4b49-9c17-a167b91d14c0
📒 Files selected for processing (3)
generate-pdf.mjsmodes/pdf.mdtemplates/cv-template.html
…plate markup, remove redundant checks and stray text
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
templates/cv-template.html (2)
396-400:⚠️ Potential issue | 🟡 MinorStandalone separator spans can wrap independently, producing orphaned
|characters.When the header width is constrained by the presence of
{{PHOTO_BLOCK}}, theflex-wrapbehavior on.contact-rowmay cause a<span class="separator">|</span>to wrap onto its own line, resulting in a line starting or ending with just|.Consider attaching the separator to each contact item via CSS pseudo-elements instead:
Proposed fix using ::after pseudo-elements
.contact-row a { color: `#555`; text-decoration: none; white-space: normal; overflow-wrap: anywhere; word-break: break-word; } + + .contact-row > *:not(:last-child)::after { + content: " | "; + color: `#ccc`; + } - .contact-row .separator { - color: `#ccc`; - }Then remove the
<span class="separator">|</span>elements from the markup:<div class="contact-row"> - {{PHONE}}<span class="separator">|</span> - <span>{{EMAIL}}</span><span class="separator">|</span> - <a href="{{LINKEDIN_URL}}">{{LINKEDIN_DISPLAY}}</a><span class="separator">|</span> - <a href="{{PORTFOLIO_URL}}">{{PORTFOLIO_DISPLAY}}</a><span class="separator">|</span> + {{PHONE}} + <span>{{EMAIL}}</span> + <a href="{{LINKEDIN_URL}}">{{LINKEDIN_DISPLAY}}</a> + <a href="{{PORTFOLIO_URL}}">{{PORTFOLIO_DISPLAY}}</a> <span>{{LOCATION}}</span> </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/cv-template.html` around lines 396 - 400, The standalone separator spans (class "separator") can wrap alone causing orphaned "|" characters; remove the explicit <span class="separator">|</span> elements from the contact markup (the lines around {{PHONE}}, {{EMAIL}}, {{LINKEDIN_DISPLAY}}, {{PORTFOLIO_DISPLAY}}, {{LOCATION}} in templates/cv-template.html) and instead add a CSS-based separator on the contact container (e.g., use the .contact-row/.contact-item element and apply a ::after pseudo-element to render the "|" for all but the last item); ensure the pseudo-element uses display/spacing that stays attached to its item and add a last-child rule to suppress the trailing separator.
124-146: 🧹 Nitpick | 🔵 TrivialCSS indentation is inconsistent with the rest of the file.
The new CSS rules for
.header-inner,.header-text, and.cv-photolack the 2-space indentation used throughout the rest of the<style>block.Proposed fix for consistent indentation
- -/* === PROFILE PHOTO (DACH market — optional) === */ -.header-inner { - display: flex; - align-items: flex-start; - gap: 20px; -} - -.header-text { - flex: 1; - min-width: 0; -} - -.cv-photo { - width: 90px; - height: 115px; - object-fit: cover; - object-position: center top; - border-radius: 6px; - flex-shrink: 0; - display: block; -} + + /* === PROFILE PHOTO (DACH market — optional) === */ + .header-inner { + display: flex; + align-items: flex-start; + gap: 20px; + } + + .header-text { + flex: 1; + min-width: 0; + } + + .cv-photo { + width: 90px; + height: 115px; + object-fit: cover; + object-position: center top; + border-radius: 6px; + flex-shrink: 0; + display: block; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/cv-template.html` around lines 124 - 146, The CSS rules for .header-inner, .header-text, and .cv-photo use no leading spaces and are inconsistent with the rest of the <style> block; update these selectors and their rule blocks to use the standard 2-space indentation (match surrounding rules), ensuring each selector line and each property line is indented by two spaces so the block for .header-inner, .header-text, and .cv-photo conforms to the file’s existing styling convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@generate-pdf.mjs`:
- Line 55: The call realpathSync(projectRoot) used to compute relPath can throw
if projectRoot becomes inaccessible; follow the defensive pattern used for
realPath by resolving projectRoot once (e.g., into realProjectRoot) inside a
try-catch and use that value in the relative(realProjectRoot, realPath) call (or
wrap the single realpathSync(projectRoot) call in a try-catch and fall back to
projectRoot or handle the error), updating the relPath assignment to reference
the safe, pre-resolved value; adjust any error logging to include context so
callers know the fallback occurred.
---
Duplicate comments:
In `@templates/cv-template.html`:
- Around line 396-400: The standalone separator spans (class "separator") can
wrap alone causing orphaned "|" characters; remove the explicit <span
class="separator">|</span> elements from the contact markup (the lines around
{{PHONE}}, {{EMAIL}}, {{LINKEDIN_DISPLAY}}, {{PORTFOLIO_DISPLAY}}, {{LOCATION}}
in templates/cv-template.html) and instead add a CSS-based separator on the
contact container (e.g., use the .contact-row/.contact-item element and apply a
::after pseudo-element to render the "|" for all but the last item); ensure the
pseudo-element uses display/spacing that stays attached to its item and add a
last-child rule to suppress the trailing separator.
- Around line 124-146: The CSS rules for .header-inner, .header-text, and
.cv-photo use no leading spaces and are inconsistent with the rest of the
<style> block; update these selectors and their rule blocks to use the standard
2-space indentation (match surrounding rules), ensuring each selector line and
each property line is indented by two spaces so the block for .header-inner,
.header-text, and .cv-photo conforms to the file’s existing styling convention.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8320162a-491c-47ec-a6fe-736ed196b92e
📒 Files selected for processing (3)
generate-pdf.mjsmodes/pdf.mdtemplates/cv-template.html
…ration scripts and template integration
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@temp_cv_generator.mjs`:
- Around line 133-138: There is duplicated photo embedding/security logic
between temp_cv_generator.mjs (the photoBlock replacement using the photoBlock
variable) and generate-pdf.mjs (handlePhotoSubstitution), causing inconsistent
YAML path handling and duplicate validation; remove the photo substitution from
temp_cv_generator.mjs (the conditional that replaces '{{PHOTO_BLOCK}}' using
photoBlock) and let handlePhotoSubstitution in generate-pdf.mjs own all photo
validation/embedding, or alternatively modify the call site in
temp_cv_generator.mjs to pass an explicit flag (e.g., skipPhotoSubstitution)
into the generate-pdf.mjs entry point so handlePhotoSubstitution can skip work
when already done—ensure the YAML photo path used by handlePhotoSubstitution is
the single source of truth and delete the photoBlock replacement code and any
related validation in temp_cv_generator.mjs if you choose the first option.
- Around line 477-480: The try/catch around await import('fs').then(fs =>
fs.promises.mkdir('output', { recursive: true })) currently swallows all errors;
change it to only ignore benign "already exists" cases and surface others by
checking the caught error (e.g., if (!e || e.code !== 'EEXIST') {
console.error('Failed to create output dir:', e); throw e; }) or simply rethrow
non-EEXIST errors; ensure you reference the same import('fs')...mkdir call and
the empty catch block when making this change.
- Around line 54-56: The language detection incorrectly treats Ukrainian ('ua')
as German by including profileData.language?.primary === 'ua' in the isGerman
check; update the logic in the isGerman/lang determination (symbols: isGerman,
lang, profileData.language?.primary) so 'ua' is not mapped to German — either
remove 'ua' from the German branch and let it fall back to English, or add a
separate mapping for Ukrainian (e.g., treat 'ua' as 'uk' or introduce a new
isUkrainian flag and set lang accordingly) so section titles are chosen
correctly.
- Around line 30-52: The photo loading block using profileData.candidate.photo
(photoPath -> pathModule.resolve -> fs.readFile -> base64Photo) must enforce the
same protections as generate-pdf.mjs: reject absolute/outside-project paths by
resolving photoFullPath against a safe base (e.g., process.cwd() or a configured
assets dir) and ensure photoFullPath.startsWith(baseDir) to prevent ../
traversal, validate the file extension against a whitelist (e.g., .png, .jpg,
.jpeg) before reading, check the file size via fs.stat and refuse files larger
than a safe limit (e.g., a few MB) to avoid memory exhaustion, and ensure
fs.readFile is only called after these checks; on any validation failure set
photoBlock = '' and log a clear error.
- Around line 153-194: The custom parseYAML function is brittle; replace it with
js-yaml's loader: add an ES import like "import { load as yamlLoad } from
'js-yaml';" at the top of temp_cv_generator.mjs, remove the parseYAML
implementation, and wherever parseYAML(...) is called return yamlLoad(yamlStr)
(or rename callers to use yamlLoad). Ensure the returned object matches existing
usage (pass { schema: ... } only if needed) and remove any local parseYAML
references to avoid duplication.
- Around line 14-22: The file paths passed to readFile (for
'templates/cv-template.html', 'cv.md', and 'config/profile.yml') are
CWD-relative and should be resolved relative to the script; update the readFile
calls to build absolute paths using the already-defined __dirname (e.g., use
path.join or path.resolve with __dirname) so readFile reads __dirname +
'/templates/cv-template.html', __dirname + '/cv.md', and __dirname +
'/config/profile.yml' when assigning template, cvMarkdown, and profileYaml
respectively.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c83b98bf-3ed2-415e-8c8f-3fa6c3fc9889
📒 Files selected for processing (2)
generate-pdf.mjstemp_cv_generator.mjs
…d template layout
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
temp_cv_generator.mjs (3)
54-57:⚠️ Potential issue | 🟡 MinorUkrainian ('ua') incorrectly mapped to German.
Line 55 treats Ukrainian as German, resulting in German section titles (e.g., "Zusammenfassung", "Berufserfahrung") for Ukrainian CVs. This appears unintentional.
Proposed fix
- const isGerman = profileData.language?.primary === 'de' || profileData.language?.primary === 'ua'; + const isGerman = profileData.language?.primary === 'de';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@temp_cv_generator.mjs` around lines 54 - 57, The language detection is mapping Ukrainian ('ua') to German because isGerman checks for 'de' OR 'ua'; update the logic so only 'de' maps to German (change isGerman to check profileData.language?.primary === 'de' only) and handle Ukrainian explicitly (e.g., set lang = 'uk' when profileData.language?.primary === 'ua', otherwise default to 'en'); update the variables isGerman and lang (and any downstream uses) accordingly to prevent Ukrainian CVs from getting German section titles.
477-480: 🧹 Nitpick | 🔵 TrivialEmpty catch block swallows mkdir errors.
If
mkdirfails for reasons other than "already exists" (e.g., permissions), the error is silently swallowed, and the subsequent PDF write will fail with a confusing error.Proposed fix
try { await import('fs').then(fs => fs.promises.mkdir('output', { recursive: true })); - } catch (e) {} + } catch (e) { + console.warn(`⚠️ Could not create output directory: ${e.message}`); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@temp_cv_generator.mjs` around lines 477 - 480, The empty catch on the output directory creation swallows real failures; update the try/catch around the dynamic fs import and the mkdir call (the import('fs').then(fs => fs.promises.mkdir('output', { recursive: true })) block) to handle errors explicitly: in the catch inspect the thrown error and if error.code indicates "EEXIST" allow it, otherwise log the error (or use console.error/processLogger) and rethrow so permission or other failures surface instead of being silently ignored.
153-194: 🛠️ Refactor suggestion | 🟠 MajorPrefer
js-yamlover custom parser.This custom YAML parser won't handle edge cases like multi-line strings, arrays, anchors, or escaped characters. Since
generate-pdf.mjsalready usesjs-yaml, reuse it for consistency and correctness.♻️ Proposed refactor
Add import at top of file:
import yaml from 'js-yaml';Replace the custom parser:
-// Helper function to parse simple YAML -function parseYAML(yamlStr) { - const lines = yamlStr.split('\n'); - const result = {}; - let currentObj = result; - let stack = [result]; - // ... (40 lines of custom parsing) - return result; -} +// Use js-yaml for robust parsing +function parseYAML(yamlStr) { + return yaml.load(yamlStr) || {}; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@temp_cv_generator.mjs` around lines 153 - 194, Replace the fragile custom parseYAML implementation with js-yaml: add "import yaml from 'js-yaml';" at the top of the file, remove the parseYAML function, and where parseYAML(...) is called use yaml.load(yamlString) (or yaml.safeLoad if you prefer) to get a JS object; ensure callers still expect a plain object and handle thrown parse errors (wrap yaml.load in try/catch or validate before use) so behavior matches the previous API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@temp_cv_generator.mjs`:
- Around line 133-138: Remove the duplicate PHOTO_BLOCK substitution from
temp_cv_generator.mjs: delete the conditional that replaces '{{PHOTO_BLOCK}}'
using the local photoBlock variable (the block around the photoBlock
replacement) so that generate-pdf.mjs's handlePhotoSubstitution is the single
source of truth for photo handling; ensure no other code in
temp_cv_generator.mjs expects the placeholder to be expanded (if so, adjust
callers to pass the raw placeholder to generate-pdf.mjs) and keep
generate-pdf.mjs's path traversal and size validations intact.
- Around line 30-52: profileData is using the wrong YAML key and the file read
is vulnerable to path traversal; change references from
profileData.candidate.photo to profileData.photo (so the photo value is actually
read), and before resolving/reading photoPath validate and restrict it to a safe
project directory: normalize the input, reject absolute paths or paths
containing ../, resolve against a known base (e.g. project asset dir) and ensure
the resolved photoFullPath starts with that base directory; keep the existing
try/catch and on validation failure set photoBlock = '' and log a clear message.
---
Duplicate comments:
In `@temp_cv_generator.mjs`:
- Around line 54-57: The language detection is mapping Ukrainian ('ua') to
German because isGerman checks for 'de' OR 'ua'; update the logic so only 'de'
maps to German (change isGerman to check profileData.language?.primary === 'de'
only) and handle Ukrainian explicitly (e.g., set lang = 'uk' when
profileData.language?.primary === 'ua', otherwise default to 'en'); update the
variables isGerman and lang (and any downstream uses) accordingly to prevent
Ukrainian CVs from getting German section titles.
- Around line 477-480: The empty catch on the output directory creation swallows
real failures; update the try/catch around the dynamic fs import and the mkdir
call (the import('fs').then(fs => fs.promises.mkdir('output', { recursive: true
})) block) to handle errors explicitly: in the catch inspect the thrown error
and if error.code indicates "EEXIST" allow it, otherwise log the error (or use
console.error/processLogger) and rethrow so permission or other failures surface
instead of being silently ignored.
- Around line 153-194: Replace the fragile custom parseYAML implementation with
js-yaml: add "import yaml from 'js-yaml';" at the top of the file, remove the
parseYAML function, and where parseYAML(...) is called use yaml.load(yamlString)
(or yaml.safeLoad if you prefer) to get a JS object; ensure callers still expect
a plain object and handle thrown parse errors (wrap yaml.load in try/catch or
validate before use) so behavior matches the previous API.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 46ec9537-03d6-4152-b3b4-a9efd59a9fbe
📒 Files selected for processing (2)
temp_cv_generator.mjstemplates/cv-template.html
- Load photo from top‑level `profileData.photo` with full path validation.
- Prevent path‑traversal, disallow absolute paths, enforce project‑relative paths, size & format limits.
- Remove duplicate {{PHOTO_BLOCK}} substitution (delegated to generate-pdf.mjs).
- Replace custom YAML parser with robust `js-yaml` loader.
- Add safety warning for output‑directory creation.
- Adjust language detection (German only for `de`).
BREAKING CHANGE: Photo field moved from `candidate.photo` to top‑level `photo` in `config/profile.yml`. Existing configs must be updated accordingly.
…ensorWave/Ema security interns) WebSearch sweep across Ashby/Greenhouse/Lever + PolyAI API. Found 4 new security-domain internship listings added to pipeline. Together AI Security Intern URL rediscovered — already evaluated santifer#334 (4.5/5, April 10); supplemental report santifer#378 written since original santifer#291 is missing from disk. SpeedyApply + SimplifyJobs: 0 new qualifying today. Pipeline additions: Obsidian Security SWE Intern (~3.7), Symmetry Systems Full Stack (~3.6), TensorWave Security Intern (~3.6), Ema Security Intern (~3.5). URGENT: Anthropic Fellows deadline April 26 (8 days). Together AI Security Intern apply if pending. https://claude.ai/code/session_01HP9N7L3xyLXiaJqCNscfKw
c911e42 to
e2bec30
Compare
…Fs, archived 7 below-threshold) - Scan: scan.mjs Level 1/2 + Level 3 WebSearch (Greenhouse FDE/AI Engineer, Ashby Applied AI/FDE, Workable FDE, Himalayas Junior); +10 new URLs appended to data/pipeline.md and data/scan-history.tsv. - Pipeline (santifer#286-santifer#294): 9 evaluations across Sezzle II (LATAM-only), Canopy Connect FDE (3.2/5 PDF), Caylent FDE (Canada Senior no-spons), Prelude FDE (Sr endpoint security), Silent Eight Jr FDE (Poland-only), InnovationTeam Jr CV (India-only Senior), Innovaccer FDE AI (3.4/5 PDF - direct healthcare archetype + domain fit), Anthropic Paris FDE (native French), Sayari CE FDE (Senior). Zapier closed (substituted IDs for Prelude/Sayari where original Greenhouse/Ashby IDs returned 404/null). - 2 PDFs generated for score >= 3.0 (Canopy Connect, Innovaccer); others marked 'Not generated (score < 3.0)' per PDF policy. - merge-tracker.mjs: +7 added (skipped Sezzle/Anthropic — existing higher- score entries already tracked). - verify-pipeline.mjs: 0 errors / 0 warnings. - cleanup-low-scores.mjs: archived 5 reports under reports/below-threshold/ (santifer#288 Caylent, santifer#289 Prelude, santifer#290 Silent Eight, santifer#291 InnovationTeam, santifer#294 Sayari) + 2 orphan reports (santifer#286 Sezzle, santifer#293 Anthropic Paris). https://claude.ai/code/session_overnight_2026-04-29_T2026-04-29T20:39Z
|
Hi, Severity: remediation recommended | Category: reliability How to fix: Use os.tmpdir() or output/ Agent prompt to fix - you can give this to your LLM of choice:
Spotted by Qodo code review - free for open-source projects. |
German-speaking market (DACH) expects a Bewerbungsfoto in the top-right corner of the CV. This change adds opt-in photo support:
Closes #264
Behavior:
References:
Testing
handlePhotoSubstitution.test.mjsthat covers:node test-all.mjs --quick.Technical Details
The photo is embedded as a base64 Data URI directly into the HTML before it is sent to Playwright. This ensures the PDF is self-contained and avoids issues with local file access in different environments.
User Review Required
None. The feature is opt-in and does not change the layout for users who don't provide a photo.
What does this PR do?
Related issue
Type of change
Checklist
node test-all.mjsand all tests passQuestions? Join the Discord for faster feedback.
Summary by CodeRabbit
New Features
Documentation
Style
Chores