Assembler Process
The assembler combines multiple documentation repositories into a unified site with custom URL prefixes.
Prerequisites: Read Functional Principles #4 and Home Provider Architecture first to understand re-homing.
Multiple repositories need to appear as one site:
elastic-docswith/api/and/guides/- Assembled site needs
/elasticsearch/api/and/elasticsearch/guides/ - Same content, different URLs, no rebuilding
Four-phase process:
public AssemblerDocumentationSet(
ILoggerFactory logFactory,
AssembleContext context,
Checkout checkout,
ICrossLinkResolver crossLinkResolver,
IConfigurationContext configurationContext,
IReadOnlySet<Exporter> availableExporters)
{
// For each repository:
// 1. Load and resolve docset.yml
var buildContext = new BuildContext(...)
{
AssemblerBuild = true
};
// 2. Build DocumentationSetNavigation with assembler context
DocumentationSet = new DocumentationSet(buildContext, logFactory, crossLinkResolver);
}
- ← CRITICAL!
Why AssemblerBuild = true matters:
// DocumentationSetNavigation constructor when creating TOCs:
var assemblerBuild = context.AssemblerBuild;
var tocHomeProvider = assemblerBuild
? new NavigationHomeProvider(...)
: parentHomeProvider;
// Result: Each TOC gets its own HomeProvider instance
- Create NEW scope
- Inherit parent's scope
Without this flag:
DocumentationSetNavigation
└─ TableOfContentsNavigation (api)
HomeProvider: INHERITED ← shares parent's provider
Can't re-home independently.
With this flag:
DocumentationSetNavigation
└─ TableOfContentsNavigation (api)
HomeProvider: NEW INSTANCE ← own provider!
Can re-home independently!
toc:
- toc: elastic-docs://api
path_prefix: elasticsearch/api
- toc: elastic-docs://guides
path_prefix: elasticsearch/guides
Defines where each TOC appears in the site.
public SiteNavigation(
SiteNavigationFile siteNavigationFile,
IDocumentationContext context,
IReadOnlyCollection<IDocumentationSetNavigation> documentationSetNavigations,
string? sitePrefix)
{
// 1. Initialize SiteNavigation as root
NavigationRoot = this;
// 2. Collect all docset/TOC nodes into dictionary
foreach (var setNavigation in documentationSetNavigations)
{
foreach (var (identifier, node) in setNavigation.TableOfContentNodes)
_nodes.TryAdd(identifier, node);
}
// 3. Process each navigation.yml reference
foreach (var tocRef in siteNavigationFile.TableOfContents)
{
var navItem = CreateSiteTableOfContentsNavigation(tocRef, index++, context, this, null);
if (navItem != null)
items.Add(navItem);
}
}
For each entry in navigation.yml:
private INavigationItem? CreateSiteTableOfContentsNavigation(
SiteTableOfContentsRef tocRef,
int index,
IDocumentationContext context,
INodeNavigationItem<INavigationModel, INavigationItem> parent,
IRootNavigationItem<INavigationModel, INavigationItem>? root)
{
// 1. Calculate final path_prefix
// 2. Look up node by identifier (elastic-docs://api)
// 3. Replace node's HomeProvider ← THE MAGIC! ⚡
// 4. Update parent/index
// 5. Process children (skip nested root nodes)
}
The critical line:
private INavigationItem? CreateSiteTableOfContentsNavigation(...)
{
// ...
homeAccessor.HomeProvider = new NavigationHomeProvider(pathPrefix, siteRoot);
}
This single assignment updates all descendant URLs instantly (O(1)).
Input: Built with AssemblerBuild = true
elastic-docs://
HomeProvider: self
└─ elastic-docs://api
HomeProvider: Instance A (PathPrefix = "")
└─ api/rest.md → URL: /api/rest/
└─ elastic-docs://guides
HomeProvider: Instance B (PathPrefix = "")
└─ guides/start.md → URL: /guides/start/
navigation.yml:
- toc: elastic-docs://api
path_prefix: elasticsearch/api
- toc: elastic-docs://guides
path_prefix: elasticsearch/guides
After Re-homing:
SiteNavigation
└─ elastic-docs://api
HomeProvider: NEW (PathPrefix = "elasticsearch/api") ← Replaced!
└─ api/rest.md → URL: /elasticsearch/api/rest/ ✓
└─ elastic-docs://guides
HomeProvider: NEW (PathPrefix = "elasticsearch/guides") ← Replaced!
└─ guides/start.md → URL: /elasticsearch/guides/start/ ✓
Scenario: Split a docset across the site.
# elastic-docs has both api/ and guides/ TOCs
toc:
- toc: elastic-docs://api
path_prefix: reference/api
- toc: elastic-docs://guides
path_prefix: learn/guides
- Goes here
- Goes there
If TOCs shared their parent's provider, both would get the same prefix. Separate providers enable different prefixes from the same repository.
1. AssemblerBuild Flag Controls Scope Creation
- True: TOCs create own HomeProvider
- False: TOCs inherit parent's HomeProvider
2. HomeProvider is the Re-homing Mechanism
- URLs calculated from
HomeProvider.PathPrefix - Changing provider changes all descendant URLs
- No tree traversal needed
3. Root Nodes Can Be Re-homed
DocumentationSetNavigation- Entire docsetTableOfContentsNavigation- Individual TOC- Must have own provider (not inherited)
4. Non-Root Nodes Inherit
FileNavigationLeaf,FolderNavigation, etc.- Use parent's HomeProvider
- Re-home automatically when parent re-homed
# Required
- toc: elastic-docs://api
path_prefix: elasticsearch/api
# Exception: narrative repository
- toc: docs-content://guides
# path_prefix defaults to "guides"
- Must be unique!
All path_prefix values must be unique across the site.
Declared but not included:
phantoms:
- source: plugins://
Prevents "undeclared navigation" warnings for excluded content.
1. Build with AssemblerBuild = true
→ TOCs get own HomeProvider
2. Collect all nodes into dictionary
→ Indexed by identifier (elastic-docs://api)
3. For each navigation.yml entry:
→ Look up node
→ Replace HomeProvider ← O(1) operation
→ All URLs update automatically
4. Result: Unified site with custom structure
O(1) Re-homing:
// This updates 10,000 URLs instantly:
node.HomeProvider = new NavigationHomeProvider(newPrefix, newRoot);
Why?
- URLs calculated on-demand from HomeProvider
- Not stored in nodes
- Changing provider = all URLs recalculate next access
- Smart caching invalidates on provider change
Pattern 1: Keep docset together
- toc: elastic-docs://
path_prefix: elasticsearch
Pattern 2: Split docset apart
- toc: elastic-docs://api
path_prefix: reference/api
- toc: elastic-docs://guides
path_prefix: learn/guides
Pattern 3: Nest docsets
- toc: products
children:
- toc: elasticsearch://
path_prefix: products/elasticsearch
- toc: kibana://
path_prefix: products/kibana
The assembler enables:
- Build repositories independently (isolated)
- Combine into unified site (assembled)
- Custom URL structure per site
- Split single docset across multiple sections
- O(1) re-homing (no tree reconstruction)
The critical piece:
AssemblerBuild = true causes TableOfContentsNavigation to create own HomeProvider, enabling independent re-homing of TOCs within a docset.
Without this, you can only re-home entire docsets. With it, you can split a docset anywhere.