63473bae85
UI-only restructuring of the Career Profile surface. No change to
database models, CareerProfiles schema, CvVariants, extraction APIs, AI
services, CV rendering, or public CV.
Terminology -> user-facing (i18n strings):
- "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 frames it as the
source the CV Builder consumes.
Component split (first step): extract ProfileCompleteness (completeness
meter + missing chips + version history) into src/views/career/. Display
only, props in, no state or API.
Save path untouched: api.put("/career/profile", { profile, cvText }). A
new test pins that exact call as the refactor invariant so the remaining
section extraction cannot silently change save behaviour. Existing
profile-page tests re-pointed to the new labels; every behavioural
assertion (save, parse, field values) kept.
Verified: tsc clean, production build clean, 136 frontend tests pass
(135 + 1 invariant). Sidebar fix from the previous task still passes.
Backend untouched.
The remaining Phase 1 work (per-section editor components, hiding the
template-driven builder and structure-overview blocks, actionable
per-section empty states) is staged in docs/career-workspace-ux-refactor.md
because it touches the live extraction test surface and is best verified
by driving the authenticated UI. This increment is a clean, non-regressing
checkpoint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
9.3 KiB
Markdown
151 lines
9.3 KiB
Markdown
# 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.
|
||
|
||
**Remaining in Phase 1 (staged, needs the app running to click-verify each section's save
|
||
round-trip):**
|
||
- Extract the editing sections into `PersonalInformationSection … LanguagesSection` components and a
|
||
`CareerProfileHeader`, keeping `structuredCv` + `setStructuredCv` + the save handler in the parent
|
||
(so behaviour stays identical). This is voluminous mechanical JSX movement through a 1376-line file.
|
||
- Hide the **template-driven CV builder** (rewrite templates + PDF carousel) and the **structure
|
||
overview** parse block from the user surface. Both are tested against live extraction behaviour, so
|
||
each removal must move its test coverage, not delete it — done incrementally with verification.
|
||
- Per-section actionable empty states ("No work experience added yet" → [Add experience]); the Add
|
||
affordances already exist, so this is copy + wiring.
|
||
|
||
Rationale for staging: these touch the live CV/extraction test surface and are best verified by
|
||
driving the authenticated UI. Increment 1 is a clean, non-regressing checkpoint per the "reviewed and
|
||
verified before Phase 2" instruction.
|
||
|
||
### Phase 2 — Import CV review screen
|
||
Add a post-upload "New information found" review (Experience / Skills / Languages / Education) with
|
||
`[Accept all] · [Review individually] · [Discard]`, diffing the extracted profile against the current
|
||
one **client-side** (no backend change — the extraction API already returns the structured result and
|
||
field metadata). Nothing is written until the user accepts. Relocate run history to Settings →
|
||
Advanced → Import history.
|
||
|
||
### Phase 3 — Remove the second CV builder
|
||
Delete the rewrite-template + PDF-carousel flow from `CareerProfilePage`. Confirm the CV Builder
|
||
(`/career/builder`) covers templates/layout/styling/variants/PDF first (it does). Verify public CV and
|
||
PDF generation still work end to end.
|
||
|
||
### Phase 4 — WYSIWYG for long-form fields
|
||
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.
|