The navigation core is genuinely principled — INavigationItem / ILeafNavigationItem / INodeNavigationItem / IRootNavigationItem form a clean recursive tree, INavigationHomeProvider buys O(1) re-homing with lazily-computed URLs, and NavigationRenderModel resolves the tree once into a dumb render model the templates consume without logic. The periphery is where it frays, in four recurring patterns. None of this is in scope for the current branch — tracking here so the investigation survives.
1. Render mode encoded as three orthogonal booleans
NavigationRenderModel.Create takes isUsingNavigationDropdown, isPrimaryNavEnabled, isGlobalAssemblyBuild — eight combinations for three real modes. Each conditional inside (CreateRootIndex, CreateBackLinks) re-derives the mode from a different pair. One mode enum would make any production-affecting flag flip obvious rather than a one-token diff.
Related: IsUsingNavigationDropdown is both an INavigationItem property and a Create parameter, and GlobalNavigationHtmlWriter previously hardcoded a literal that contradicted the root's own value.
2. ~15 hand-rolled recursive walkers
The same tree is walked by ~15 independent recursive visitors, each re-deriving its own leaf/node/hidden/crosslink policy, despite INavigationTraversable existing. There is already a behavioural divergence:
SitemapBuilder.cs:89-100 filters on Hidden where the rest of the pipeline uses ExcludeFromIndexing, so listing pages that are deliberately "hidden but indexed" are silently missing from the sitemap.
CodexNavigation.cs:48-54 documents why a second root over shared nodes cannot reuse UpdateNavigationIndex at all — mutable NavigationIndex on a shared node is the leak.
3. docs-content privileged in code rather than configuration
docs-content is special-cased in at least five places:
SiteNavigation.cs:59-71 — root-leaf hoisting
SiteNavigation.cs:224-240 — path_prefix-required exemption (error path invents a bad-mapping-… URL segment)
SiteNavigationFile.cs — URI scheme default
AssembleSources.cs — implicit special casing
HtmlWriter.cs — indexing rule
Additionally, SiteNavigationFile.cs:158-257 and :259-332 are ~70 lines of copy-paste YAML converters that share the docs-content:// implicit scheme.
4. Mutation-after-construction as the extension mechanism
IAssignableChildrenNavigation, IAssignableIslandNavigation, SectionNavigation.Url's internal set, Index = null! / Id = null! sentinels, public Parent and NavigationIndex setters — each forces a defensive check downstream.
SiteNavigation implements IAssignableChildrenNavigation and then throws from SetNavigationItems (:184-185) — the interface contract is a lie for one implementation.
Feature-specific follow-ups (from the top-nav branch)
Localized messes worth their own cleanups
GlobalNavigationPathProvider.OutputFile (:59-111) — a dozen hardcoded repo/path prefixes under a //TODO clean up
ConfigurationFileProvider.CreateNavigationFile (:140-212) — regex line-surgery on navigation.yml plus an inlined hardcoded docs-builder:// block
The navigation core is genuinely principled —
INavigationItem/ILeafNavigationItem/INodeNavigationItem/IRootNavigationItemform a clean recursive tree,INavigationHomeProviderbuys O(1) re-homing with lazily-computed URLs, andNavigationRenderModelresolves the tree once into a dumb render model the templates consume without logic. The periphery is where it frays, in four recurring patterns. None of this is in scope for the current branch — tracking here so the investigation survives.1. Render mode encoded as three orthogonal booleans
NavigationRenderModel.CreatetakesisUsingNavigationDropdown,isPrimaryNavEnabled,isGlobalAssemblyBuild— eight combinations for three real modes. Each conditional inside (CreateRootIndex,CreateBackLinks) re-derives the mode from a different pair. One mode enum would make any production-affecting flag flip obvious rather than a one-token diff.Related:
IsUsingNavigationDropdownis both anINavigationItemproperty and aCreateparameter, andGlobalNavigationHtmlWriterpreviously hardcoded a literal that contradicted the root's own value.2. ~15 hand-rolled recursive walkers
The same tree is walked by ~15 independent recursive visitors, each re-deriving its own leaf/node/hidden/crosslink policy, despite
INavigationTraversableexisting. There is already a behavioural divergence:SitemapBuilder.cs:89-100filters onHiddenwhere the rest of the pipeline usesExcludeFromIndexing, so listing pages that are deliberately "hidden but indexed" are silently missing from the sitemap.CodexNavigation.cs:48-54documents why a second root over shared nodes cannot reuseUpdateNavigationIndexat all — mutableNavigationIndexon a shared node is the leak.3.
docs-contentprivileged in code rather than configurationdocs-contentis special-cased in at least five places:SiteNavigation.cs:59-71— root-leaf hoistingSiteNavigation.cs:224-240—path_prefix-required exemption (error path invents abad-mapping-…URL segment)SiteNavigationFile.cs— URI scheme defaultAssembleSources.cs— implicit special casingHtmlWriter.cs— indexing ruleAdditionally,
SiteNavigationFile.cs:158-257and:259-332are ~70 lines of copy-paste YAML converters that share thedocs-content://implicit scheme.4. Mutation-after-construction as the extension mechanism
IAssignableChildrenNavigation,IAssignableIslandNavigation,SectionNavigation.Url'sinternal set,Index = null!/Id = null!sentinels, publicParentandNavigationIndexsetters — each forces a defensive check downstream.SiteNavigationimplementsIAssignableChildrenNavigationand then throws fromSetNavigationItems(:184-185) — the interface contract is a lie for one implementation.Feature-specific follow-ups (from the top-nav branch)
SectionNavigation.Url= first child's URL (SiteNavigation.cs:96-99) is the root cause of both theGetParents()URL-dedupe drop and the "← Guides goes to get-started" limitation. A synthetic section landing page gives the node its own URL and removes both special cases.SectionTopNavBuilder.cs:34-41keys plain tocs byIdentifierbut sections byTitlewithOrdinalIgnoreCase— renaming a section in YAML silently drops its tab. Match on identity (section://…).TopNavDropdownItemandTopNavLinkItem.SectionIdsare never constructed anywhere insrc/— only in tests. The dropdown branch at_SecondaryNav.cshtml,secondary-nav.ts, andsecondary-nav-dropdown.cssare unreachable in real builds. Either wire adropdown:concept into the schema or delete all of it.SiteNavigation.TopLevelItemsis marked//TODO Obsolete?yet is an input toSectionTopNavBuilderandGlobalNavigationHtmlWriter. Resolve before adding a third consumer.LlmsNavigationEnhancer.cs:96dispatches onitem.GetType().FullName?.StartsWith("Elastic.ApiExplorer.")to avoid an assembly reference — reflection-shaped dispatch in an AOT codebase. A marker interface onINavigationItemremoves it.Localized messes worth their own cleanups
GlobalNavigationPathProvider.OutputFile(:59-111) — a dozen hardcoded repo/path prefixes under a//TODO clean upConfigurationFileProvider.CreateNavigationFile(:140-212) — regex line-surgery on navigation.yml plus an inlined hardcodeddocs-builder://block