Skip to main content

CV Field Mapping Fixes & Schema Alignment

Date: 2026-03-31 Scope: Structured CV field mapping across all views (company talent, company candidate, jobseeker CV builder), edit guards, onboarding pre-fill, skill selector UX.


1. Problem Statement

The same user's CV showed different data depending on where it was viewed:

  • Talent view (company side) showed data from user.qualifications (legacy JSON blob)
  • Candidate view showed data from structured CV tables (talent.activeCV)
  • Jobseeker view read from structured CV but several fields were missing in the mapping pipeline

Root causes:

  1. Structured CV fields were not fully mapped through the frontend pipeline
  2. TalentDetails bypassed structured CV data entirely for qualifications
  3. cvSkills was missing from key extraction/normalization functions
  4. Several backend schema fields had no frontend representation
  5. Some frontend fields (e.g., Certificate.location) didn't exist in the backend

2. Data Flow Architecture (After Fix)

Backend Schema (12 structured tables)


GraphQL Fragments (cv/fragments.ts)
│ CV_STRUCTURED_FIELDS ── all fields from all 12 tables
│ CV_STRUCTURED_LIST_FIELDS ── profile + jobPreference + skills (list views)

TypeScript Types (structuredCv.ts, structuredCvInputs.ts)
│ StructuredCvFields ── composite of all 12 interfaces
│ Update/Create input types ── mirror backend input types

Mappers
│ structuredCvToProfileForm.ts ── GraphQL → ProfileFormData (for editing)
│ structuredCvMapper.ts ── GraphQL → Qualifications (for display)
│ dataTransformers.ts ── Resolution layer (structured → schema → user fallback)
│ profileFormToInputs.ts ── ProfileFormData → mutation inputs (for saving)

Components
│ ProfileForm.tsx ── Edit form (jobseeker + company add candidate)
│ ResumePreviewPage.tsx ── Read-only display
│ ExperienceCard / EducationCard / CertificateCard ── Inline edit cards
│ SkillsSelector / LanguagesSelectorWrapper ── Skill/language pickers

Mutations (structuredMutations.ts)
27 jobseeker "My" mutations
3 company-scoped skill mutations

Data Source Priority

ViewPriority
Talent detail (company)structured CV (activeCV) → user.qualifications (legacy JSON)
Candidate detail (company)structured CV (talent.activeCV ?? candidate.cv) → user.qualifications
Jobseeker editstructured CV via GET_CV_BY_IDstructuredCvToProfileForm()
Jobseeker previewstructured CV → structuredCvToProfileForm()

3. Field Mapping Matrix

CvProfile → ProfileForm

Backend FieldForm FieldMutation InputSent?
firstNamefirstNamefirstNameYes
middleNamemiddleNamemiddleNameYes
lastNamelastNamelastNameYes
emailemailemailYes
phonephonephoneYes
titletitletitleYes
locationlocationlocationYes
linkedinUrllinkedinlinkedinUrlYes
portfolioUrlportfolioportfolioUrlYes
biographydescriptionbiographyYes
profileImageUrlprofileImageUrlprofileImageUrlYes (stripped of base URL)
gendergendergenderYes
dateOfBirthbirthDatedateOfBirthYes (ISO date)
willingToRelocatewillingToRelocatewillingToRelocateYes
preferredLocationpreferredLocationpreferredLocationYes
addressStreetaddressStreetaddressStreetYes
addressCityaddressCityaddressCityYes
addressStateaddressStateaddressStateYes
addressCountryaddressCountryaddressCountryYes
addressZipCodeaddressZipCodeaddressZipCodeYes
currentRolecurrentRolecurrentRoleYes
industriesindustriesindustriesYes (as [] not null — Prisma constraint)
specializationsspecializationsspecializationsYes (as [] not null)
preferredLanguagepreferredLanguagepreferredLanguageYes
yearsOfExperienceyearsOfExperienceyearsOfExperienceYes
fullNameComputed field, not sent

CvJobPreference → ProfileForm

Backend FieldForm FieldSent?
desiredJobTitlespreferredTitlesYes
preferredJobTypespreferredJobTypesYes
preferredWorkSitepreferredWorkSiteYes
availableInavailableInYes
expectedSalaryexpectedSalaryYes
salaryCurrencysalaryCurrencyYes
salaryFrequencysalaryFrequencyYes

CvWorkExperience → Experience

Backend FieldForm FieldSent to Mutation?
jobTitletitle (form) / jobTitle (qualifications)Yes
companyNamecompanyYes
startDatestartDateYes
endDateendDateYes
locationlocationYes
descriptiondescriptionYes
technologiestechnicalSkillsYes
isCurrentisCurrentYes
achievementsachievementsYes
durationdurationYes
orderDrag-and-drop positionVia reorder

CvEducation → Education

Backend FieldForm FieldSent to Mutation?
degreetitleYes
fieldOfStudyfieldOfStudyYes
institutioninstituteYes
graduationYearendDate (parsed as int)Yes
locationlocationYes
gpagpaYes
honorshonorsYes
relevantCourseworkrelevantCourseworkYes
startDateNOT IN BACKEND (see Backend Requirements)

CvCertification → Certificate

Backend FieldForm FieldSent to Mutation?
nametitleYes
issuingOrganizationinstituteYes
issueDateyearYes
expirationDateexpirationDateYes
credentialIdcredentialIdYes
verificationUrlverificationUrlYes
statusstatusYes

Note: location was removed from Certificate — the backend CvCertification type has no location field.

CvLanguage → LanguageItem

Backend FieldForm FieldSent to Mutation?
languagenameYes
proficiencylevel (0-3 number)Yes (converted to string)
certificationcertificationYes

CVSkill → SkillRequirementInput

Backend FieldForm FieldSent to Mutation?
skill.namenameYes (as skillName)
proficiencylevel (0-4 number)Yes (converted to enum string)
yearsExperienceyearsExperienceYes

Sections with No Form UI

SectionFieldsStatus
CvProject9 fieldsMutations exist, zero form support
CvPublication8 fieldsMutations exist, zero form support
CvAward6 fieldsMutations exist, zero form support
CvVolunteer7 fieldsMutations exist, zero form support
CvMetadata12 fieldsRead-only parsing metadata, no form

4. Edit Guards (Company Side)

Company users can only edit talent/candidate CVs when:

  1. Talent source === "MANUAL_ENTRY" (company added this talent manually)
  2. Talent owner isOnboarding !== true (user hasn't completed onboarding yet)

Once the CV owner completes onboarding, they own their profile data — the company can no longer edit it.

Implementation

ComponentGuard
TalentDetails.tsxcanEdit computed from source + isOnboarding
CandidateDetails.tsxSame logic, checks resumeMode for talent vs candidate source
talents/page.tsxList view edit action checks source + isOnboarding
Both detail componentsEdit button hidden, inline edit handlers set to undefined

Why "My" Mutations Fail for Company Users

The backend structured CV mutations (addMyCvEducation, updateMyCvProfile, etc.) check ownership: cv.userId === currentUser.id. Company users don't own the talent's CV, so these mutations return "CV not found".

Only 3 company-scoped mutations exist: addCvSkill, updateCvSkill, removeCvSkill (use TalentAuthorizationService.checkWriteAccessToCV()).


5. Onboarding Pre-fill for Invited Talents

Flow

  1. Company adds talent with CV → talent record + activeCV created
  2. Company clicks "Invite to Register" → password reset email sent
  3. Talent sets password → logs in → isOnboarding === false, activeCvId exists
  4. AuthGuard detects activeCvId → sets onboardingRole = "jobseeker" → redirects to /onboarding/jobseeker (skips role selection)
  5. Onboarding starts at Step 2 (Profile) with all fields pre-filled from active CV
  6. Talent reviews/edits → completes onboarding → isOnboarding = true

Pre-filled Fields

  • Full CV fetched via GET_CV_BY_ID with CV_STRUCTURED_FIELDS
  • Profile: name, title, phone, location, bio, links, image, gender, DOB
  • Job preferences: salary, currency, frequency, availability, job types, work site
  • Work experiences: all entries
  • Education: all entries
  • Certifications: all entries
  • Languages: all entries
  • Skills: all entries from CVSkill join table

Files Changed

  • components/auth/authGuard.tsx — auto-route when activeCvId exists
  • app/onboarding/jobseeker/page.tsxGET_CV_BY_ID query + pre-fill effect + start at step 2

6. Skill Selector UX

Main Skills Selector (SkillsSelector.tsx)

Before: Already-selected skills were hidden from dropdown.

After:

  • Already-selected skills appear in dropdown as: "React · Advanced ✓"
  • Clicking one scrolls to and highlights the skill row (2-second primary color pulse)
  • User can then adjust level with +/- buttons on the highlighted row

Experience Technical Skills (ExperienceCard.tsx, AddExperienceCard.tsx)

Before: No filtering — same skill could be added multiple times. No search filtering in dropdown.

After:

  • Already-added skills show as "React ✓" with disabled: true
  • Disabled options are greyed out (50% opacity) and unclickable
  • Added searchable prop — typing now filters the dropdown

TWSelect Disabled Option Support (TWSelect.tsx, TWSharedStyles.tsx)

Added disabled?: boolean to the Option interface. Disabled options:

  • Render at 50% opacity with cursor-default
  • Don't respond to click, hover, or keyboard Enter
  • Work in both normal and virtualized (react-window) rendering paths

7. Backend Requirements

Company-Scoped Mutations Needed

For company users to edit talent CVs through the frontend, the backend needs company-scoped variants of all structured CV mutations:

Mutation NeededSame AsAccess Check
addCvWorkExperienceaddMyCvWorkExperienceTalentAuthorizationService.checkWriteAccessToCV()
updateCvWorkExperienceupdateMyCvWorkExperienceSame
removeCvWorkExperienceremoveMyCvWorkExperienceSame
addCvEducationaddMyCvEducationSame
updateCvEducationupdateMyCvEducationSame
removeCvEducationremoveMyCvEducationSame
addCvCertificationaddMyCvCertificationSame
updateCvCertificationupdateMyCvCertificationSame
removeCvCertificationremoveMyCvCertificationSame
addCvLanguageaddMyCvLanguageSame
updateCvLanguageupdateMyCvLanguageSame
removeCvLanguageremoveMyCvLanguageSame
updateCvProfileupdateMyCvProfileSame
updateCvJobPreferenceupdateMyCvJobPreferenceSame

These follow the exact pattern of existing company-scoped skill mutations (addCvSkill, updateCvSkill, removeCvSkill).

CvEducation Date Fields Needed

Field NeededTypeInput TypesReason
startDateDateTimeCreateCvEducationInput, UpdateCvEducationInputFrontend has start date picker but can't persist
endDateDateTimeSameCurrently uses graduationYear: Int which loses month precision

8. Files Changed

GraphQL

  • graphql/operations/schema/cv/fragments.ts — Added addressStreet, addressZipCode, preferredLanguage to CvProfile fragment
  • graphql/operations/talents/queries.ts — Added isOnboarding to GET_TALENT and GET_COMPANY_TALENTS
  • graphql/operations/candidate/queries.ts — Added isActive, isOnboarding to GET_CANDIDATE_DETAILED

Types

  • lib/types/structuredCv.ts — Added addressStreet, addressZipCode to StructuredCvProfile
  • lib/types/structuredCvInputs.ts — Added addressStreet, addressZipCode, preferredLanguage to UpdateCvProfileInput
  • lib/types/cvbuilder.ts — Added all missing fields to Experience, Education, Certificate, ProfileFormData
  • lib/types/qualifications.ts — Added all missing fields to Experience, Education, Certificate
  • components/reusable/jobs/LanguagesSelectorWrapper.tsx — Added certification to LanguageItem
  • components/ux/TWSharedStyles.tsx — Added disabled to Option interface

Mappers

  • lib/utils/cv/structuredCvToProfileForm.ts — Maps all new fields from structured CV to form
  • lib/utils/cv/profileFormToInputs.ts — Sends all new fields to mutations
  • lib/utils/cv/structuredCvMapper.ts — Includes all new fields in qualifications converter
  • app/company/resume/candidates/_components/utils/dataTransformers.ts — Fixed cvSkills, jobTitle, removed cert location

Components

  • app/company/resume/talents/_components/TalentDetails.tsx — Structured CV qualifications, edit guard
  • app/company/resume/candidates/_components/CandidateDetails.tsx — Edit guard
  • app/company/resume/talents/page.tsx — List edit guard
  • app/company/resume/candidates/_components/utils/useInlineResumeEdit.ts — Pass-through new fields
  • app/jobseeker/resume/page.tsx — Removed broken optimistic response
  • app/onboarding/jobseeker/page.tsx — Pre-fill from active CV, skip upload step
  • components/auth/authGuard.tsx — Auto-route invited talents
  • components/reusable/jobs/SkillsSelector.tsx — Selected skill UX
  • components/reusable/qualifications/ExperienceCard.tsx — Skill dedup + searchable
  • components/reusable/qualifications/AddExperinceCard.tsx — Same
  • components/reusable/qualifications/CertificateCard.tsx — Removed location
  • components/reusable/qualifications/AddCertificationCard.tsx — Removed location
  • components/cv/ProfileForm.tsx — Removed cert location mapping
  • components/ux/TWSelect.tsx — Disabled option support

Cleanup (removed cert location across)

  • lib/export/profileFormToExportable.ts
  • lib/hooks/useAddCandidate.ts
  • lib/utils/cv/cvMapper.ts
  • lib/utils/userProfileInputs.ts
  • app/onboarding/jobseeker/steps/SummaryStep.tsx

Tests

  • e2e/cv-field-mapping.spec.ts — 16 Playwright tests across 5 suites

9. E2E Test Coverage

Run: npx playwright test e2e/cv-field-mapping.spec.ts

SuiteTestsWhat It Verifies
Company Talent5List loads, detail shows profile, resume sections visible, sidebar salary, edit form populated
Company Candidate3List loads, detail shows structured CV, edit form populated
Jobseeker CV Builder4Resume list, preview sections, edit form all fields, save roundtrip
Cross-View Consistency2Same talent shows consistent data in detail vs edit form
Field Mapping Audit1Logs complete Schema→Form→Mutation→Display matrix for all 12 types