Continue the j / k hotkeys through the API reference tables - #1328
Merged
Merged
Conversation
The API reference index and the namespace pages carry their content in a table rather than under headings, so j / k had almost nothing to stop at there: two headings on the index, one on a namespace page, and then the keys went dead over the list the reader actually came for. The generator now marks the links of those two tables with data-fsdocs-nav, and the hotkeys treat every heading plus every marked link as a stop, in document order. The member tables of a module or a type page are left unmarked: those pages have headings of their own, and stopping at every member would bury them. A heading still scrolls to the top of the content with its section below it; a marked link only scrolls when it is off screen, so holding j walks down a long table instead of paging it one row per screen. A marked link has no anchor of its own, so walking a table leaves the URL alone. data-fsdocs-nav is not specific to the API reference, so a link in hand-written content can opt in the same way.
The API reference was the one part of the site with neither menu working. The pages carry their content in tables rather than under headings, so the "On this page" menu was hidden there, and the menu on the left showed a single "All Namespaces" link with nothing to navigate. Both now work. A namespace page lists its types and modules in the page menu, each entry linking to that type's own page rather than to the row on the page the reader is already on. A type or module page lists its sections, which needed anchors of their own: "Static members", "Nested modules" and "Inherited members" had none and could not be linked to at all. The index of all namespaces gets no menu, since the page is already that list, and keeps the wide two column layout. The inline "Table of contents" and "Contents" blocks are gone, the menu replaces them. Marking the current section reuses the scroll driven animations of the content pages, over the section headings only, because each tracked element costs three generated CSS rules and a module can have hundreds of members. The menu on the left was a bug rather than a design. Every API page computed the namespace list and then had it silently replaced, because the global substitutions define fsdocs-list-of-namespaces as well, with the collapsed link meant for the content pages, and the last value for a key wins. Its entries now drop the prefix that all the namespaces of a collection share, which otherwise left a column of identically truncated names, with the full name on the title of the entry. The header links to the index, and the entity list that expanded under the current namespace is gone now that the page menu covers it. Grouping the model by category is memoised in both renderers. It is the same answer for the lifetime of a renderer, and it was being computed once per page of the whole reference. Signature files for GenerateHtml and GenerateMarkdown come along, moving the documentation of the internals out of the implementation.
nojaf
marked this pull request as ready for review
September 15, 2026 17:42
The namespace list rendered from `_menu_template.html` marked nothing as current: every item was built with `IsActive = false`, and the category itself was passed `false` as well. A template that folds its sections away therefore rendered the one section the reader was actually inside closed, and no entry stood out as the page being read. The list built without menu templates has always marked both, so this was the templated path falling behind rather than a deliberate difference. It stayed invisible while the API pages showed the collapsed "All Namespaces" link instead of the namespace list. An entry is now active when it is the namespace of the page being shown, and the category is active whenever the menu is rendered for an API page, which is exactly when the reader is inside it. The content pages render the same list while the reader is elsewhere, so their section is left as it was.
A menu template could only use the handful of keys it was handed, so it
could link where fsdocs told it to and nowhere else. Writing {{root}} in
one reached the browser with the braces intact, because the substitutions
are applied in a single pass: a menu is a value inserted into the page,
and values are never rescanned.
Menu.createMenu now takes the substitutions the page itself is rendered
with and applies them to both templates. {{root}} is relative to the page,
so {{root}}reference/index.html resolves from every depth. The keys of the
menu go in last, so a template cannot lose one of its own to a project
that happens to define the same name.
An item also carries a Title, offered as {{fsdocs-menu-item-title}}, for
the hover text of a link whose own text does not say where it leads. The
API reference fills it with the full name of a namespace, where the menu
shortened the entry to the part that tells it apart from its neighbours.
The Markdown renderer built the same menu from the same templates and had
none of the recent fixes to it: nothing marked the namespace being read,
nothing marked the section, and its entries carried their full names. It
now matches the HTML renderer.
The list of documents no longer escapes a page title before handing it to
createMenu, which escapes it again, so a title carrying an ampersand
reached the page as the escape sequence itself. The menu templating path
had no test of its own, which is how that survived; it has two now.
Under watch, the menus are built from the substitutions of the moment
rather than the ones the server started with, so a change to a project
file reaches them.
Menu.createMenu takes an argument it did not before, and Menu.MenuItem has
a field it did not before. Both are breaking for code that builds menus
through FSharp.Formatting.Common.
This was referenced Sep 15, 2026
This was referenced Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The API reference index and the namespace pages carry their content in a
table rather than under headings, so j / k had almost nothing to stop at
there: two headings on the index, one on a namespace page, and then the
keys went dead over the list the reader actually came for.
The generator now marks the links of those two tables with
data-fsdocs-nav, and the hotkeys treat every heading plus every marked
link as a stop, in document order. The member tables of a module or a
type page are left unmarked: those pages have headings of their own, and
stopping at every member would bury them.
A heading still scrolls to the top of the content with its section below
it; a marked link only scrolls when it is off screen, so holding j walks
down a long table instead of paging it one row per screen. A marked link
has no anchor of its own, so walking a table leaves the URL alone.
data-fsdocs-nav is not specific to the API reference, so a link in
hand-written content can opt in the same way.
Before:

After:

Ever API docs page now also has a right "On this page" section (similar to how content has this):