Hierarchical Family¶
When To Use It¶
Use HierarchicalVM<TModel, TVM> when the domain is intrinsically recursive:
folders, notebooks, comment threads, taxonomies, or any tree whose nodes need
parent, depth, and path semantics.
This family exists so you do not have to manually recurse a CompositeVM and
rebuild tree bookkeeping in every app.
Shape And Ownership¶
A hierarchical node owns:
- its
Model - its recursive
Children - structural metadata such as
Parent,Depth,Path,IsRoot, andIsLeaf
Children are lazy by default and can be materialized eagerly where the host needs the whole tree available during construct.
Lifecycle And Messaging¶
The important differences from manual recursive composites are:
- eager trees construct depth-first across the full subtree
- lazy children do not participate until materialized
- structural mutations publish
TreeStructureChangedMessage - cache invalidation publishes property-changed for the children property
The family integrates directly with walk and walk_expanded.
Cross-Language Surface¶
| Concept | C# | Python | TypeScript | Swift |
|---|---|---|---|---|
| Core type | HierarchicalVM<TModel, TVM> |
HierarchicalVM[TModel, TVM] |
HierarchicalVM<TModel, TVM> |
HierarchicalVM<TModel, TVM> |
| Builder | HierarchicalVMBuilder<TModel, TVM> |
HierarchicalVMBuilder[TModel, TVM] |
HierarchicalVMBuilder<TModel, TVM> |
HierarchicalVM<TModel, TVM>.builder() |
| Eager flag | EagerChildren(true) |
eager_children(True) |
eagerChildren(true) |
eagerChildren(true) |
| Invalidate subtree | InvalidateSubtree() |
invalidate_subtree() |
invalidateSubtree() |
invalidateSubtree() |
Example¶
The current Notes Workspace examples do not subclass HierarchicalVM in
any flavor. Each NotebooksRootVM owns a flat notebook collection plus
roots / childrenOf / walk projections and republishes
TreeStructureChangedMessage, which gives the host tree-shaped state without
making the VM itself a recursive node tree.
Use HierarchicalVM when the VM graph should actually be recursive:
These concise examples show the canonical builder path per flavor. C#, Swift,
and TypeScript supply a TestNode factory for their concrete recursive node;
Python can build plain HierarchicalVM directly and uses vm_factory(...) only
when a subclass is required.
var root = HierarchicalVMBuilder<string, TestNode>.Empty
.Model("root")
.ChildrenFactory(_ => Array.Empty<TestNode>())
.Services(hub, dispatcher)
.VmFactory(ctx => new TestNode(
ctx.Model, ctx.ChildrenFactory, ctx.Hub, ctx.Dispatcher,
ctx.Name, ctx.Hint, ctx.EagerChildren))
.Build();
root = (
HierarchicalVMBuilder()
.model("root")
.children_factory(lambda _parent: [])
.services(hub, dispatcher)
.build()
)
const root = new HierarchicalVMBuilder<string, TestNode>()
.model("root")
.childrenFactory((_parent) => [])
.services(hub, dispatcher)
.vmFactory((ctx) => new TestNode(ctx))
.build();
let root = try HierarchicalVM<String, TestNode>.builder()
.model("root")
.childrenFactory { _ in [] }
.services(hub: hub, dispatcher: dispatcher)
.vmFactory { model, childrenFactory, hub, dispatcher, name, hint, eager in
TestNode(
model: model,
childrenFactory: childrenFactory,
hub: hub,
dispatcher: dispatcher,
name: name,
hint: hint,
eagerChildren: eager
)
}
.build()
That split is the practical rule: use the tree primitive when the tree is real, not simply because the UI displays indentation.
Common Pitfalls¶
- Recursing
CompositeVMinstead of using the tree primitive and then having to rebuildParent,Depth, andPathyourself. - Expecting lazy children to exist during construct without materializing them.
- Treating cache invalidation as structural mutation. It is a refresh contract, not an add/remove event.
- Reparenting a node under itself or its descendants. The contract rejects these cycles.