diff --git a/CHANGELOG.md b/CHANGELOG.md
index 745f894..ed4d745 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,14 @@ All notable changes to ShellDocs land here. Format follows [Keep a Changelog](ht
## [Unreleased]
+### Fixed
+
+- **The table of contents on interactive sites.** With ``, the "On this page" highlight stopped moving once the circuit started, and clicking an entry went to the home page. The scroll-spy now re-attaches when Blazor replaces the list or the headings (or changes `data-toc-ids` on navigation), TOC links carry the page's path (`docs/setup#install`; a bare `#install` resolves against ``), and same-page links, including `[x](#id)` in content, scroll in place and keep the path in the address bar. Sites that added their own `toc.js` workaround can drop it.
+- **The last short sections of a page never became active in the TOC.** At the bottom of the page the last heading on screen is highlighted.
+- **`border-*` utilities lost to the base border colour.** The `*, *::before, *::after` rule in `shelldocs-theme.css` was outside any cascade layer, so it beat Tailwind's `border-destructive` and friends in previews and home pages. It's in `@layer base` now.
+- **`llms.txt` listed a group twice** when top-level pages sat on both sides of a folder ("## Pages" before and after it). Each group now appears once, in first-seen order.
+- **The page menu's chat links used the visitor's origin instead of `SiteUrl`**, so a static export previewed on localhost asked about a localhost URL. With `SiteUrl` set they name the published page.
+
## [0.1.18-alpha] — 2026-10-19
Colour presets, `shelldocs preview` for checking a static export locally, package pages on NuGet that render properly, plus fixes for sub-path links, folder titles and entities in component attributes.
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index eea92d5..62cf8ae 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -229,7 +229,7 @@ Chrome that must work without a Blazor runtime follows one contract:
| Iframe previews | `[data-iframe-preview][data-src][data-lazy]` | sets the iframe's `src` (plus `?theme=`) when visible (IntersectionObserver), on `[data-iframe-run]` or at once; posts `{ type: "shelldocs-theme", theme }` to loaded frames on theme change; a host Blazor reuses for another page (`data-src` or `data-lazy` changed) is unloaded and set up again |
| Mobile nav | page shell `[data-mobile-open]` | hamburger toggles; backdrop, Escape, link or navigation closes; page scroll locked on `` |
| Sidebar collapse | page shell `[data-sidebar-collapsed]`, `[data-sidebar-collapse-toggle]` | toggles collapse; remembered in `localStorage` and restored as soon as `shelldocs.js` runs, so the sidebar doesn't flash open |
-| TOC | `[data-toc-list][data-toc-ids]` | IntersectionObserver scroll-spy, re-attached on `enhancedload` |
+| TOC | `[data-toc-list][data-toc-ids]`, `.toc-link` hrefs with the page path | IntersectionObserver scroll-spy (plus a scroll check for the page bottom), re-attached on `enhancedload` and when a render replaces the list or headings or changes `data-toc-ids`; same-page links (`#id` or the page's own path) are handled in a window capture listener: scroll, `replaceState` with the full path |
| Search | `[data-search-dialog]` template, `[data-search-open]` | Cmd/Ctrl+K or a button opens a clone of the template; ranks `/_shelldocs/search.json`; arrows, Enter, Escape |
| Theme | ``, `[data-theme-toggle]` | inline head script applies the saved / system theme; the toggle flips the class and CSS picks its icon; a `MutationObserver` saves any change (including another library's toggle) and pushes it to `ThemeState`; re-applied after enhanced navigation |
diff --git a/src/ShellDocs.Components/Chrome/DocsPageActions.razor b/src/ShellDocs.Components/Chrome/DocsPageActions.razor
index 525b24b..6a5053b 100644
--- a/src/ShellDocs.Components/Chrome/DocsPageActions.razor
+++ b/src/ShellDocs.Components/Chrome/DocsPageActions.razor
@@ -8,7 +8,7 @@
with the page's absolute .md URL ([data-page-ai]), so it works in static exports. *@
@if (_markdownHref is not null)
{
-
+
@@ -50,10 +50,13 @@
: null;
}
- // Absolute with SiteUrl; otherwise shelldocs.js puts the visitor's origin in on click.
+ // The canonical .md URL with SiteUrl; null leaves the attribute out.
+ private string? SiteMarkdownUrl => string.IsNullOrWhiteSpace(Options.SiteUrl) ? null : Options.SiteUrl.TrimEnd('/') + "/" + _markdownHref;
+
+ // shelldocs.js fills in the visitor's origin when there is no SiteUrl.
private string ChatHref(string chat)
{
- var url = string.IsNullOrWhiteSpace(Options.SiteUrl) ? "/" + _markdownHref : Options.SiteUrl.TrimEnd('/') + "/" + _markdownHref;
+ var url = SiteMarkdownUrl ?? "/" + _markdownHref;
return chat + Uri.EscapeDataString($"Read {url} and help me with it.");
}
}
diff --git a/src/ShellDocs.Components/Chrome/TableOfContents.razor b/src/ShellDocs.Components/Chrome/TableOfContents.razor
index 58574b0..8f6dc77 100644
--- a/src/ShellDocs.Components/Chrome/TableOfContents.razor
+++ b/src/ShellDocs.Components/Chrome/TableOfContents.razor
@@ -1,9 +1,11 @@
@inject DocsPageState PageState
+@inject NavigationManager Nav
@implements IDisposable
@using ShellDocs.Core
-@* shelldocs.js attaches the scroll-spy from [data-toc-list] / [data-toc-ids];
- links are plain #anchors, so it works without a Blazor runtime. *@
+@* shelldocs.js attaches the scroll-spy from [data-toc-list] / [data-toc-ids] and
+ scrolls on click, so it works without a Blazor runtime. Links carry the page's
+ base-relative path: a bare #id resolves against to the home page. *@
@if (_items.Count > 0)
{