Skip to content

Fix nested-route nav highlighting, frozen footer year, missing themeColor, keyboard-unreachable dropdown - #323

Merged
chonilius merged 4 commits into
MergeFi:mainfrom
miraclesonly:fix/nav-prefix-highlight-footer-year-theme-color-dashboards-keyboard
Aug 25, 2026
Merged

Fix nested-route nav highlighting, frozen footer year, missing themeColor, keyboard-unreachable dropdown#323
chonilius merged 4 commits into
MergeFi:mainfrom
miraclesonly:fix/nav-prefix-highlight-footer-year-theme-color-dashboards-keyboard

Conversation

@miraclesonly

Copy link
Copy Markdown
Contributor

Summary

Four fixes across navigation, layout metadata, and accessibility:

Nested routes lose sidebar highlighting (#220) — active-nav detection used exact pathname === item.href equality. Navigating from the pipeline board into a specific bounty (/issues/abc123) made pathname !== "/issues", so "Bounty pipeline" lost its highlighted state exactly while the user was still conceptually inside that section — the sidebar appeared to have nothing selected. Added a prefix check for non-overview items so nested routes keep their parent section highlighted, while /dashboard/* overview links keep exact matching.

Frozen copyright year on static builds (#221)Footer.tsx computed new Date().getFullYear() directly in a Server Component with no dynamic API usage. On any route Next.js could statically optimize, that call evaluated once at build time, not per-request — the year could silently freeze for the rest of that build's lifetime. Extracted it into a new CopyrightYear Client Component whose useEffect always runs client-side on every hydration, correcting a stale build-time year the moment any visitor's page hydrates.

No themeColor viewport metadata (#224)viewport only set colorScheme, which doesn't control the mobile browser's own chrome (address/status bar). Neither the app's dark (#0a0a0f) nor light (#fbfbfd) background was ever passed as themeColor, so mobile browsers fell back to a default (usually white) that visibly clashes with the page, especially in dark mode. Added a themeColor array matching globals.css's existing prefers-color-scheme split.

Keyboard-unreachable Dashboards dropdown (#222) — the submenu was CSS-only hover (group-hover:visible group-hover:opacity-100) with no focus/focus-within variant, no onClick, and no aria-haspopup/aria-expanded. A keyboard user tabbing to the trigger never made the submenu visible, and the three links inside stayed in the tab order while invisible — worse than being skipped, since focus could land somewhere a keyboard user couldn't see. Added group-focus-within:visible group-focus-within:opacity-100 alongside the hover variant, plus aria-haspopup="menu".

Changes

  • src/components/dashboard/DashboardShell.tsx — prefix-based active-nav check.
  • src/components/layout/Footer.tsx / new CopyrightYear.tsx — self-correcting year.
  • src/app/layout.tsxthemeColor added to viewport.
  • src/components/layout/Navbar.tsxfocus-within + aria-haspopup on the dropdown.

Test plan

  • npx tsc --noEmit — clean
  • npx eslint on all changed files — clean
  • npx jest (full suite) — 207/207 passing, no regressions

Closes #220
Closes #221
Closes #224
Closes #222

Active-nav-item detection used exact pathname equality
(pathname === item.href). A maintainer navigating from the pipeline
board into a specific bounty (/issues/abc123, reached via
PipelineBoard.tsx's own links) made pathname !== "/issues", so
"Bounty pipeline" lost its highlighted state exactly while the user
was drilled into a page that's conceptually still part of that
section — the sidebar appeared to have nothing selected, a common and
expected navigation state this exact-match check didn't account for.

Added a prefix check for non-overview items (anything not under
/dashboard/*) so a nested route like /issues/abc123 keeps its parent
section highlighted, while /dashboard/* overview links keep exact
matching so they don't stay highlighted while browsing unrelated
sections (closes MergeFi#220).
…mized builds

Footer.tsx computed new Date().getFullYear() directly in a Server
Component with no dynamic API usage of its own. Next.js statically
optimizes any route it can prove has no per-request dynamic data, so
that call was evaluated once, at build time, not per-request, on any
route Next was able to optimize this way — nothing in Footer.tsx
declared force-dynamic or otherwise opted into per-request evaluation,
so the correctness of the displayed year was incidentally dependent on
unrelated routes' fetch behavior. On a statically-optimized route, the
year silently froze at whatever `next build` was run and stayed wrong
for the rest of that build's lifetime, with nothing surfacing the
staleness.

Extracted the year into a new CopyrightYear Client Component: the
initial render still uses whatever Date.now() was available at
build/SSR time, but a useEffect always runs client-side on every
hydration and re-reads the browser's actual current date, correcting
any stale build-time year the moment a visitor's page hydrates rather
than waiting for the next deploy (closes MergeFi#221).
…kgrounds

viewport only set colorScheme, which controls native UI elements
(scrollbars, form controls) but not the mobile browser's own chrome —
the address/status bar background on iOS Safari and Android Chrome.
Neither this app's dark background (#0a0a0f) nor its light background
(#fbfbfd, both from globals.css) was ever passed as themeColor, so
mobile browsers fell back to their own default (usually white),
visibly clashing with the page the instant it loads — especially in
dark mode, where a white/default chrome bar sits directly above a
near-black page.

Added a themeColor array mirroring the same prefers-color-scheme split
already used in globals.css (closes MergeFi#224).
The "Dashboards" submenu was a CSS-only hover pattern
(group-hover:visible group-hover:opacity-100) with no focus/
focus-within variant anywhere, no onClick on the trigger, and no
aria-expanded/aria-haspopup. A keyboard user tabbing to the "Dashboards"
button never made the submenu visible — Tab skipped straight past it —
and the three links inside remained in the tab order (invisible/
opacity-0, not display: none) rather than being removed from it, so
focus could land on a link a keyboard user couldn't see was there at
all, worse than being skipped entirely.

Added group-focus-within:visible group-focus-within:opacity-100
alongside the existing hover variant, so focus landing anywhere inside
the wrapper (the trigger, or one of the links themselves once tabbed
to) keeps the menu open the same way hover does. Also added
aria-haspopup="menu" to the trigger for correct semantics (closes MergeFi#222).
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

@miraclesonly is attempting to deploy a commit to the chonilius' projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Aug 25, 2026

Copy link
Copy Markdown

@miraclesonly Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment