Skip to content

TOC block: clicking an entry highlights the previous entry, not the one clicked (scroll-spy offset rounding) #1255

Description

@georgestephanis

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

  1. Add a Table of Contents block to a post with several headings.
  2. Enable Smooth Scroll and Scroll Spy.
  3. On the front end, click a TOC entry that isn't the first one.
  4. 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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions