Skip to content

Snapshot the API navigation tree as text - #4755

Merged
Mpdreamz merged 26 commits into
mainfrom
feature/floating-nav-printer
Nov 5, 2026
Merged

Mpdreamz merged 26 commits into
mainfrom
feature/floating-nav-printer

Conversation

@Mpdreamz

@Mpdreamz Mpdreamz commented Nov 5, 2026

Copy link
Copy Markdown
Member

Adds a text printer for navigation trees and a snapshot test that shows the mounted API tree, so a change to where API pages sit or which versions are hidden shows up as a readable diff.

Affects: API Explorer, Navigation

Prompt summary: Add a navigation tree pretty-printer snapshot test that shows API pages make it into the navigation tree, and open it as its own PR.

Why

Nothing in the tests showed the shape of the API tree at a glance. The existing tests assert single properties, so a page moving to the wrong parent or a version losing its hidden flag needed a specific assertion to catch it.

What

Tree printer

NavigationTreePrinter prints one node per line with its URL, indented by depth, and flags island and hidden. It can limit depth and children per node.

Snapshot

FloatingSection_AfterResolve_PrintsTheApiTreeWithItsVersions mounts the Elasticsearch API with three versions and compares the printed tree to a verified file. main is visible, v9 and v8 are hidden.

Verify

dotnet test --project tests/Elastic.ApiExplorer.Tests --treenode-filter "/*/*/OpenApiGeneratorMultiVersionTests/FloatingSection_AfterResolve_PrintsTheApiTreeWithItsVersions"

Setting the hidden rule in ApiFloatingSection so no version is hidden makes the test fail.

Stack: 6 of 6, on top of #4738.

🤖 Generated with Claude Code

Mpdreamz and others added 25 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>
…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>
A docset that declares an API in its api: block now has that API in its navigation tree, in isolated and assembler builds, hidden in the docset sidebar. The explicit api: toc entry is gone, so its links.json lists the API without anyone editing a toc. Each assembler docset resolves its sections from generators that the build creates first, and the site APIs section lists the same APIs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AssembleAsync resolves every docset tree before the API generators exist, so the API sections stayed empty and links.json had no floating entries. The build now resolves the floating sections again once the generators are attached, and a section starts from how its root was created.

The section dropdown no longer lists hidden top-level roots, such as implicitly mounted APIs.

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

The build resolves the docset trees before any generator exists, then attaches the generators and resolves the floating sections again. The new test follows that order. It checks that the root fills, stays hidden and publishes its pages in links.json. Without the second resolution the test fails.

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>
A tree printer shows where each API page sits and which versions are hidden, so a change to the mounted tree shows up as a readable diff.

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 19:02
@Mpdreamz
Mpdreamz requested a review from theletterf November 5, 2026 19:02
@Mpdreamz Mpdreamz added the chore label Nov 5, 2026
Base automatically changed from feature/floating-implicit to main November 5, 2026 20:53
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Docs preview (local build)

Handbook preview: https://docs-v3-preview.elastic.dev/elastic/docs-builder/pull/4755/

@Mpdreamz
Mpdreamz merged commit b5ff994 into main Nov 5, 2026
36 checks passed
@Mpdreamz
Mpdreamz deleted the feature/floating-nav-printer branch November 5, 2026 21:05
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