feat(builder): redesign CV creator UI for typing comfort and optimize ATS PDF template#3
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughInput focus styling is changed globally from black-background/white-text to white-background/black-text with border and box-shadow, propagated across all builder form components. ChangesBuilder UI Styling, Badges, and ATS PDF Overhaul
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. 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.
Actionable comments posted: 8
🧹 Nitpick comments (1)
src/components/pdf/ats-pdf.tsx (1)
284-287: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffHardcoded English connectors bypass localization.
While section headers and most strings come from
l.*, the connectorsat {exp.company},{edu.degree} in {edu.field}, andRelevant Coursework:are hardcoded English. For non-English locales these will render mixed-language output. Consider sourcing them fromPdfLabels.Also applies to: 491-496, 506-512
🤖 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 `@src/components/pdf/ats-pdf.tsx` around lines 284 - 287, The PDF ATS component still renders hardcoded English connectors in the experience, education, and coursework sections, which causes mixed-language output in localized builds. Update the affected rendering in ats-pdf.tsx to use PdfLabels-backed strings instead of inline literals for the “at”, “in”, and “Relevant Coursework” text, and wire the existing JSX in the exp/edu/coursework blocks to read from the locale labels consistently with the rest of the file.
🤖 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 `@src/app/globals.css`:
- Line 44: The CSS declaration in the global styles uses a keyword casing that
breaks stylelint’s value-keyword-case rule. Update the text-rendering value in
the globals stylesheet to use the expected lowercase keyword form, and keep the
change localized to the same declaration so the lint error is resolved without
affecting the rest of the styles.
- Around line 197-200: The select focus styling in select:focus uses white
gradient caret indicators that disappear against the new white focus background.
Update the caret/background-image colors in the select:focus rule so the
dropdown indicator remains visible on focus, and keep the change aligned with
the existing select focus styling in globals.css.
In `@src/components/builder/builder-form.tsx`:
- Around line 241-246: The score badge thresholds in builder-form.tsx are
inconsistent with the ScoreRing display, causing 80–84 scores to be shown
differently in the navigation and panel. Update the scoreColor logic in the
builder-form component to match the ScoreRing green threshold at 80 instead of
85, and keep the yellow/red cutoffs aligned with the same scoring rules.
- Around line 178-246: The switch branches in getSectionBadge declare bindings
directly under case labels, which violates noSwitchDeclarations. Wrap the
personal, skills, and score case bodies in braces so their const declarations
are scoped safely, and keep the existing logic and return values unchanged.
- Around line 56-59: The outside-click check in builder-form uses a single
sectionManagerRef for both desktop and mobile wrappers, so it can resolve to
only one layout and misclassify clicks in the other as outside. Split this into
separate refs for the desktop and mobile section-manager wrappers, then update
the click containment logic to treat a click as inside if it lands within either
ref. Use the existing sectionManagerRef usage site and the wrapper components in
builder-form to replace the shared ref with breakpoint-specific refs.
In `@src/components/builder/forms/personal-info-form.tsx`:
- Around line 684-687: The invalid input styling in the personal info form loses
the red border on focus because the conditional class string only sets the red
border by default, so update the field class logic in personal-info-form.tsx to
keep the error state visibly red while focused by adding the focused red border
class alongside the existing error styles in the relevant input rendering path.
Use the conditional class block that checks errors[field] to ensure the focus
styling for invalid fields overrides the global input focus border rule.
In `@src/components/pdf/ats-pdf.tsx`:
- Around line 420-432: The competition link in the ATS PDF is using the wrong
visible label, so update the `Link` inside `ats-pdf.tsx` to use a
competition-appropriate text instead of `l.project`. Use an existing
localization key that matches a competition action such as `l.website` or a
certificate/view label, or render the competition name/URL directly, and keep
the `comp.url` and `ensureHref` logic unchanged.
- Around line 205-250: The contact separator logic in ats-pdf.tsx is using
pairwise conditions, so bullets disappear whenever a middle field is missing.
Refactor the contact row rendering around the contact item list in the ats-pdf
component: collect the visible fields for personalInfo.email, phone, location,
website, linkedin, and github, then render them in order with separators
inserted between every adjacent rendered item. Keep ensureHref and the existing
labels (like l.portfolio and LinkedIn) when building each item.
---
Nitpick comments:
In `@src/components/pdf/ats-pdf.tsx`:
- Around line 284-287: The PDF ATS component still renders hardcoded English
connectors in the experience, education, and coursework sections, which causes
mixed-language output in localized builds. Update the affected rendering in
ats-pdf.tsx to use PdfLabels-backed strings instead of inline literals for the
“at”, “in”, and “Relevant Coursework” text, and wire the existing JSX in the
exp/edu/coursework blocks to read from the locale labels consistently with the
rest of the file.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: d0768d17-ad96-4d1c-8434-0c8e603727d1
📒 Files selected for processing (13)
src/app/globals.csssrc/components/builder/builder-form.tsxsrc/components/builder/builder-header.tsxsrc/components/builder/forms/certifications-form.tsxsrc/components/builder/forms/competitions-form.tsxsrc/components/builder/forms/education-form.tsxsrc/components/builder/forms/experience-form.tsxsrc/components/builder/forms/languages-form.tsxsrc/components/builder/forms/personal-info-form.tsxsrc/components/builder/forms/projects-form.tsxsrc/components/builder/forms/skills-form.tsxsrc/components/builder/resume-score.tsxsrc/components/pdf/ats-pdf.tsx
| font-feature-settings: | ||
| "kern" 1, | ||
| "liga" 1; | ||
| text-rendering: optimizeLegibility; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the lint-breaking keyword casing.
Line 44 will keep stylelint failing under value-keyword-case.
Suggested fix
- text-rendering: optimizeLegibility;
+ text-rendering: optimizelegibility;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| text-rendering: optimizeLegibility; | |
| text-rendering: optimizelegibility; |
🧰 Tools
🪛 Stylelint (17.13.0)
[error] 44-44: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)
(value-keyword-case)
🤖 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 `@src/app/globals.css` at line 44, The CSS declaration in the global styles
uses a keyword casing that breaks stylelint’s value-keyword-case rule. Update
the text-rendering value in the globals stylesheet to use the expected lowercase
keyword form, and keep the change localized to the same declaration so the lint
error is resolved without affecting the rest of the styles.
Source: Linters/SAST tools
| select:focus { | ||
| background-image: | ||
| linear-gradient(45deg, transparent 50%, #FFFFFF 50%), | ||
| linear-gradient(135deg, #FFFFFF 50%, transparent 50%); | ||
| linear-gradient(45deg, transparent 50%, #ffffff 50%), | ||
| linear-gradient(135deg, #ffffff 50%, transparent 50%); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Focused select caret becomes invisible on the new white focus background.
With white focus background (Lines 97-98), keeping white caret gradients at Line 199-200 removes the visible dropdown indicator.
Suggested fix
select:focus {
background-image:
- linear-gradient(45deg, transparent 50%, `#ffffff` 50%),
- linear-gradient(135deg, `#ffffff` 50%, transparent 50%);
+ linear-gradient(45deg, transparent 50%, `#000000` 50%),
+ linear-gradient(135deg, `#000000` 50%, transparent 50%);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| select:focus { | |
| background-image: | |
| linear-gradient(45deg, transparent 50%, #FFFFFF 50%), | |
| linear-gradient(135deg, #FFFFFF 50%, transparent 50%); | |
| linear-gradient(45deg, transparent 50%, #ffffff 50%), | |
| linear-gradient(135deg, #ffffff 50%, transparent 50%); | |
| select:focus { | |
| background-image: | |
| linear-gradient(45deg, transparent 50%, `#000000` 50%), | |
| linear-gradient(135deg, `#000000` 50%, transparent 50%); | |
| } |
🤖 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 `@src/app/globals.css` around lines 197 - 200, The select focus styling in
select:focus uses white gradient caret indicators that disappear against the new
white focus background. Update the caret/background-image colors in the
select:focus rule so the dropdown indicator remains visible on focus, and keep
the change aligned with the existing select focus styling in globals.css.
| if ( | ||
| sectionManagerRef.current && | ||
| !sectionManagerRef.current.contains(e.target as Node) | ||
| ) { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify there is only one assignment per section manager ref after the fix.
rg -n -C2 'sectionManagerRef|desktopSectionManagerRef|mobileSectionManagerRef' src/components/builder/builder-form.tsxRepository: lab68dev/lab68cvbuilder
Length of output: 1049
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the surrounding component structure and the click-outside logic.
sed -n '1,120p' src/components/builder/builder-form.tsx
printf '\n---\n'
sed -n '240,520p' src/components/builder/builder-form.tsxRepository: lab68dev/lab68cvbuilder
Length of output: 17928
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check whether desktop/mobile sections are both rendered in the same component tree.
rg -n -C3 'lg:|md:|Mobile|Desktop|sectionManagerRef|showSectionManager' src/components/builder/builder-form.tsxRepository: lab68dev/lab68cvbuilder
Length of output: 4132
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the surrounding component structure and the click-outside logic.
sed -n '1,120p' src/components/builder/builder-form.tsx
printf '\n---\n'
sed -n '240,520p' src/components/builder/builder-form.tsxRepository: lab68dev/lab68cvbuilder
Length of output: 17928
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check whether desktop/mobile sections are both rendered in the same component tree.
rg -n -C3 'lg:|md:|Mobile|Desktop|sectionManagerRef|showSectionManager' src/components/builder/builder-form.tsxRepository: lab68dev/lab68cvbuilder
Length of output: 4132
Split the section-manager refs by breakpoint. The same ref is attached to both desktop and mobile wrappers, so it can point at the later one in the tree and make clicks in the other layout register as outside clicks. Use separate refs and treat either wrapper as inside.
🤖 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 `@src/components/builder/builder-form.tsx` around lines 56 - 59, The
outside-click check in builder-form uses a single sectionManagerRef for both
desktop and mobile wrappers, so it can resolve to only one layout and
misclassify clicks in the other as outside. Split this into separate refs for
the desktop and mobile section-manager wrappers, then update the click
containment logic to treat a click as inside if it lands within either ref. Use
the existing sectionManagerRef usage site and the wrapper components in
builder-form to replace the shared ref with breakpoint-specific refs.
| const getSectionBadge = (id: string) => { | ||
| switch (id) { | ||
| case "personal": | ||
| const hasContact = Boolean( | ||
| data.personalInfo.fullName.trim() && data.personalInfo.email.trim(), | ||
| ); | ||
| return hasContact ? ( | ||
| <span className="text-[9px] font-bold px-1.5 py-0.5 uppercase tracking-wider bg-green-100 text-green-800 border border-green-200"> | ||
| OK | ||
| </span> | ||
| ) : ( | ||
| <span className="text-[9px] font-bold px-1.5 py-0.5 uppercase tracking-wider bg-amber-100 text-amber-800 border border-amber-200"> | ||
| Required | ||
| </span> | ||
| ); | ||
| case "experience": | ||
| return data.experience.length > 0 ? ( | ||
| <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-sm bg-gray-100 text-gray-800 border border-gray-300"> | ||
| {data.experience.length} | ||
| </span> | ||
| ) : null; | ||
| case "education": | ||
| return data.education.length > 0 ? ( | ||
| <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-sm bg-gray-100 text-gray-800 border border-gray-300"> | ||
| {data.education.length} | ||
| </span> | ||
| ) : null; | ||
| case "skills": | ||
| const skillCount = data.skills.reduce( | ||
| (acc, cat) => acc + cat.items.length, | ||
| 0, | ||
| ); | ||
| return skillCount > 0 ? ( | ||
| <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-sm bg-gray-100 text-gray-800 border border-gray-300"> | ||
| {skillCount} | ||
| </span> | ||
| ) : null; | ||
| case "projects": | ||
| return data.projects.length > 0 ? ( | ||
| <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-sm bg-gray-100 text-gray-800 border border-gray-300"> | ||
| {data.projects.length} | ||
| </span> | ||
| ) : null; | ||
| case "certifications": | ||
| return data.certifications.length > 0 ? ( | ||
| <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-sm bg-gray-100 text-gray-800 border border-gray-300"> | ||
| {data.certifications.length} | ||
| </span> | ||
| ) : null; | ||
| case "competitions": | ||
| return (data.competitions ?? []).length > 0 ? ( | ||
| <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-sm bg-gray-100 text-gray-800 border border-gray-300"> | ||
| {(data.competitions ?? []).length} | ||
| </span> | ||
| ) : null; | ||
| case "languages": | ||
| return data.languages.length > 0 ? ( | ||
| <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-sm bg-gray-100 text-gray-800 border border-gray-300"> | ||
| {data.languages.length} | ||
| </span> | ||
| ) : null; | ||
| case "score": | ||
| const { total } = scoreResume(data); | ||
| const scoreColor = | ||
| total >= 85 | ||
| ? "bg-green-100 text-green-800 border-green-200" | ||
| : total >= 50 | ||
| ? "bg-yellow-100 text-yellow-800 border-yellow-200" | ||
| : "bg-red-100 text-red-800 border-red-200"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
# Inspect the target file and the surrounding switch cases
git ls-files src/components/builder/builder-form.tsx
wc -l src/components/builder/builder-form.tsx
sed -n '160,270p' src/components/builder/builder-form.tsxRepository: lab68dev/lab68cvbuilder
Length of output: 4749
🏁 Script executed:
# Inspect the target file and the surrounding switch cases
git ls-files src/components/builder/builder-form.tsx
wc -l src/components/builder/builder-form.tsx
sed -n '160,270p' src/components/builder/builder-form.tsxRepository: lab68dev/lab68cvbuilder
Length of output: 4749
🏁 Script executed:
nl -ba src/components/builder/builder-form.tsx | sed -n '175,250p'Repository: lab68dev/lab68cvbuilder
Length of output: 197
🏁 Script executed:
nl -ba src/components/builder/builder-form.tsx | sed -n '175,250p'Repository: lab68dev/lab68cvbuilder
Length of output: 197
Wrap the case bodies that declare bindings. personal, skills, and score each introduce const values directly under case; wrap those branches in braces to satisfy lint/correctness/noSwitchDeclarations.
🧰 Tools
🪛 Biome (2.5.0)
[error] 181-183: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
[error] 206-209: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
[error] 240-240: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
[error] 241-246: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
🤖 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 `@src/components/builder/builder-form.tsx` around lines 178 - 246, The switch
branches in getSectionBadge declare bindings directly under case labels, which
violates noSwitchDeclarations. Wrap the personal, skills, and score case bodies
in braces so their const declarations are scoped safely, and keep the existing
logic and return values unchanged.
Source: Linters/SAST tools
| const scoreColor = | ||
| total >= 85 | ||
| ? "bg-green-100 text-green-800 border-green-200" | ||
| : total >= 50 | ||
| ? "bg-yellow-100 text-yellow-800 border-yellow-200" | ||
| : "bg-red-100 text-red-800 border-red-200"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Align the score badge threshold with the score panel.
Line 242 uses green at 85%, while ScoreRing turns green at 80%; scores from 80–84 will show conflicting colors between navigation and the score panel.
- total >= 85
+ total >= 80📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const scoreColor = | |
| total >= 85 | |
| ? "bg-green-100 text-green-800 border-green-200" | |
| : total >= 50 | |
| ? "bg-yellow-100 text-yellow-800 border-yellow-200" | |
| : "bg-red-100 text-red-800 border-red-200"; | |
| const scoreColor = | |
| total >= 80 | |
| ? "bg-green-100 text-green-800 border-green-200" | |
| : total >= 50 | |
| ? "bg-yellow-100 text-yellow-800 border-yellow-200" | |
| : "bg-red-100 text-red-800 border-red-200"; |
🧰 Tools
🪛 Biome (2.5.0)
[error] 241-246: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
🤖 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 `@src/components/builder/builder-form.tsx` around lines 241 - 246, The score
badge thresholds in builder-form.tsx are inconsistent with the ScoreRing
display, causing 80–84 scores to be shown differently in the navigation and
panel. Update the scoreColor logic in the builder-form component to match the
ScoreRing green threshold at 80 instead of 85, and keep the yellow/red cutoffs
aligned with the same scoring rules.
| `w-full border bg-transparent px-4 py-3 transition-all duration-150 ${ | ||
| errors[field] | ||
| ? "border-red-500 focus:bg-red-50 focus:text-red-900" | ||
| : "border-black focus:border-black" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep error border red while focused.
Line 686 misses focus:border-red-500; with global input:focus { border-color: #000000; }, invalid fields can look valid while focused.
Suggested fix
const inputClass = (field: string) =>
`w-full border bg-transparent px-4 py-3 transition-all duration-150 ${
errors[field]
- ? "border-red-500 focus:bg-red-50 focus:text-red-900"
+ ? "border-red-500 focus:border-red-500 focus:bg-red-50 focus:text-red-900"
: "border-black focus:border-black"
}`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| `w-full border bg-transparent px-4 py-3 transition-all duration-150 ${ | |
| errors[field] | |
| ? "border-red-500 focus:bg-red-50 focus:text-red-900" | |
| : "border-black focus:border-black" | |
| `w-full border bg-transparent px-4 py-3 transition-all duration-150 ${ | |
| errors[field] | |
| ? "border-red-500 focus:border-red-500 focus:bg-red-50 focus:text-red-900" | |
| : "border-black focus:border-black" |
🤖 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 `@src/components/builder/forms/personal-info-form.tsx` around lines 684 - 687,
The invalid input styling in the personal info form loses the red border on
focus because the conditional class string only sets the red border by default,
so update the field class logic in personal-info-form.tsx to keep the error
state visibly red while focused by adding the focused red border class alongside
the existing error styles in the relevant input rendering path. Use the
conditional class block that checks errors[field] to ensure the focus styling
for invalid fields overrides the global input focus border rule.
| <View style={s.contactRow}> | ||
| {personalInfo.email && <Text style={s.contactText}>{personalInfo.email}</Text>} | ||
| {personalInfo.phone && <Text style={s.contactText}>{personalInfo.phone}</Text>} | ||
| {personalInfo.location && <Text style={s.contactText}>{personalInfo.location}</Text>} | ||
| {personalInfo.website && <Link src={ensureHref(personalInfo.website)} style={{ ...s.contactText, color: "#666666", textDecoration: "none" }}>{l.portfolio}</Link>} | ||
| {personalInfo.linkedin && <Link src={ensureHref(personalInfo.linkedin)} style={{ ...s.contactText, color: "#666666", textDecoration: "none" }}>LinkedIn</Link>} | ||
| {personalInfo.github && <Link src={ensureHref(personalInfo.github)} style={{ ...s.contactText, color: "#666666", textDecoration: "none" }}>GitHub</Link>} | ||
| {personalInfo.email && ( | ||
| <Text style={s.contactItem}>{personalInfo.email}</Text> | ||
| )} | ||
| {personalInfo.email && personalInfo.phone && ( | ||
| <Text style={s.bulletSeparator}>•</Text> | ||
| )} | ||
| {personalInfo.phone && ( | ||
| <Text style={s.contactItem}>{personalInfo.phone}</Text> | ||
| )} | ||
| {personalInfo.phone && personalInfo.location && ( | ||
| <Text style={s.bulletSeparator}>•</Text> | ||
| )} | ||
| {personalInfo.location && ( | ||
| <Text style={s.contactItem}>{personalInfo.location}</Text> | ||
| )} | ||
| {personalInfo.location && personalInfo.website && ( | ||
| <Text style={s.bulletSeparator}>•</Text> | ||
| )} | ||
| {personalInfo.website && ( | ||
| <Link | ||
| src={ensureHref(personalInfo.website)} | ||
| style={s.contactItem} | ||
| > | ||
| {l.portfolio} | ||
| </Link> | ||
| )} | ||
| {(personalInfo.website || personalInfo.location) && | ||
| personalInfo.linkedin && <Text style={s.bulletSeparator}>•</Text>} | ||
| {personalInfo.linkedin && ( | ||
| <Link | ||
| src={ensureHref(personalInfo.linkedin)} | ||
| style={s.contactItem} | ||
| > | ||
| </Link> | ||
| )} | ||
| {personalInfo.linkedin && personalInfo.github && ( | ||
| <Text style={s.bulletSeparator}>•</Text> | ||
| )} | ||
| {personalInfo.github && ( | ||
| <Link src={ensureHref(personalInfo.github)} style={s.contactItem}> | ||
| GitHub | ||
| </Link> | ||
| )} | ||
| </View> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Contact separators collapse when an intermediate field is missing.
The bullet separators are gated on adjacent-pair presence, so any absent middle field drops the separator between the surviving neighbors. Examples that render with no separator:
email+location(no phone) → joined directly.email+linkedinonly →(website || location) && linkedinis false.website+github(no linkedin) →linkedin && githubis false.email+githubonly.
Build the visible items into a list and interleave separators instead of pairwise conditions.
🐛 Suggested approach: derive items, then join with separators
const contactItems = [
personalInfo.email && (
<Text style={s.contactItem}>{personalInfo.email}</Text>
),
personalInfo.phone && (
<Text style={s.contactItem}>{personalInfo.phone}</Text>
),
personalInfo.location && (
<Text style={s.contactItem}>{personalInfo.location}</Text>
),
personalInfo.website && (
<Link src={ensureHref(personalInfo.website)} style={s.contactItem}>
{l.portfolio}
</Link>
),
personalInfo.linkedin && (
<Link src={ensureHref(personalInfo.linkedin)} style={s.contactItem}>
LinkedIn
</Link>
),
personalInfo.github && (
<Link src={ensureHref(personalInfo.github)} style={s.contactItem}>
GitHub
</Link>
),
].filter(Boolean);
// ...
<View style={s.contactRow}>
{contactItems.map((item, i) => (
<React.Fragment key={i}>
{i > 0 && <Text style={s.bulletSeparator}>•</Text>}
{item}
</React.Fragment>
))}
</View>🤖 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 `@src/components/pdf/ats-pdf.tsx` around lines 205 - 250, The contact separator
logic in ats-pdf.tsx is using pairwise conditions, so bullets disappear whenever
a middle field is missing. Refactor the contact row rendering around the contact
item list in the ats-pdf component: collect the visible fields for
personalInfo.email, phone, location, website, linkedin, and github, then render
them in order with separators inserted between every adjacent rendered item.
Keep ensureHref and the existing labels (like l.portfolio and LinkedIn) when
building each item.
| {comp.url && ( | ||
| <Link | ||
| src={ensureHref(comp.url)} | ||
| style={{ | ||
| fontSize: 7.5, | ||
| color: "#666666", | ||
| textDecoration: "none", | ||
| marginBottom: 2, | ||
| }} | ||
| > | ||
| {l.project} | ||
| </Link> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Competition link is labeled "Project".
The competition URL uses l.project as its visible text, which mislabels the link. Use a competition-appropriate label (e.g. l.website / l.viewCertificate-style key) or render the URL/competition name instead.
🤖 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 `@src/components/pdf/ats-pdf.tsx` around lines 420 - 432, The competition link
in the ATS PDF is using the wrong visible label, so update the `Link` inside
`ats-pdf.tsx` to use a competition-appropriate text instead of `l.project`. Use
an existing localization key that matches a competition action such as
`l.website` or a certificate/view label, or render the competition name/URL
directly, and keep the `comp.url` and `ensureHref` logic unchanged.
Summary
This Pull Request delivers key UX/UI visual improvements to the CV Creator section to make typing and form navigation highly comfortable, while reformatting the ATS PDF template to a single-column structure to maximize parseability for Applicant Tracking Systems.
Proposed Changes
UI/UX & Focus States Comfort
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1)) insideglobals.css.focus:bg-blackandfocus:text-whiteclasses across all form files (personal-info,experience,education,skills,projects,certifications,competitions,languages) andbuilder-header.tsx, allowing them to cleanly inherit the new global comfort-focused styling.Desktop Sidebar Navigation & Progress Tracking
mdandlgscreens) inbuilder-form.tsx, leaving the main editing form with ample vertical focus.Experience (3),Projects (2)).Personal Info).ATS Optimization
ats-pdf.tsx) from a two-column flex layout to a standardized single-column layout flowing from top to bottom. This prevents automated parser engines from reading left/right columns out of order.competitionssupport to the ATS PDF template, matching the schema rendered by the HTML preview.Verification Plan
Automated Tests
Manual Checks
Summary by CodeRabbit
New Features
Bug Fixes