4f69d395be
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>
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
import { activeNavTo } from "./layout/AppShell";
|
|
|
|
// Route ownership under test (the real sidebar entries):
|
|
// /career -> Career Workspace
|
|
// /career/builder -> CV Builder
|
|
// A child route must activate exactly one nav item — the most specific owner.
|
|
const TOS = ["/dashboard", "/jobs", "/career", "/career/builder", "/settings"];
|
|
|
|
describe("sidebar active nav ownership", () => {
|
|
test("/career activates Career Workspace only", () => {
|
|
expect(activeNavTo("/career", TOS)).toBe("/career");
|
|
});
|
|
|
|
test("/career/builder activates CV Builder only, not Career Workspace", () => {
|
|
expect(activeNavTo("/career/builder", TOS)).toBe("/career/builder");
|
|
});
|
|
|
|
test("/career/builder/{id} activates CV Builder only (the reported bug)", () => {
|
|
// Previously /career matched via startsWith('/career/') AND /career/builder matched — both lit up.
|
|
expect(activeNavTo("/career/builder/42", TOS)).toBe("/career/builder");
|
|
});
|
|
|
|
test("a plain child of /career (not /builder) still belongs to Career Workspace", () => {
|
|
expect(activeNavTo("/career/anything-else", TOS)).toBe("/career");
|
|
});
|
|
|
|
test("unrelated routes are unaffected and exact matches win", () => {
|
|
expect(activeNavTo("/jobs", TOS)).toBe("/jobs");
|
|
expect(activeNavTo("/settings/connected-accounts", TOS)).toBe("/settings");
|
|
});
|
|
|
|
test("a path owned by no nav item activates nothing", () => {
|
|
expect(activeNavTo("/admin/system", TOS)).toBeNull();
|
|
});
|
|
|
|
test("exactly one item is ever active (no double highlight)", () => {
|
|
for (const path of ["/career", "/career/builder", "/career/builder/7", "/jobs"]) {
|
|
const active = activeNavTo(path, TOS);
|
|
const matches = TOS.filter((t) => t === active);
|
|
expect(matches).toHaveLength(1);
|
|
}
|
|
});
|
|
});
|