fix(nav): only the most-specific sidebar item is active
On /career/builder/{id} both "Career Workspace" (/career) and "CV Builder"
(/career/builder) highlighted, because AppShell tested each item with
`pathname === to || pathname.startsWith(to + "/")` — so /career matched
every /career/... child. No "most specific wins" rule.
Add AppShell.activeNavTo(pathname, tos): the longest `to` that the path is
at or under wins, across both nav lists; every other item is inactive. A
child route never lights up a parent nav item. `selected` now compares
against that single computed activeTo. Exported as a pure function so the
ownership rule is unit-tested directly (sidebar-active-nav.test.ts):
exactly one active item for /career, /career/builder and
/career/builder/{id}, and no double-highlight.
Also give the breadcrumb/title in App.tsx explicit /career/builder ->
"CV Builder" ownership (it previously showed "Career Workspace"), and
reframe the Career Workspace header to the "Career Profile" product
framing: "This information powers your CVs, applications, cover letters
and AI assistance."
Frontend only — no change to CareerProfiles, CvVariants, CV generation,
extraction APIs, AI, permissions or tenant isolation. Plan for the deeper
information-architecture work is in docs/career-workspace-ux-refactor.md,
staged so the 1376-line CareerProfilePage and the live CV/extraction
pipeline are refactored incrementally with verification, not in one risky
rewrite.
Verified: tsc clean, frontend build clean, 135 frontend tests pass
(128 + 7 new nav tests). Backend untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# 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 — Career Profile editor sections (frontend)
|
||||
Reorganise `CareerProfilePage` presentation into the eight named sections with user-facing labels;
|
||||
strip internal vocabulary from headings/help. No data-model or API change — the same
|
||||
`StructuredCvProfile` shape is read and saved. Ship behind the existing page; verify save/load of each
|
||||
section against the existing profile API.
|
||||
|
||||
### 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.
|
||||
Reference in New Issue
Block a user