feat: show ATS-safety badge on each CV template

The competitor research flagged ATS transparency as a market gap
worth owning (nobody shows an ATS rating; Canva's canvas layouts fail
72% of parses with no warning). The backend template descriptor
already carried a LayoutFamily/AtsRating pair; the frontend template
picker duplicates that catalog in a hardcoded array and never
displayed it.

Add atsRating to each of the six templates, matching the backend
values (single-column templates: High: sidebar/grid templates:
Medium), and show it as a badge on every template card.

The frontend/backend template catalog duplication itself is a known
gap (frontend never calls GET /profile-cv/templates) -- left as-is
here since rewiring it is Phase F3/F4 scope, not a quick fix.
This commit is contained in:
cesnimda
2026-07-12 15:47:04 +02:00
parent 00a035ea20
commit 00c7e0b6ca
+27 -7
View File
@@ -71,6 +71,12 @@ type RewriteTemplateOption = {
sampleHeading: string; sampleHeading: string;
sampleMeta: string; sampleMeta: string;
sampleBullets: string[]; sampleBullets: string[];
// Mirrors JobTrackerApi's CvTemplateDescriptor.AtsRating (career-workspace-implementation-roadmap.md
// Phase F3): single-column layouts read top-to-bottom with no ATS parsing risk; sidebar/grid
// layouts carry the same risk pattern the competitor research flagged in Canva's floating-box
// designs, so they're rated Medium even though structured-data rendering keeps them far safer
// than a canvas tool's undefined reading order.
atsRating: "High" | "Medium";
}; };
type CvBuilderPreview = { type CvBuilderPreview = {
@@ -136,7 +142,8 @@ const REWRITE_TEMPLATES: RewriteTemplateOption[] = [
blurb: "Compact, direct, and easy for screening systems to parse.", blurb: "Compact, direct, and easy for screening systems to parse.",
sampleHeading: "Senior Backend Engineer", sampleHeading: "Senior Backend Engineer",
sampleMeta: "Acme Systems · Oslo · 2021 - Present", sampleMeta: "Acme Systems · Oslo · 2021 - Present",
sampleBullets: ["Built API workflows with measurable delivery outcomes.", "Kept skills and achievements easy to scan."] sampleBullets: ["Built API workflows with measurable delivery outcomes.", "Kept skills and achievements easy to scan."],
atsRating: "High"
}, },
{ {
id: "harvard", id: "harvard",
@@ -146,7 +153,8 @@ const REWRITE_TEMPLATES: RewriteTemplateOption[] = [
blurb: "Formal hierarchy and restrained tone for conservative hiring flows.", blurb: "Formal hierarchy and restrained tone for conservative hiring flows.",
sampleHeading: "Professional Summary", sampleHeading: "Professional Summary",
sampleMeta: "Clear structure · precise dates · credible language", sampleMeta: "Clear structure · precise dates · credible language",
sampleBullets: ["Emphasizes polished summaries.", "Works well for broad professional roles."] sampleBullets: ["Emphasizes polished summaries.", "Works well for broad professional roles."],
atsRating: "High"
}, },
{ {
id: "auckland", id: "auckland",
@@ -156,7 +164,8 @@ const REWRITE_TEMPLATES: RewriteTemplateOption[] = [
blurb: "Sharper highlights with a more contemporary, design-forward rhythm.", blurb: "Sharper highlights with a more contemporary, design-forward rhythm.",
sampleHeading: "Selected Impact", sampleHeading: "Selected Impact",
sampleMeta: "Focused strengths · compact highlights", sampleMeta: "Focused strengths · compact highlights",
sampleBullets: ["Pulls skills into stronger highlight clusters.", "Good when you want a fresher feel."] sampleBullets: ["Pulls skills into stronger highlight clusters.", "Good when you want a fresher feel."],
atsRating: "Medium"
}, },
{ {
id: "edinburgh", id: "edinburgh",
@@ -166,7 +175,8 @@ const REWRITE_TEMPLATES: RewriteTemplateOption[] = [
blurb: "More personality and stronger section contrast without losing clarity.", blurb: "More personality and stronger section contrast without losing clarity.",
sampleHeading: "Experience Highlights", sampleHeading: "Experience Highlights",
sampleMeta: "Premium spacing · stronger visual voice", sampleMeta: "Premium spacing · stronger visual voice",
sampleBullets: ["Useful when the CV should feel more distinctive.", "Still keeps wording grounded and factual."] sampleBullets: ["Useful when the CV should feel more distinctive.", "Still keeps wording grounded and factual."],
atsRating: "Medium"
}, },
{ {
id: "monarch", id: "monarch",
@@ -176,7 +186,8 @@ const REWRITE_TEMPLATES: RewriteTemplateOption[] = [
blurb: "High-contrast premium presentation for leadership-heavy applications.", blurb: "High-contrast premium presentation for leadership-heavy applications.",
sampleHeading: "Executive Profile", sampleHeading: "Executive Profile",
sampleMeta: "Leadership clarity · premium hierarchy", sampleMeta: "Leadership clarity · premium hierarchy",
sampleBullets: ["Adds more top-level summary emphasis.", "Well suited to senior strategic roles."] sampleBullets: ["Adds more top-level summary emphasis.", "Well suited to senior strategic roles."],
atsRating: "High"
}, },
{ {
id: "fjord", id: "fjord",
@@ -186,7 +197,8 @@ const REWRITE_TEMPLATES: RewriteTemplateOption[] = [
blurb: "Calm, high-density layout for engineering resumes and project-heavy CVs.", blurb: "Calm, high-density layout for engineering resumes and project-heavy CVs.",
sampleHeading: "Projects & Systems", sampleHeading: "Projects & Systems",
sampleMeta: "Technical depth · practical readability", sampleMeta: "Technical depth · practical readability",
sampleBullets: ["Gives projects and skills more weight.", "Better for technical detail without chaos."] sampleBullets: ["Gives projects and skills more weight.", "Better for technical detail without chaos."],
atsRating: "Medium"
}, },
]; ];
@@ -1059,7 +1071,15 @@ export default function ProfilePage() {
<Typography variant="subtitle2" sx={{ fontWeight: 900 }}>{option.title}</Typography> <Typography variant="subtitle2" sx={{ fontWeight: 900 }}>{option.title}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", mt: 0.25, lineHeight: 1.4 }}>{option.blurb}</Typography> <Typography variant="body2" sx={{ color: "text.secondary", mt: 0.25, lineHeight: 1.4 }}>{option.blurb}</Typography>
</Box> </Box>
{selected ? <Chip size="small" color="primary" label="Selected" /> : null} <Box sx={{ display: "flex", flexDirection: "column", gap: 0.5, alignItems: "flex-end" }}>
{selected ? <Chip size="small" color="primary" label="Selected" /> : null}
<Chip
size="small"
variant="outlined"
color={option.atsRating === "High" ? "success" : "default"}
label={`ATS: ${option.atsRating}`}
/>
</Box>
</Box> </Box>
</Box> </Box>
</Box> </Box>