From 154185eb706ffb04953cc23121623fe5655be1ae Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 10:25:23 +0000 Subject: [PATCH 1/3] Fix Aliki TOC highlighting and method group nesting The TOC highlight only changed when a heading crossed the viewport, so it went stale after long jumps and stayed empty when landing on a method link. Compute the current heading on scroll instead, falling back to the last heading above the viewport. Constants, Attributes and method group headings in the untitled section are now listed at the same TOC level as the class comment's sections, instead of nesting under its last heading. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Gxq9jGyjWoANhMZXRtssiu --- lib/rdoc/generator/template/aliki/js/aliki.js | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/lib/rdoc/generator/template/aliki/js/aliki.js b/lib/rdoc/generator/template/aliki/js/aliki.js index 2855cabb75..6776b9fa5f 100644 --- a/lib/rdoc/generator/template/aliki/js/aliki.js +++ b/lib/rdoc/generator/template/aliki/js/aliki.js @@ -267,8 +267,7 @@ function generateToc() { if (!heading.id) return; const li = document.createElement('li'); - const level = heading.tagName.toLowerCase(); - li.className = `toc-item toc-${level}`; + li.className = `toc-item toc-${tocLevel(heading)}`; const link = document.createElement('a'); link.href = `#${heading.id}`; @@ -292,6 +291,17 @@ function generateToc() { } } +// Constants, Attributes and method group headings are h3 so they nest under a +// titled section's h2. In the untitled section, list them as h2 so they don't +// look like children of the class comment's last heading. +function tocLevel(heading) { + const level = heading.tagName.toLowerCase(); + if (level !== 'h3' || !heading.matches('.documentation-section > section > header > h3')) return level; + + const section = heading.closest('.documentation-section'); + return section.querySelector(':scope > .documentation-section-title') ? level : 'h2'; +} + function hookTocActiveHighlighting() { const tocLinks = document.querySelectorAll('.toc-link'); const targetHeadings = []; @@ -305,28 +315,25 @@ function hookTocActiveHighlighting() { if (targetHeadings.length === 0) return; - const observerOptions = { - root: null, - rootMargin: '0% 0px -35% 0px', - threshold: 0 - }; - - const intersectingHeadings = new Set(); + let activeLink = null; const update = () => { - const firstIntersectingHeading = targetHeadings.find((heading) => { - return intersectingHeadings.has(heading); + // Prefer the first heading in the upper 65% of the viewport. With no + // heading there, such as deep inside a long method list, the current + // section is the last heading above the viewport. + const viewportLimit = window.innerHeight * 0.65; + const currentHeading = targetHeadings.find((heading) => { + const rect = heading.getBoundingClientRect(); + return rect.bottom > 0 && rect.top < viewportLimit; + }) || targetHeadings.findLast((heading) => { + return heading.getBoundingClientRect().top < 0; }); - if (!firstIntersectingHeading) return; - const correspondingLink = document.querySelector(`.toc-link[data-target="${firstIntersectingHeading.id}"]`); - if (!correspondingLink) return; + if (!currentHeading) return; + const correspondingLink = document.querySelector(`.toc-link[data-target="${currentHeading.id}"]`); + if (!correspondingLink || correspondingLink === activeLink) return; - // Remove active class from all links - tocLinks.forEach((link) => { - link.classList.remove('active'); - }); - - // Add active class to current link + activeLink?.classList.remove('active'); correspondingLink.classList.add('active'); + activeLink = correspondingLink; // Scroll link into view if needed const tocNav = document.querySelector('#toc-nav'); @@ -339,21 +346,18 @@ function hookTocActiveHighlighting() { } } }; - const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - intersectingHeadings.add(entry.target); - } else { - intersectingHeadings.delete(entry.target); - } + + let updateScheduled = false; + window.addEventListener('scroll', () => { + if (updateScheduled) return; + updateScheduled = true; + requestAnimationFrame(() => { + updateScheduled = false; + update(); }); - update(); - }, observerOptions); + }, { passive: true }); - // Observe all headings that have corresponding TOC links - targetHeadings.forEach((heading) => { - observer.observe(heading); - }); + update(); } function setHeadingScrollHandler(heading, link) { From 7d2b797d65787c71cd83f2befa8791f73236bdb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 11:21:08 +0000 Subject: [PATCH 2/3] Leave Aliki TOC nesting to the heading level change ruby/rdoc#1842 fixes the method group nesting by emitting h2 headings in the default section, so the TOC needs no special case for them. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Gxq9jGyjWoANhMZXRtssiu --- lib/rdoc/generator/template/aliki/js/aliki.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/rdoc/generator/template/aliki/js/aliki.js b/lib/rdoc/generator/template/aliki/js/aliki.js index 6776b9fa5f..20886c7cca 100644 --- a/lib/rdoc/generator/template/aliki/js/aliki.js +++ b/lib/rdoc/generator/template/aliki/js/aliki.js @@ -267,7 +267,8 @@ function generateToc() { if (!heading.id) return; const li = document.createElement('li'); - li.className = `toc-item toc-${tocLevel(heading)}`; + const level = heading.tagName.toLowerCase(); + li.className = `toc-item toc-${level}`; const link = document.createElement('a'); link.href = `#${heading.id}`; @@ -291,17 +292,6 @@ function generateToc() { } } -// Constants, Attributes and method group headings are h3 so they nest under a -// titled section's h2. In the untitled section, list them as h2 so they don't -// look like children of the class comment's last heading. -function tocLevel(heading) { - const level = heading.tagName.toLowerCase(); - if (level !== 'h3' || !heading.matches('.documentation-section > section > header > h3')) return level; - - const section = heading.closest('.documentation-section'); - return section.querySelector(':scope > .documentation-section-title') ? level : 'h2'; -} - function hookTocActiveHighlighting() { const tocLinks = document.querySelectorAll('.toc-link'); const targetHeadings = []; From 8512f011112fdc576ed2853445cf8e6c74e9269b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 21:16:12 +0000 Subject: [PATCH 3/3] Update the Aliki TOC highlight on resize A taller viewport can bring the next heading into the upper part of the page without a scroll event. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Gxq9jGyjWoANhMZXRtssiu --- lib/rdoc/generator/template/aliki/js/aliki.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rdoc/generator/template/aliki/js/aliki.js b/lib/rdoc/generator/template/aliki/js/aliki.js index 20886c7cca..6d1605a10e 100644 --- a/lib/rdoc/generator/template/aliki/js/aliki.js +++ b/lib/rdoc/generator/template/aliki/js/aliki.js @@ -338,14 +338,16 @@ function hookTocActiveHighlighting() { }; let updateScheduled = false; - window.addEventListener('scroll', () => { + const scheduleUpdate = () => { if (updateScheduled) return; updateScheduled = true; requestAnimationFrame(() => { updateScheduled = false; update(); }); - }, { passive: true }); + }; + window.addEventListener('scroll', scheduleUpdate, { passive: true }); + window.addEventListener('resize', scheduleUpdate); update(); }