Functional Principles
These principles define what the navigation system does and why.
Also see: Technical Principles for implementation details.
Navigation construction follows a strict two-phase approach:
Phase 1: Configuration Resolution (Elastic.Documentation.Configuration)
- Parse YAML files (
docset.yml,toc.yml,navigation.yml) - Resolve all file references to full paths relative to documentation set root
- Validate configuration structure and relationships
- Output: Fully resolved configuration objects with complete file paths
Phase 2: Navigation Construction (Elastic.Documentation.Navigation)
- Consume resolved configuration from Phase 1
- Build navigation tree with full URLs
- Create node relationships (parent/child/root)
- Set up home providers for URL calculation
- Output: Complete navigation tree with calculated URLs
Why Two Phases?
- Separation of Concerns: Configuration parsing is independent of navigation structure
- Validation: Catch file/structure errors before building expensive navigation trees
- Reusability: Same configuration can build different navigation structures (isolated vs assembler)
- Performance: Resolve file system operations once, reuse for navigation
See Two-Phase Loading for detailed explanation.
URLs are always built relative to the documentation set's source directory:
- Files referenced in
docset.ymlare relative to the docset root - Files referenced in nested
toc.ymlare relative to the toc directory - During Phase 1, all paths are resolved to be relative to the docset root
- During Phase 2, URLs are calculated from these resolved paths
Example:
docs/
├── docset.yml
├── index.md
└── api/
├── toc.yml
└── rest.md
- Root
- Nested TOC
Phase 1 resolves api/toc.yml reference to rest.md as: api/rest.md (relative to docset root)
Phase 2 builds URL as: /api/rest/
URLs are calculated on-demand, not stored:
- Nodes don't store their final URL
- URLs are computed from
HomeProvider.PathPrefix+ relative path - Changing a
HomeProviderinstantly updates all descendant URLs - No tree traversal needed to update URLs
Why Dynamic?
- Re-homing: Same subtree can have different URLs in different contexts
- Memory Efficient: Don't store redundant URL strings
- Consistency: URLs always reflect current home provider state
See Home Provider Architecture for implementation details.
A key design feature that enables assembler builds:
- Isolated Build: Each
DocumentationSetNavigationis its own root - Assembler Build:
SiteNavigationbecomes the root, docsets are "re-homed" - Re-homing: Replace a subtree's
HomeProviderto change its URL prefix - Cheap Operation: O(1) - just replace the provider reference
Example:
// Isolated: URLs start at /
homeProvider.PathPrefix = "";
// → /api/rest/
// Assembled: Re-home to /guide
homeProvider = new NavigationHomeProvider("/guide", siteNav);
// → /guide/api/rest/
See Assembler Process for how re-homing works in practice.
INavigationHomeProvider creates navigation scopes:
- Provider: Defines
PathPrefixandNavigationRootfor a scope - Accessor: Children use
INavigationHomeAccessorto access their scope - Inheritance: Child nodes inherit their parent's accessor
- Isolation: Changes to a provider only affect its scope
Scope Creators:
DocumentationSetNavigation- Creates scope for entire docsetTableOfContentsNavigation- Creates scope for TOC subtree (enables re-homing)
Scope Consumers:
FileNavigationLeaf- Uses accessor to calculate URLFolderNavigation- Passes accessor to childrenVirtualFileNavigation- Passes accessor to children
Every folder/node navigation has an Index:
- Index is either
index.mdor the first file - The node's URL is the same as its Index's URL
- Children appear "under" the index in navigation
- Index files map to folder paths:
/api/index.md→/api/
Why?
- Consistent URL Structure: Folders and their indexes share the same URL
- Natural Navigation: Index represents the folder's landing page
- Hierarchical: Clear parent-child URL relationships
Best practices for maintainability:
- Navigation structure should follow file system structure
- Avoid deep-linking files from different directories
- Use
folder:references when possible - Virtual files should group sibling files, not restructure the tree
Rationale:
- Discoverability: Developers can find files by following navigation
- Predictability: URL structure matches file structure
- Maintainability: Moving files in navigation matches moving them on disk
The navigation forms a directed acyclic graph (DAG):
- Tree Structure: Each node has exactly one parent (except root)
- No Cycles: Following parent pointers always terminates at root
- Single Root: Every node has a
NavigationRootpointing to the ultimate ancestor - Predictable Traversal: Tree structure enables efficient queries and traversal
Why This Matters:
- URL Uniqueness: Tree structure ensures each file has one canonical URL
- Consistent Hierarchy: Clear parent-child relationships for breadcrumbs and navigation
- Efficient Queries: Can traverse up (to root) or down (to leaves) without cycle detection
- Re-homing Safety: Replacing a subtree's root doesn't create cycles
Invariants:
- Following
.Parentchain always reaches root (or null for root) - Following
.NavigationRootimmediately reaches ultimate root - No node can be its own ancestor
- Every node appears exactly once in the tree
navigation.yml can declare phantoms:
phantoms:
- source: plugins://
Purpose:
- Reference nodes that exist but aren't included in site navigation
- Prevent "undeclared navigation" warnings
- Document intentionally excluded content
- Enable validation of cross-links
- Phase Order: Configuration must be fully resolved before navigation construction
- Path Resolution: All paths in configuration are relative to docset root after Phase 1
- URL Uniqueness: Every navigation item must have a unique URL within its site
- Root Consistency: All nodes in a subtree point to the same
NavigationRoot - Provider Validity: A node's
HomeProvidermust be an ancestor in the tree - Index Requirement: All node navigations (folder/toc/docset) must have an Index
- Path Prefix Uniqueness: In assembler builds, all
path_prefixvalues must be unique
- Tree Construction: O(n) where n = number of files
- URL Calculation: O(depth) for first access, O(1) with caching
- Re-homing: O(1) - just replace HomeProvider reference
- Tree Traversal: O(n) for full tree, but rarely needed
- Memory: O(n) for nodes, URLs computed on-demand