From 9064b3e90c0c72a122fa8ed1d66b4480552f98f1 Mon Sep 17 00:00:00 2001 From: Mark Dumay <61946753+markdumay@users.noreply.github.com> Date: Wed, 5 Aug 2026 07:04:18 +0200 Subject: [PATCH 1/3] fix(sidebar): highlight nearest ancestor item The sidebar markup is rendered once per section and cached, so the active item is resolved client side. Matching only considered links flagged `data-sidebar-match="prefix"`, which the L1-only mode emits and the full-tree mode never does. A page absent from the menu therefore matched nothing at all: no active item, every group collapsed. Pages built with `build.list = never`, or generated by a content adapter, are excluded from the page collections the menu is built from, so they never have an entry of their own and always hit this path. Fall back to the longest link that is a path ancestor of the current page whenever no link matches exactly. Ancestor matching compares against `path + "/"`, so `/docs/guides` cannot claim `/docs/guides-advanced/`. The brand logo is excluded from the candidates: it sits inside the nav and points at the site home, so it would otherwise match every page below it. Co-Authored-By: Claude Opus 5 (1M context) --- assets/js/critical/sidebar-active.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/assets/js/critical/sidebar-active.js b/assets/js/critical/sidebar-active.js index 42129a3e1..4bf86df79 100644 --- a/assets/js/critical/sidebar-active.js +++ b/assets/js/critical/sidebar-active.js @@ -2,6 +2,19 @@ // (section, version, language, variant) and cached, so the server no longer emits per-page // `active` classes or an expanded collapse trail; this script applies them from the current // location. It runs in the critical bundle so the highlight lands before first paint. +// +// Matching is exact first, nearest-ancestor second: a link whose path equals the current +// path always wins, and only when no link matches exactly does the longest link that is a +// path ancestor of the current page take over (flagged `aria-current="true"` rather than +// "page"). The ancestor pass is what keeps the trail highlighted for pages the menu cannot +// contain - a page built with `build.list = never`, or one generated by a content adapter, +// is absent from the page collections the menu is built from, so it has no entry of its own. +// Without the fallback nothing matched at all and the sidebar rendered with no active item +// and every group collapsed. Ancestor matching compares against `path + "/"`, so +// `/docs/guides` never claims `/docs/guides-advanced/`. +// +// The `data-sidebar-match="prefix"` attribute that assets/sidebar.html still emits on group +// links predates this and is now redundant: every link is an ancestor candidate. (function () { 'use strict' @@ -15,7 +28,10 @@ var best = null var bestLength = -1 var bestExact = false - nav.querySelectorAll('a[href]').forEach(function (link) { + // The brand logo is inside the