Skip to content

Table of Content#311

Merged
fretchen merged 7 commits intomainfrom
toc
Feb 13, 2026
Merged

Table of Content#311
fretchen merged 7 commits intomainfrom
toc

Conversation

@fretchen
Copy link
Owner

No description provided.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a Table of Contents (ToC) feature for blog posts, providing a sticky sidebar navigation for h2 and h3 headings with scroll-spy functionality. The implementation follows the documented plan in TOC_IMPLEMENTATION_PLAN.md and includes comprehensive test coverage.

Changes:

  • Added two custom hooks (useTableOfContents and useActiveHeading) for heading extraction and scroll-spy functionality
  • Created a TableOfContents component with automatic ID generation, German umlaut handling, and responsive design
  • Refactored Post.tsx layout to use a 3-column grid with centered content and right sidebar for ToC
  • Added comprehensive test suites for all new hooks and components
  • Extended global test setup with IntersectionObserver mock

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
website/hooks/useTableOfContents.ts New hook that extracts h2/h3 headings from DOM and generates URL-safe IDs with German umlaut support
website/hooks/useActiveHeading.ts New hook implementing scroll-spy with IntersectionObserver to track visible heading
website/components/TableOfContents.tsx New component rendering sticky ToC sidebar with active state highlighting and smooth scrolling
website/components/Post.tsx Refactored to use 3-column grid layout and integrate ToC sidebar with content ref passing
website/layouts/styles.ts Added ToC styles and 3-column article layout with responsive breakpoints
website/test/useTableOfContents.test.ts Comprehensive tests for heading extraction, ID generation, and edge cases
website/test/useActiveHeading.test.ts Tests for scroll-spy functionality with IntersectionObserver mocking
website/test/TableOfContents.test.tsx Component integration tests for rendering and click handling
website/test/setup.ts Added global IntersectionObserver mock for test environment
website/TOC_IMPLEMENTATION_PLAN.md Documentation of implementation phases and requirements

return () => {
observer.disconnect();
};
}, [headingIds, activeId]);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including activeId in the dependency array causes the effect to re-run every time the active heading changes. This recreates the IntersectionObserver unnecessarily and defeats the purpose of the if (!activeId && headingIds.length > 0) check on line 60, which is meant to only set the initial value. Remove activeId from the dependency array to prevent unnecessary re-initialization of the observer.

Suggested change
}, [headingIds, activeId]);
}, [headingIds]);

Copilot uses AI. Check for mistakes.
*/
export function TableOfContents({ contentRef, minHeadings = 2, title = "On this page" }: TableOfContentsProps) {
const headings = useTableOfContents(contentRef);
const { activeId, setActiveId } = useActiveHeading(headings.map((h) => h.id));
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The headings.map((h) => h.id) expression creates a new array on every render, causing useActiveHeading's effect to re-run unnecessarily. Wrap this in a useMemo to maintain referential equality when the heading IDs haven't actually changed. Example: const headingIds = useMemo(() => headings.map((h) => h.id), [headings]);

Copilot uses AI. Check for mistakes.
return () => {
observer.disconnect();
};
}, [headingIds, activeId]);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dependency array includes headingIds which is an array. Since arrays are compared by reference in React, this could cause unnecessary re-runs of the effect even when the heading IDs haven't changed. Consider using a more stable approach such as converting the array to a string for comparison, or using a deep equality check with a custom hook like useDeepCompareEffect.

Copilot uses AI. Check for mistakes.
@fretchen fretchen merged commit af594e9 into main Feb 13, 2026
4 checks passed
@fretchen fretchen deleted the toc branch February 13, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant