From a82783162b6dab43f7b7f925bf8c4af0f98dec80 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:20:52 +0200 Subject: [PATCH] fix(nav): the current-page highlight was wired end to end, then dropped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GlobalNavigation computes `currentView` from the path with mapPathToView() and passes it into TopNavigation as a prop — but TopNavigation declared the prop and never read it anywhere in its render. The "highlight the page you're on" feature existed on both ends and did nothing in between: neither a sighted user nor a screen reader could tell which top-level page was current. That is also why the nav-contract audit's grep flagged this file — the string `currentView` is present, computing an active state is the declared intent, and zero aria-current existed in the repo. Fixed with `usePathname()` directly in TopNavigation rather than finally consuming `currentView`: that mapping is coarser than the nav items it would need to match against (`ROUTES.forms` maps to the view key `'saved-forms'`, `Blog` isn't mapped at all and silently falls through to `'builder'`), so comparing against the live path is more correct, not just more available. Only the mobile menu's `navigation.map()` render was touched — it's the only place that array is rendered; the desktop nav renders its top links as separate inline JSX, a different item the fleet audit did not flag here. This does change what a user sees, not just what assistive tech hears: the active mobile nav item is now visibly highlighted, because a broken feature was actually restored rather than an attribute bolted onto nothing. The `currentView` prop is left unused where it already was — retiring the dead mapPathToView plumbing is a separate cleanup, not folded in here. Typecheck 0 errors (once contentlayer types are generated, per this repo's own CLAUDE.md — pre-existing prerequisite, unrelated to this change). Lint 0 errors, 2 pre-existing warnings (identical before/after). 46 tests pass. Found by the fleet nav-contract audit. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XeELB8b3N4JrT2asYL9WvE --- frontend/src/app/components/TopNavigation.tsx | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/components/TopNavigation.tsx b/frontend/src/app/components/TopNavigation.tsx index 5f6628a..417fe14 100644 --- a/frontend/src/app/components/TopNavigation.tsx +++ b/frontend/src/app/components/TopNavigation.tsx @@ -5,6 +5,7 @@ import { Dialog, Menu, Transition } from '@headlessui/react'; import { Fragment } from 'react'; import { XMarkIcon } from '@heroicons/react/24/outline'; import Link from 'next/link'; +import { usePathname } from 'next/navigation'; import Image from 'next/image'; import { useAuth } from '../context/AuthContext'; import { useBrandName } from '../../hooks/useBranding'; @@ -106,6 +107,15 @@ const navigation = [ export function TopNavigation({ currentView, onViewChange = () => {} }: TopNavigationProps) { const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + // GlobalNavigation computes `currentView` from the path and passes it down, + // but nothing here ever read it — the whole "highlight the current page" + // feature was wired end to end and then dropped on the floor, so neither a + // sighted user nor a screen reader could tell which top-level page they were + // on. `currentView`'s own mapping (mapPathToView) is coarser than the nav + // items — 'Forms' maps to the view key 'saved-forms', 'Blog' isn't mapped at + // all — so comparing against the live path directly, the way every other nav + // in the fleet does, is more robust than threading that mapping through. + const pathname = usePathname(); const { token, user, logout, loading } = useAuth(); // Use the auth context const displayName = (user?.name && user.name.trim()) || user?.email || ''; const displayInitial = displayName ? displayName.charAt(0).toUpperCase() : 'U'; @@ -468,16 +478,24 @@ export function TopNavigation({ currentView, onViewChange = () => {} }: TopNavig
- {navigation.map((item) => ( - setMobileMenuOpen(false)} - > - {item.name} - - ))} + {navigation.map((item) => { + const isActive = pathname === item.href || pathname.startsWith(`${item.href}/`); + return ( + setMobileMenuOpen(false)} + > + {item.name} + + ); + })}
{!loading && (