Summary
When a Table of Contents block has both Smooth Scroll and Scroll Spy enabled, clicking an entry scrolls to the right heading, but the previous entry stays marked active instead of the one just clicked. It self-corrects on the next manual scroll.
Root cause
src/assets/js/kb-table-of-contents.js, scrollToElement():
const originalTop = Math.floor(element.getBoundingClientRect().top) - offset;
window.scrollBy({ top: originalTop, left: 0, behavior: 'smooth' });
Math.floor truncates the fractional part of rect.top before subtracting offset, so the computed scroll distance always slightly undershoots. After the smooth scroll settles, the heading's top ends up a fraction of a pixel above offset, not at-or-below it.
src/assets/js/gumshoe.js (bundled scroll-spy library) decides which entry is active with:
return parseInt(bounds.top, 10) <= offset;
Because the click-scroll landed just above offset, this check fails for the just-scrolled-to heading, so Gumshoe keeps the previously-active entry highlighted until the user scrolls manually (which recomputes on real scroll-event polling and self-corrects).
Confirmed present on master @ da98205 (2026-08-05) — not something already fixed since a recent release.
Steps to reproduce
- Add a Table of Contents block to a post with several headings.
- Enable Smooth Scroll and Scroll Spy.
- On the front end, click a TOC entry that isn't the first one.
- Observe: the scroll lands correctly, but the previous entry (or none) stays highlighted instead of the one clicked, until you scroll manually.
Proposed fix
I've opened a PR that (a) fixes the rounding in scrollToElement(), and (b) replaces the bundled Gumshoe scroll-event-polling scroll-spy with a native IntersectionObserver implementation for this block (~96% global browser support per caniuse.com, W3C polyfill available for the remainder) — scoped only to the TOC block; the navigation block's own separate Gumshoe-based scroll-spy usage (src/assets/js/kb-navigation-block.js) is untouched and could be a good candidate for the same treatment in a follow-up if maintainers are interested.
AI disclosure
This issue was drafted with the assistance of Claude (Anthropic's AI coding assistant), operating under my direction and review. Claude located and read the relevant source in this repo, traced the root cause across kb-table-of-contents.js and gumshoe.js, confirmed against the current master commit that the bug was still present, and drafted this writeup. I reviewed the analysis and code excerpts before posting.
Summary
When a Table of Contents block has both Smooth Scroll and Scroll Spy enabled, clicking an entry scrolls to the right heading, but the previous entry stays marked
activeinstead of the one just clicked. It self-corrects on the next manual scroll.Root cause
src/assets/js/kb-table-of-contents.js,scrollToElement():Math.floortruncates the fractional part ofrect.topbefore subtractingoffset, so the computed scroll distance always slightly undershoots. After the smooth scroll settles, the heading'stopends up a fraction of a pixel aboveoffset, not at-or-below it.src/assets/js/gumshoe.js(bundled scroll-spy library) decides which entry is active with:Because the click-scroll landed just above
offset, this check fails for the just-scrolled-to heading, so Gumshoe keeps the previously-active entry highlighted until the user scrolls manually (which recomputes on real scroll-event polling and self-corrects).Confirmed present on
master@da98205(2026-08-05) — not something already fixed since a recent release.Steps to reproduce
Proposed fix
I've opened a PR that (a) fixes the rounding in
scrollToElement(), and (b) replaces the bundled Gumshoe scroll-event-polling scroll-spy with a nativeIntersectionObserverimplementation for this block (~96% global browser support per caniuse.com, W3C polyfill available for the remainder) — scoped only to the TOC block; the navigation block's own separate Gumshoe-based scroll-spy usage (src/assets/js/kb-navigation-block.js) is untouched and could be a good candidate for the same treatment in a follow-up if maintainers are interested.AI disclosure
This issue was drafted with the assistance of Claude (Anthropic's AI coding assistant), operating under my direction and review. Claude located and read the relevant source in this repo, traced the root cause across
kb-table-of-contents.jsandgumshoe.js, confirmed against the currentmastercommit that the bug was still present, and drafted this writeup. I reviewed the analysis and code excerpts before posting.