|
| 1 | +# Functional Principles |
| 2 | + |
| 3 | +These principles define what the navigation system does and why. |
| 4 | + |
| 5 | +> **Also see:** [Technical Principles](technical-principles.md) for implementation details. |
| 6 | +
|
| 7 | +## 1. Two-Phase Loading |
| 8 | + |
| 9 | +Navigation construction follows a strict two-phase approach: |
| 10 | + |
| 11 | +**Phase 1: Configuration Resolution** (`Elastic.Documentation.Configuration`) |
| 12 | +- Parse YAML files (`docset.yml`, `toc.yml`, `navigation.yml`) |
| 13 | +- Resolve all file references to **full paths** relative to documentation set root |
| 14 | +- Validate configuration structure and relationships |
| 15 | +- Output: Fully resolved configuration objects with complete file paths |
| 16 | + |
| 17 | +**Phase 2: Navigation Construction** (`Elastic.Documentation.Navigation`) |
| 18 | +- Consume resolved configuration from Phase 1 |
| 19 | +- Build navigation tree with **full URLs** |
| 20 | +- Create node relationships (parent/child/root) |
| 21 | +- Set up home providers for URL calculation |
| 22 | +- Output: Complete navigation tree with calculated URLs |
| 23 | + |
| 24 | +**Why Two Phases?** |
| 25 | +- **Separation of Concerns**: Configuration parsing is independent of navigation structure |
| 26 | +- **Validation**: Catch file/structure errors before building expensive navigation trees |
| 27 | +- **Reusability**: Same configuration can build different navigation structures (isolated vs assembler) |
| 28 | +- **Performance**: Resolve file system operations once, reuse for navigation |
| 29 | + |
| 30 | +> See [Two-Phase Loading](two-phase-loading.md) for detailed explanation. |
| 31 | +
|
| 32 | +## 2. Single Documentation Source |
| 33 | + |
| 34 | +URLs are always built relative to the documentation set's source directory: |
| 35 | +- Files referenced in `docset.yml` are relative to the docset root |
| 36 | +- Files referenced in nested `toc.yml` are relative to the toc directory |
| 37 | +- During Phase 1, all paths are resolved to be relative to the docset root |
| 38 | +- During Phase 2, URLs are calculated from these resolved paths |
| 39 | + |
| 40 | +**Example:** |
| 41 | +``` |
| 42 | +docs/ |
| 43 | +├── docset.yml # Root |
| 44 | +├── index.md |
| 45 | +└── api/ |
| 46 | + ├── toc.yml # Nested TOC |
| 47 | + └── rest.md |
| 48 | +``` |
| 49 | + |
| 50 | +Phase 1 resolves `api/toc.yml` reference to `rest.md` as: `api/rest.md` (relative to docset root) |
| 51 | +Phase 2 builds URL as: `/api/rest/` |
| 52 | + |
| 53 | +## 3. URL Building is Dynamic and Cheap |
| 54 | + |
| 55 | +URLs are **calculated on-demand**, not stored: |
| 56 | +- Nodes don't store their final URL |
| 57 | +- URLs are computed from `HomeProvider.PathPrefix` + relative path |
| 58 | +- Changing a `HomeProvider` instantly updates all descendant URLs |
| 59 | +- No tree traversal needed to update URLs |
| 60 | + |
| 61 | +**Why Dynamic?** |
| 62 | +- **Re-homing**: Same subtree can have different URLs in different contexts |
| 63 | +- **Memory Efficient**: Don't store redundant URL strings |
| 64 | +- **Consistency**: URLs always reflect current home provider state |
| 65 | + |
| 66 | +> See [Home Provider Architecture](home-provider-architecture.md) for implementation details. |
| 67 | +
|
| 68 | +## 4. Navigation Roots Can Be Re-homed |
| 69 | + |
| 70 | +A key design feature that enables assembler builds: |
| 71 | +- **Isolated Build**: Each `DocumentationSetNavigation` is its own root |
| 72 | +- **Assembler Build**: `SiteNavigation` becomes the root, docsets are "re-homed" |
| 73 | +- **Re-homing**: Replace a subtree's `HomeProvider` to change its URL prefix |
| 74 | +- **Cheap Operation**: O(1) - just replace the provider reference |
| 75 | + |
| 76 | +**Example:** |
| 77 | +```csharp |
| 78 | +// Isolated: URLs start at / |
| 79 | +homeProvider.PathPrefix = ""; |
| 80 | +// → /api/rest/ |
| 81 | +
|
| 82 | +// Assembled: Re-home to /guide |
| 83 | +homeProvider = new NavigationHomeProvider("/guide", siteNav); |
| 84 | +// → /guide/api/rest/ |
| 85 | +``` |
| 86 | + |
| 87 | +> See [Assembler Process](assembler-process.md) for how re-homing works in practice. |
| 88 | +
|
| 89 | +## 5. Navigation Scope via HomeProvider |
| 90 | + |
| 91 | +`INavigationHomeProvider` creates navigation scopes: |
| 92 | +- **Provider**: Defines `PathPrefix` and `NavigationRoot` for a scope |
| 93 | +- **Accessor**: Children use `INavigationHomeAccessor` to access their scope |
| 94 | +- **Inheritance**: Child nodes inherit their parent's accessor |
| 95 | +- **Isolation**: Changes to a provider only affect its scope |
| 96 | + |
| 97 | +**Scope Creators:** |
| 98 | +- `DocumentationSetNavigation` - Creates scope for entire docset |
| 99 | +- `TableOfContentsNavigation` - Creates scope for TOC subtree (enables re-homing) |
| 100 | + |
| 101 | +**Scope Consumers:** |
| 102 | +- `FileNavigationLeaf` - Uses accessor to calculate URL |
| 103 | +- `FolderNavigation` - Passes accessor to children |
| 104 | +- `VirtualFileNavigation` - Passes accessor to children |
| 105 | + |
| 106 | +## 6. Index Files Determine Folder URLs |
| 107 | + |
| 108 | +Every folder/node navigation has an **Index**: |
| 109 | +- Index is either `index.md` or the first file |
| 110 | +- The node's URL is the same as its Index's URL |
| 111 | +- Children appear "under" the index in navigation |
| 112 | +- Index files map to folder paths: `/api/index.md` → `/api/` |
| 113 | + |
| 114 | +**Why?** |
| 115 | +- **Consistent URL Structure**: Folders and their indexes share the same URL |
| 116 | +- **Natural Navigation**: Index represents the folder's landing page |
| 117 | +- **Hierarchical**: Clear parent-child URL relationships |
| 118 | + |
| 119 | +## 7. File Structure Should Mirror Navigation |
| 120 | + |
| 121 | +Best practices for maintainability: |
| 122 | +- Navigation structure should follow file system structure |
| 123 | +- Avoid deep-linking files from different directories |
| 124 | +- Use `folder:` references when possible |
| 125 | +- Virtual files should group sibling files, not restructure the tree |
| 126 | + |
| 127 | +**Rationale:** |
| 128 | +- **Discoverability**: Developers can find files by following navigation |
| 129 | +- **Predictability**: URL structure matches file structure |
| 130 | +- **Maintainability**: Moving files in navigation matches moving them on disk |
| 131 | + |
| 132 | +## 8. Acyclic Graph Structure |
| 133 | + |
| 134 | +The navigation forms a **directed acyclic graph (DAG)**: |
| 135 | +- **Tree Structure**: Each node has exactly one parent (except root) |
| 136 | +- **No Cycles**: Following parent pointers always terminates at root |
| 137 | +- **Single Root**: Every node has a `NavigationRoot` pointing to the ultimate ancestor |
| 138 | +- **Predictable Traversal**: Tree structure enables efficient queries and traversal |
| 139 | + |
| 140 | +**Why This Matters:** |
| 141 | +- **URL Uniqueness**: Tree structure ensures each file has one canonical URL |
| 142 | +- **Consistent Hierarchy**: Clear parent-child relationships for breadcrumbs and navigation |
| 143 | +- **Efficient Queries**: Can traverse up (to root) or down (to leaves) without cycle detection |
| 144 | +- **Re-homing Safety**: Replacing a subtree's root doesn't create cycles |
| 145 | + |
| 146 | +**Invariants:** |
| 147 | +1. Following `.Parent` chain always reaches root (or null for root) |
| 148 | +2. Following `.NavigationRoot` immediately reaches ultimate root |
| 149 | +3. No node can be its own ancestor |
| 150 | +4. Every node appears exactly once in the tree |
| 151 | + |
| 152 | +## 9. Phantom Nodes for Incomplete Navigation |
| 153 | + |
| 154 | +`navigation.yml` can declare phantoms: |
| 155 | +```yaml |
| 156 | +phantoms: |
| 157 | + - source: plugins:// |
| 158 | +``` |
| 159 | +
|
| 160 | +**Purpose:** |
| 161 | +- Reference nodes that exist but aren't included in site navigation |
| 162 | +- Prevent "undeclared navigation" warnings |
| 163 | +- Document intentionally excluded content |
| 164 | +- Enable validation of cross-links |
| 165 | +
|
| 166 | +--- |
| 167 | +
|
| 168 | +## Key Invariants |
| 169 | +
|
| 170 | +1. **Phase Order**: Configuration must be fully resolved before navigation construction |
| 171 | +2. **Path Resolution**: All paths in configuration are relative to docset root after Phase 1 |
| 172 | +3. **URL Uniqueness**: Every navigation item must have a unique URL within its site |
| 173 | +4. **Root Consistency**: All nodes in a subtree point to the same `NavigationRoot` |
| 174 | +5. **Provider Validity**: A node's `HomeProvider` must be an ancestor in the tree |
| 175 | +6. **Index Requirement**: All node navigations (folder/toc/docset) must have an Index |
| 176 | +7. **Path Prefix Uniqueness**: In assembler builds, all `path_prefix` values must be unique |
| 177 | + |
| 178 | +## Performance Characteristics |
| 179 | + |
| 180 | +- **Tree Construction**: O(n) where n = number of files |
| 181 | +- **URL Calculation**: O(depth) for first access, O(1) with caching |
| 182 | +- **Re-homing**: O(1) - just replace HomeProvider reference |
| 183 | +- **Tree Traversal**: O(n) for full tree, but rarely needed |
| 184 | +- **Memory**: O(n) for nodes, URLs computed on-demand |
0 commit comments