# Career Workspace + CV Builder UX refactor

> 2026-07-20. UX/product restructuring against v1.0.0. **Frontend-only** — no change to
> CareerProfiles, CvVariants, the CV generation pipeline, extraction APIs, AI services, permissions,
> or tenant isolation.
>
> This document is the plan. It is delivered **staged**: Phase 0 (the sidebar bug and the framing
> copy) is implemented and tested now; Phases 1–4 are scoped for incremental delivery because they
> require surgery on the 1376-line `CareerProfilePage` and touch the production CV/extraction flow,
> where a single large rewrite would risk the "do not break" list. Each later phase is independently
> shippable and verifiable.

## Current problems

1. **Sidebar double-highlight.** On `/career/builder/{id}`, both "Career Workspace" and "CV Builder"
   light up. Root cause: `AppShell` used `pathname === to || pathname.startsWith(to + "/")` per item,
   so `/career` matched every `/career/...` child. No "most specific wins" rule. *(Fixed — Phase 0.)*
2. **Too many competing concepts on one page.** `CareerWorkspacePage` is a thin shell around
   `CareerProfilePage` (1376 lines), which bundles: a structured-CV editor, extraction-run history, a
   "CV structure overview", rewrite **templates** with a PDF carousel (a second, template-driven CV
   builder), and field-level review metadata. A user cannot tell which artifact is "their CV".
3. **Internal vocabulary leaks to users** — "structured CV", "JSON structure", "extraction schema",
   "structure overview". These are implementation concepts.
4. **Extraction is opaque.** Upload runs, the profile changes, but the user never sees *what* changed
   and cannot approve it. History is shown as a primary section instead of the *result* of an import.
5. **Two CV builders.** The template-driven rewrite/PDF flow inside the profile page overlaps the real
   CV Builder (`/career/builder`), which already owns templates, layout, styling, variants and PDF.

## New information architecture

**Product rule:** *Career Profile* holds your information; *CV Builder* creates documents from it.

| Surface | Owns | Does NOT own |
|---|---|---|
| **Career Profile** (`/career`) | Personal info, professional summary, work experience, education, skills, projects, certifications, languages. The facts. | Templates, layout, styling, PDF, variants |
| **CV Builder** (`/career/builder`, `/career/builder/{id}`) | Templates, layout, styling, section order/visibility, variants, PDF generation | Career facts (it *reads* the profile) |

Career Profile page structure (target):

- **Header** — "Career Profile", subtitle *"This information powers your CVs, applications, cover
  letters and AI assistance."*, profile completeness %, last updated, quick action → CV Builder.
- **Sections** (user-facing labels only): Personal information · Professional summary · Work
  experience · Education · Skills · Projects · Certifications · Languages.
- **Import CV** — current source (filename, date, status) + `[Upload new CV]`; after extraction a
  review screen (below). History moves to Settings → Advanced → Import history.

## Removed / relocated concepts

| Concept | Disposition |
|---|---|
| "Structured CV Editor" | Renamed and reframed to **Career Profile editor** (same fields, user vocabulary) |
| "CV Structure Overview" | Removed from the user surface; if needed for debugging, move under admin/developer tools |
| "Template-driven CV Builder" (rewrite templates + PDF carousel inside the profile page) | **Removed** — the CV Builder already provides templates, layouts, styling, sections, customization and PDF. One CV Builder only |
| Extraction run history as a primary section | **Relocated** to Settings → Advanced → Import history |
| Internal terms ("structured CV", "JSON", "schema") in labels/help text | Replaced with plain language |

## Route ownership (authoritative)

```
/career                 → Career Profile        (Career Workspace nav item)
/career/builder         → CV Builder            (CV Builder nav item)
/career/builder/{id}    → CV Builder            (child of CV Builder, NOT Career Profile)
```

Rule: the sidebar item whose `to` is the **longest prefix** the current path is at or under wins;
all others are inactive. A child route never activates a parent nav item.

## Implementation plan

### Phase 0 — Sidebar bug + framing (DONE, this change)
- `AppShell.activeNavTo(pathname, tos)` — exported pure function; longest-owning `to` wins. `selected`
  now compares against the single computed `activeTo` across both nav lists.
- Breadcrumb/title in `App.tsx`: explicit `/career/builder` → "CV Builder" ownership before the
  `/career` fallback (previously `/career/builder` showed "Career Workspace").
- Career Workspace header reframed to the "Career Profile" product framing.
- Tests: `sidebar-active-nav.test.ts` — asserts exactly one active item for `/career`,
  `/career/builder`, `/career/builder/{id}`, and that no item double-highlights.

### Phase 1 — terminology + first component split (IN PROGRESS)

**Delivered 2026-07-20 (increment 1):**

*Terminology → user-facing* (`src/i18n/translations.ts`, no structural change):
| Internal term (before) | User-facing (after) |
|---|---|
| "Structured CV editor" | "Career information" |
| "CV structure overview" | "Profile sections" |
| "Summary bullets" | "Professional summary" |
| "Core skills" | "Skills" |
| "Analyze sections" | "Read sections" |
| "Original extraction" | "Original import" |
| hardcoded "Master career profile" | "Career profile" |
Help text de-jargoned; the "Career information" help now says *"The CV Builder uses this information
to create documents."*

*Component extracted:* `src/views/career/ProfileCompleteness.tsx` — the completeness meter + missing
chips + version-history accordion, pulled out of `CareerProfilePage`. Display-only, props in, no state
or API — the first step of the split.

*No API / data / model change.* The save path is untouched:
`api.put("/career/profile", { profile: structuredCv, cvText })`. A new test
(`profile-page.test.tsx` → "saving the career profile PUTs … unchanged (Phase 1 refactor invariant)")
pins exactly that call so the remaining extraction can't silently change it. Existing profile-page
tests were re-pointed to the new labels; all behavioural assertions (save, parse, field values) kept.

*Verified:* tsc clean, production build clean, 136 frontend tests pass (was 135; +1 invariant test).
Sidebar fix from the previous task still passes.

**Delivered 2026-07-20 (increment 2 — COMPLETED):**

*Sections extracted* into `src/views/career/CareerProfileSections.tsx` (one file, one component per
export), plus `FieldReviewNote` + `confidenceTone` moved there verbatim and shared with the parent:

| Component | Slice | Props |
|---|---|---|
| `PersonalInformationSection` | `contact` | `value`, `onChange`, `getMetadata` |
| `ProfessionalSummarySection` | `summary` | `value`, `onChange`, `getMetadata` |
| `SkillsSection` | `skills` | `value`, `onChange`, `getMetadata` |
| `InterestsSection` | `interests` | `value`, `onChange`, `getMetadata` |
| `LanguagesSection` | `languages` | `value`, `onChange`, `getMetadata` |
| `WorkExperienceSection` | `jobs` | `value`, `onChange` |
| `EducationSection` | `education` | `value`, `onChange` |
| `OtherSectionsSection` | `otherSections` | `value`, `onChange` |

Each section is presentational: it receives its slice + `onChange(next)`; the parent still holds
`structuredCv`, loads, saves, and owns every extraction/import action. `getMetadata` is a callback
(`getStructuredCvFieldMetadata` over the whole profile) so sections stay decoupled from the full shape.
No section makes an API call. `CareerProfilePage` dropped from 1376 → ~1200 lines.

**No `ProjectsSection` / `CertificationsSection` were created** — the editor never had those sections
(`StructuredCvProfile` has no editable projects/certifications UI here; such content lives in "Other
sections"). Inventing them would add functionality, which this refactor explicitly avoids. Flagged for
a product decision in a later phase.

*Duplicate concepts hidden* (not deleted) behind an **"Advanced CV tools"** toggle on the Career
Profile page, collapsed by default:
- **CV Structure Overview** ("Profile sections" parse block)
- **Template-driven CV Builder** (rewrite templates + PDF carousel)

Both stay mounted and fully functional — gated with `display: none` via `showAdvancedCvTools`, so the
underlying flows (and their tests) are intact and reachable, just out of the default workflow. The
real CV Builder at `/career/builder` is the single CV-generation surface.

*Tests* (all green): existing profile-page tests re-pointed to reveal advanced tools before touching
those controls; added "editing a field in an extracted section updates parent state and flows into
save" (proves render → edit → `PUT /career/profile { profile, cvText }`). The save-invariant test from
increment 1 still pins the exact payload.

*Verified:* tsc clean, production build clean, **137 frontend tests pass**. No API, save-payload,
extraction, or data-model change.

**Future removal plan (later phase, not now):** once Phase 2 (Import CV review) and the real CV
Builder cover every flow the hidden blocks serve, delete the structure-overview parse block and the
template-driven builder from `CareerProfilePage` entirely, and drop the `showAdvancedCvTools` toggle.
Until then they remain behind the toggle so no tested functionality is lost.

**Still open for Phase 1 polish (optional, low priority):** a dedicated `CareerProfileHeader`
component and per-section actionable empty-state copy ("No work experience added yet" → the Add button
already present). Neither is blocking; both are cosmetic.

### Phase 2 — Import CV review screen (DONE 2026-07-30)
Extraction runs now stop at `pending_review`; they no longer overwrite the career profile. A backend diff
and conservative merge preserve curated/unmatched data, stable item IDs and non-empty values. The
Career Profile shows additions/updates by category with Apply/Discard actions. Low-confidence changes
have stable change IDs, are excluded by default in the API, and require individual checkbox confirmation.
Projects, certifications, languages-from-prose, grouped skill prefixes, glued date/title text, common
mojibake, and nested earlier/part-time roles are covered by regression tests. Part-time roles are separate
experience entries. No schema migration was required; pending profiles already fit `CvExtractionRun`.

### Phase 3 — Remove the second CV builder (DONE 2026-07-30)
The hidden template/rewrite/PDF-carousel block and all of its state, helpers, saved-job fetch, preview
dialog, and obsolete tests were deleted from `CareerProfilePage`. Career Profile now links directly to
`/career/builder`, the single owner of templates, layout, variants, previews and PDF generation. The
raw import section parser remains under Advanced CV tools as a recovery path.

### Phase 4 — WYSIWYG for long-form fields (DONE 2026-07-30)
Reused the existing dependency-free Markdown toolbar for the professional summary, work and education details, custom/project sections, and cover letters. Native textarea undo/redo remains available and stored payloads remain clean strings/string arrays.

Rich editor (bold, lists, links, undo/redo) for professional summary, work descriptions, achievements,
projects, cover letters. **Store clean HTML or Markdown only** — no editor-specific state — and the
renderer consumes the same format. Choose a small dependency already compatible with the stack, or a
minimal contentEditable wrapper; decide at Phase 4 to avoid a premature dependency.

## Preserved (verified not touched)
CareerProfiles, CvVariants, CV generation, public CV pages, extraction APIs, AI services, permissions,
tenant isolation. Phases 1–4 are frontend-only; any that appears to need a backend change is a signal
to re-scope, not to change the model.

## Why staged
The removals and the review/WYSIWYG surfaces all require editing the 1376-line `CareerProfilePage` and
the live extraction/generation path. Delivering them as one change would put the v1.0.0 CV pipeline at
risk with no incremental verification. Each phase above is small enough to ship and verify on its own.
