Skip to content

Put the APIs in the assembler navigation tree - #4737

Merged
Mpdreamz merged 22 commits into
mainfrom
feature/floating-assembler
Nov 5, 2026
Merged

Mpdreamz merged 22 commits into
mainfrom
feature/floating-assembler

Conversation

@Mpdreamz

@Mpdreamz Mpdreamz commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

A section: APIs with floating: api in navigation.yml now mounts every declared API in the assembler navigation tree, so the sitemap walk and the llms.txt listing read the APIs from the tree like any other page. A build that does not render the API Explorer is unchanged.

Affects: Assembler builds, Navigation, Content export

Prompt summary: Make API pages part of the navigation tree in isolated and assembler builds, because the sitemap and llms.txt read that tree and the API pages were a side door next to it. The roots must exist when the navigation is built, with the details filled in later. This is the fourth step of the design in RFC: Floating sections in the navigation tree, and it implements the acceptance criteria of docs-eng-team#825.

Why

The assembler renders API pages after the Markdown build and SiteNavigation never sees them. llms.txt has to append the API catalog by hand in GenerateApiSection, and the sitemap walk cannot reach the pages.

What

The APIs section

SiteNavigation mounts a section: that has floating: <kind> as a hub. Its children are the roots of every floating section of that kind the build offers, and its URL is the hub page of the kind, /docs/api. A section without any such sections keeps its other behaviour. The APIs tab of the navigation preview keeps its external: link either way. navigation.yml gets an APIs section, and the preview file gets floating: api on its existing tab.

Roots first, children later

AssemblerApiSections finds the repositories that declare APIs when the build starts, and offers one section per API in key order. Their roots exist when the navigation is built. AssemblerOpenApiBuildStep attaches a generator to each owner and fills the sections from the same trees it renders the pages from, so nothing is fetched or built twice. A root takes the spec title once the spec is read, as the API hub does. A failed API hides its root.

llms.txt and the sitemap

The llms.txt listing walks the tree, and GenerateApiSection is deleted. It still links the Markdown export of each API, as before, so its output does not change. The sitemap walk skips links to a heading inside a page. The /docs/api/ filter stays until docs-eng-team#725.

Reading order

The hub and the root of each API take a place in the navigation order, so the previous and next links would walk into them: the last page of the docs would point to the APIs, and in the preview navigation the Reference page would have the Logstash API as its previous page. The pager now skips the hub and every floating root, so paging through the docs does not change.

Docs

docs/documentation/assembler/navigation.md documents floating:.

Stack: 4 of 5, on top of #4731.

Out of scope: replacing the link check in #4653, attaching every declared API without a toc entry, publishing the assembler's APIs in links.json, and removing the sitemap filter. The assembler, serve and Codex builds still reject an api: entry in a docset toc.

Verify

mise run test
dotnet test --project tests/Navigation.Tests --treenode-filter "/*/*/FloatingHubNavigationTests/*"
dotnet test --project tests/Elastic.Documentation.Build.Tests --treenode-filter "/*/*/AssemblerOpenApiBuildStepIntegrationTests/*"

The integration test needs network access to CloudFront. It builds a docset with the Elasticsearch API, runs the assembler step, and checks the hub, the sitemap walk and the llms.txt listing.

🤖 Generated with Claude Code

Mpdreamz and others added 17 commits November 5, 2026 12:02
A toc entry such as `api: elasticsearch` now mounts a floating section: a set of pages that owns its URL root and sidebar and is not made of Markdown files. A provider resolves the section before navigation is built, and it renders as an island. `CreateLinkReference()` skips these nodes instead of casting them to `MarkdownFile`. No provider is registered yet, so a build that uses the entry reports an error.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…entries

The site assembler dropped every root child of a mounted docset and warned
that it was undeclared, so an `api:` entry vanished from the assembled
navigation. Floating section roots now carry a marker interface and the
assembler keeps them. An `api:` entry that also sets `children:`, `file:` or
other keys now reports an error instead of silently dropping them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A floating section root now exists as soon as the toc mounts it, with its landing URL and title, so layout code can link to it. The children come later from `ResolveAsync`, which `DocumentationSet.ResolveFloatingSectionsAsync` runs before generation. The children keep their own numbering, so filling them never renumbers the surrounding navigation.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The narrative root copied only leaf items into the site tree, so an
`- api: <key>` entry in its toc resolved and then vanished. The root now
keeps floating section roots next to its leaves.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ResolveDirectoryTree now fills floating sections, so DocumentationGenerator, the assembler and the codex build see their pages without a separate call. The test goes through ResolveDirectoryTree and checks resolution runs once.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An `api: <key>` toc entry now resolves to an API section. Its root exists when the toc is read, and `ResolveAsync` fills it with the version trees before generation. The generator builds each API's trees once and renders from them, so mounting a section adds no second fetch or build. Only the current version is visible.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The docset now fills floating sections itself, so a build run with `--skip-api` resolves an `api:` entry without a generator. The section stays empty and hidden instead of failing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ip-api

A spec that fails to build no longer stops the Markdown build: the section reports the error and hides. The section also hides when no main version resolved, and a hidden API no longer appears in the section dropdown. The generation state records --skip-api, so toggling it triggers a full build. The docs now say that only the isolated build accepts api: entries.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The floating section no longer emits its own error when tree building fails, because the generator awaits the same cached task and already reports it with the stack trace. The no-main test now makes main unreadable without throwing, so it proves released versions loaded. A new build-twice test covers toggling --skip-api over unchanged pages.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A mounted API now publishes its pages in `links.json` under `floating`, one entry per page path with a bit mask of the versions that contain it. A cross-link such as `elasticsearch://api/elasticsearch/v8/operation/operation-search` resolves through hash lookups and a bit test, and a page missing in that version is an error. The resolver builds the final URL from the site root, because API pages sit outside every repository path prefix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An alias now belongs to the versions that list it, so a link to an alias
in a version without it fails, and an alias can name different pages in
different versions. A cross-link to an API page keeps its #fragment.
CodexAwareUriResolver sends API pages to the published site instead of
a repository path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A fragment stays on the resolved URL and the build does not check the anchor. Aliases of a collapsed operation resolve only in the versions that list them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A floating link section no longer assumes how a kind routes its URLs. Each version names its root, and whether it is hidden or current, so a reader joins a root and a page path and never adds a `v` segment. A cross-link picks a version by its name, and aliases are listed under their canonical path, which writes that path once however many aliases it has.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An API page called `main` or `8` was read as a version, so the link reached
the landing page. A page of the current version now takes precedence, so
adding a version never changes where an existing link points. The current
version's bit is cached instead of scanned for on every unversioned link.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The lookups of a section are built on first use. The copy that a `with` expression makes carried them over, so replacing the versions or the aliases kept answering from the old data, and a test failed only when another test had used the shared fixture first. A copy now keeps the data and none of the lookups.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A `section: APIs` with `floating: api` in navigation.yml now mounts every declared API under a hub, so the sitemap walk and the llms.txt listing read the APIs from the tree like any other page. The roots exist when the navigation is built and the OpenAPI step fills them from the trees it renders from. GenerateApiSection and its catalog side door are gone, and the walk skips links to a heading inside a page.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Mpdreamz
Mpdreamz requested a review from a team as a code owner November 5, 2026 13:42
@Mpdreamz
Mpdreamz requested a review from reakaleek November 5, 2026 13:42
…ML exports

The hub URL has no trailing slash, so the sitemap filter now matches it exactly.
The top navigation tab for a floating section without external: links the hub
instead of the first child. API sections are only mounted when the HTML export,
and with it the API build step, runs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The exporter guard sat inline in the build service, so no test reached it. It now lives in AssemblerApiSections.For, and a test checks that an LLM-only build gets no API sections while an HTML build keeps them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The APIs hub and the root of each API take a place in the global navigation order, so the pager walked into them. In the preview navigation the Reference page had the Logstash API as its previous page, and the last page of the docs had the APIs as its next page. The previous and next links now skip the hub and every floating root.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Base automatically changed from feature/floating-links to main November 5, 2026 19:36
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment on lines +64 to +65
var tabUrl = section.FloatingKind is not null
? sectionNav.Url

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: P3 (Low)

For a floating APIs section without external:, the tab gets the hub URL but is still assigned SectionId: sectionNav.Id below. API pages and the catalog retain their own navigation roots, so that ID never matches; the header's URL-based active-state fallback also excludes links with a section ID. Consequently, the newly supported tab never becomes active on its own pages. Leave the section ID unset for floating hubs or otherwise associate the rendered API roots with the tab, and cover active-state matching in the test.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

@@ -1,322 +1,3 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: P3 (Low)

This PR deletes every test in SitemapTests.cs and leaves only the license header. That removes all coverage of SitemapBuilder.Generate: XML output, ordering, the entry-count limit and the /docs/api/ exclusion. It also removes coverage of EsSitemapReader.BuildSearchBody, and nothing else in tests/ covers either one. The PR also changes the sitemap filter in this same PR (it now drops the /docs/api hub URL as well), and no test covers that change. The deletion looks accidental. Please restore the file and add a case for the exact /docs/api key.

Generated by Libra. Specialist: libra-complexity. React with 👍 or 👎 to give feedback on this comment.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Mpdreamz
Mpdreamz merged commit 95f2ca0 into main Nov 5, 2026
36 checks passed
@Mpdreamz
Mpdreamz deleted the feature/floating-assembler branch November 5, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant