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:
cesnimda
2026-07-20 02:29:50 +02:00
parent 4db8c08958
commit 4f69d395be
5 changed files with 180 additions and 4 deletions
+2
View File
@@ -85,6 +85,7 @@ function breadcrumbsFor(path: string, t: (k: any) => string): string[] {
if (path.startsWith("/trash")) return [t("home"), t("trash")];
if (path.startsWith("/settings")) return [t("home"), t("settings")];
if (path.startsWith("/profile")) return [t("home"), t("account"), t("profile")];
if (path.startsWith("/career/builder")) return [t("home"), "Career Workspace", "CV Builder"];
if (path.startsWith("/career")) return [t("home"), "Career Workspace"];
if (path.startsWith("/settings/connected-accounts")) return [t("home"), t("settings"), "Connected accounts"];
if (path.startsWith("/admin/audit")) return [t("home"), t("admin"), t("auditLog")];
@@ -104,6 +105,7 @@ function titleFor(path: string, t: (k: any) => string): string {
if (path.startsWith("/trash")) return t("trash");
if (path.startsWith("/settings")) return t("settings");
if (path.startsWith("/profile")) return t("profile");
if (path.startsWith("/career/builder")) return "CV Builder";
if (path.startsWith("/career")) return "Career Workspace";
if (path.startsWith("/settings/connected-accounts")) return "Connected accounts";
if (path.startsWith("/admin/audit")) return t("auditLog");