193 lines
12 KiB
Markdown
193 lines
12 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.
|
||
|
||
**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.
|