Skip to content

feat: feature-flag navigation_preview.yml, enable for dev + assembler-preview - #3862

Merged
Mpdreamz merged 16 commits into
mainfrom
feature/nav-preview-flag
Aug 13, 2026
Merged

feat: feature-flag navigation_preview.yml, enable for dev + assembler-preview#3862
Mpdreamz merged 16 commits into
mainfrom
feature/nav-preview-flag

Conversation

@Mpdreamz

Copy link
Copy Markdown
Member

Summary

Adds a NAVIGATION_PREVIEW feature flag that switches assembled-site navigation from config/navigation.yml to config/navigation_preview.yml, enabling staged review of the ~69 island: true markers across /reference before they go to production.

Why a separate file instead of editing navigation.yml directly: The island treatment is a large, visible restructure of the sidebar across the entire /reference section. It needs to be exercised on a real assembled build first.

Stacked on: feature/toc-islands (#3851) → feature/toc-listings (#3781)

What changes

  • FeatureFlags — adds NavigationPreviewEnabled (navigation-preview key); env-var FEATURE_NAVIGATION_PREVIEW also works for local testing without editing config
  • PublishEnvironment.ToFeatureFlags() — normalizes UPPER_SNAKE yaml keys through Set() so the round-trip is correct (guard: NavigationPreviewEnabled_ReadsUnderscoredEnvironmentKey test)
  • ConfigurationFileProvider.UseNavigationPreview() — repoints NavigationFile at navigation_preview.yml; falls back to navigation.yml when the file is absent (safe for old remote config refs); GetEmbeddedStream gains fallback support to match Local/Remote branches
  • AssembleContext ctor — calls UseNavigationPreview() immediately after the environment is resolved, before any NavigationFile reader, so private-repo stripping applies to preview content automatically
  • config/navigation_preview.yml — copy of navigation.yml + 69 island: true lines across the /reference subtrees
  • config/assembler.ymlNAVIGATION_PREVIEW: true under dev and preview (the assembler-preview workflow uses --environment preview, so every PR preview deployment picks it up with no workflow change)

config/navigation.yml is not changed.

Test plan

  • dotnet test tests/Elastic.Documentation.Configuration.Tests/ — 668 pass including new UseNavigationPreviewTests
  • ./build.sh unit-test — all pass
  • Assembled build with --environment prod — navigation byte-identical to main; no islands
  • Assembled build with --environment dev — log line Feature flag navigation-preview appears; /reference shows island stubs
  • FEATURE_NAVIGATION_PREVIEW=true --environment prod — env-var override produces same result as dev
  • Check this PR's assembler-preview deployment — islands present and private-repo stripping still produces valid yaml

🤖 Generated with Claude Code

Mpdreamz and others added 16 commits August 13, 2026 15:25
Adds a new `listing:` entry type for docset.yml / toc.yml that:
- Glob-discovers pages under a folder and registers them in the nav
  tree with Hidden=true (links, prev/next, search all work; nav tree
  does not render them)
- Generates a grouped index page with stacked page-cards, a text
  filter input, and multi-select group chips
- Pages opt into a group via frontmatter `listing: group-name`
  (shorthand) or `listing: {group: group-name}` (mapping form)
- Supports a real index.md at the listing root and per group;
  creates a synthetic page if absent (appends `:::{listing}\n:::`
  to the body)
- Groups ordered by the `groups:` list in the TOC entry, then
  remaining groups alphabetically; unnamed pages rendered last

New files:
- Toc/Listing/ListingFrontMatterReader.cs — frontmatter-only YAML read
- Extensions/Listing/ListingDocsBuilderExtension.cs — auto-enabled
  extension; intercepts real index files; creates synthetic ones
- Extensions/Listing/ListingIndexFile.cs — MarkdownFile subtype that
  appends the listing directive to its content
- Myst/Directives/Listing/ListingBlock.cs — render-time card collection
- Myst/Directives/Listing/ListingView.cshtml — grouped page-cards UI
- Myst/Directives/Listing/ListingViewModel.cs
- Assets/listing.ts — client-side multi-select filter + group chips
- Assets/listing.test.ts — 17 Jest tests (pure predicate + DOM)

Key decisions:
- ExcludeFromIndexing added to INavigationItem (default = Hidden) so
  listing pages are hidden from nav but still indexed for search
- ListingFrontMatterConverter handles both scalar and mapping YAML
- NavigationDocumentationFileLookup uses reference identity; synthetic
  index files must be created directly (not via defaultFileHandling
  which returns ExcludedFile, filtered from Files)
- MarkdownParser constructed before InstantiateExtensions so it can
  be passed to ListingDocsBuilderExtension

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When visual: island is set, listing pages are hidden from the main nav
tree but get a dedicated island sidebar nav instead. The island nav
renders a back button linking to the parent root, a flat listing root
entry (using the listing's navigation_title), and all groups with their
pages expanded.

- Add ListingVisual.Island enum value
- Add IslandListingRoot and IsIslandListing to INavigationItem interface
- Set IslandListingRoot on all island listing pages/groups so they render
  the island nav instead of the main tree
- Set IsIslandListing on the listing root FolderNavigation so the index
  page also enters the island nav
- Add IslandNavViewModel, _IslandNav.cshtml for island sidebar rendering
- Wire island nav rendering in both IsolatedBuildNavigationHtmlWriter
  and GlobalNavigationHtmlWriter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove ListingVisual.Island; island nav is now configured with island: true
  independently of visual: (none|groups|all)
- Add Island bool to ListingOptions; parse island: true/false from YAML
- Validate: island: true + visual: none is an error (listing unreachable)
- groupHidden is now derived from visual: none only, not island status
- Back button in _IslandNav uses full-width outlined button style
  (border-2 border-blue-elastic, w-full)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Island listings always show as a single item in the parent nav tree.
The visual: option now controls what the island sidebar renders:
- visual: groups -> island shows group headings only (no pages listed)
- visual: all -> island shows groups with their pages

Parent nav changes:
- Island listing groups are always hidden from the parent tree
- Island folders render a link chevron (right-pointing) instead of
  a toggle, so clicking it navigates into the island

Island nav changes:
- Pages beneath groups only rendered when visual: all
- Visual passed through IslandVisual on FolderNavigation / INavigationItem

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three fixes:

1. QueryIndex path-first matching: island groups hide their index page
   so the main nav tree suppresses the group node, but QueryIndex was
   skipping hidden items and picking the first visible content page as
   the folder index. Now QueryIndex checks by SourcePath first
   (visibility-independent) before falling back to the visibility scan.

2. Island nav group headings now use nav-link (flex w-full) instead of
   nav-folder-link, making them full-width clickable like all other nav
   items. Page items under groups use ml-4 indentation.

3. Island chevron in parent nav: remove inline style that was conflicting
   with nav-chevron CSS; the class already applies -rotate-90 (right-
   pointing) by default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NavigationRenderNodeKind gets an Island value alongside Leaf/Node
- CreateNode returns Kind=Island for listing nodes with island:true;
  no NavigationItems/ShowToggle needed (island folders have no subtree
  in the main nav)
- IsIslandListing bool removed from NavigationRenderNode (Kind captures it)
- _TocTreeNav.cshtml: explicit else-if branch for Island renders a link
  with a double-chevron (>>) signalling the user enters a sub-navigation
- Add icon-chevron-double-down symbol to the SVG sprite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d chevron

BuildContext: when --path targets the docs folder directly, rootFolder equals
DocumentationSourceDirectory and the FindGitRoot ceiling blocks walking up to
find .git one level above, so DocumentationSet.Name falls back to
"unknown-{docsFolder}". Use null ceiling (depth-1 limit) when rootFolder is
already the docs folder — the .git directory is always within one hop.

Double-chevron: island entries in the parent nav show >> (icon-chevron-double-down
in the SVG sprite, rotated right by nav-chevron CSS) to signal they open a
sub-navigation rather than expand an inline subtree.

NavigationRenderNodeKind: add Island as a dedicated kind so the template has a
clean three-way branch (Leaf / Island / Node) instead of a Node kind with a
separate IsIslandListing boolean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NavigationRenderCache only supports IRootNavigationItem keys via
GetOrRenderAsync; island nav uses string keys. Use a separate
ConcurrentDictionary<string, string> for island HTML caching and
make RenderNavigation async to support awaiting island render.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Extends the island navigation concept from listing-only to any TOC
element. Islands render as a ≫ stub in the parent sidebar; clicking
swaps the sidebar for a dedicated island nav with a root-first back-link
stack.

## New placement options for island: true

- Root of docset.yml or any nested toc.yml
- Inline beside - toc: <path> in parent docset.yml/toc.yml (OR-ed with
  the toc.yml's own flag)
- Any - toc: entry in config/navigation.yml (assembler-side override;
  can only enable, never disable)

In isolated builds the docset root with island: true is silently inert
(no parent → RendersAsIsland() returns false). After SiteNavigation
re-parents it the same flag becomes active.

## Design

Three old members (IslandListingRoot, IsIslandListing, IslandVisual) on
INavigationItem are collapsed into a single bool IsIsland, set via a new
IAssignableIslandNavigation interface. The island root of a page is now
found by walking the Parent chain (FindIslandRoot()), which makes
nesting work for free.

RendersAsIsland() = item.IsIsland && item.Parent is not null
FindIslandRoot() = nearest ancestor where RendersAsIsland() is true

## Island sidebar

The island view model now reuses the same NavigationRenderNode tree the
main sidebar uses, plus a root-first back-link stack built by
NavigationRenderModel.CreateIsland(). The old hand-rolled
IslandNavGroup/IslandNavPage types and duplicated writer code are gone.

The NavigationRenderCache key is widened from IRootNavigationItem to
INavigationItem so islands share the same cache without the old per-
writer ConcurrentDictionary that collided across repos.

## Tests added

- IslandNavigationTests (7 tests): isolation-level coverage of all goal
  cases
- IslandSiteNavigationTests (3 tests): assembler-level OR semantics,
  docset root island after re-parenting, navigation.yml override
- NavigationRenderModelTests: IslandNode_ProjectsAsIslandKind,
  CreateIsland_BuildsBackLinkStack, CreateIsland_ContentHash tests
- Configuration Tests: island: true parsing at all four config levels,
  private-repo stripping survival

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion.yml

- SiteNavigation implicitly marks every top-level toc entry as an island
  so the two parallel 'owns the sidebar' mechanisms (top-level sections +
  explicit islands) are now one: FindIslandRoot resolves both.

- Delete _IslandNav.cshtml and IslandNavViewModel; merge their behavior
  into NavigationRenderModel + _TocTree.cshtml:
  - NavigationRenderModel.Create gains BackLinks computed from the
    render root's ancestor walk (nav root suppressed when dropdown is on)
  - CreateRootIndex now shows the island's own title row for nested islands
    in assembler builds
  - Current top-level item resolved by ancestor walk so nested islands
    correctly highlight the right dropdown entry

- Both writers simplified to a single GetOrRenderAsync(renderRoot) path;
  RenderIslandAsync removed.

- config/navigation.yml: add island: true to 69 reference section entries
  (elasticsearch, kibana, cloud, security, observability, ingestion tools,
  APM agents x8, elasticsearch-clients x11, ecs-logging x9, EDOT SDKs x8,
  beats, logstash, logstash plugins, fleet, machine-learning, search-ui, ecs)
  Top-level entries (reference, troubleshoot, …) stay without the marker —
  SiteNavigation makes them islands structurally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace heavy border-2 box back-links with compact text links in a
  subtle grouped section separated from the dropdown by a border-b line
- Add <hr class="border-grey-20"> after the island root-index row so the
  island home heading is visually distinct from the tree content below it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…chronous

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tsFileSystem

FileSystemFactory.ScopeSourceDirectory was removed; callers now construct
CheckoutsFileSystem directly, matching the pattern in SiteNavigationTests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-preview

Add a NAVIGATION_PREVIEW feature flag that switches the assembled site's
navigation from config/navigation.yml to config/navigation_preview.yml.
This lets island: true markers across /reference be staged and reviewed on
real assembled builds before they hit production.

How it works:
- FeatureFlags gets NavigationPreviewEnabled (key: navigation-preview)
- PublishEnvironment.ToFeatureFlags() normalizes UPPER_SNAKE yaml keys
  through FeatureFlags.Set() so env-var overrides (FEATURE_NAVIGATION_PREVIEW)
  work out of the box
- ConfigurationFileProvider.UseNavigationPreview() repoints NavigationFile
  at a new navigation_preview.yml temp copy (falls back to navigation.yml
  when the file is absent, safe for old remote config refs)
- GetEmbeddedStream gains fallback support to match Local/Remote branches
- AssembleContext constructor calls UseNavigationPreview() immediately after
  resolving the environment — before any reader touches NavigationFile — so
  private-repo stripping applies to the preview content automatically
- config/navigation_preview.yml: copy of navigation.yml + 69 island: true
  lines across the /reference subtrees
- config/assembler.yml: NAVIGATION_PREVIEW: true under dev and preview
  (assembler-preview.yml uses --environment preview, so every PR preview
  deployment picks it up with no workflow change)

config/navigation.yml is deliberately unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Mpdreamz
Mpdreamz requested a review from a team as a code owner August 13, 2026 14:42
@Mpdreamz
Mpdreamz requested a review from technige August 13, 2026 14:42
@Mpdreamz
Mpdreamz enabled auto-merge (squash) August 13, 2026 14:49
@Mpdreamz
Mpdreamz disabled auto-merge August 13, 2026 14:59
@Mpdreamz
Mpdreamz merged commit 73a7b5a into main Aug 13, 2026
22 of 24 checks passed
@Mpdreamz
Mpdreamz deleted the feature/nav-preview-flag branch August 13, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants