6.2.5. Composite Family¶
6.2.5.1. When To Use It¶
Use CompositeVM<VM> when the parent owns an ordered homogeneous child list and
one child may be current. Tabs, note lists, result lists, and other "collection
plus selection" workflows belong here.
Use the modeled composite when children are projected from models during construction instead of being provided directly.
6.2.5.2. Shape And Ownership¶
CompositeVM adds one core concept on top of a collection container:
Currentmay be one child ornull
Everything else follows from that contract: guarded selection helpers,
foreground updates of IsCurrent, and optional builder hooks for initial
selection and current-changed callbacks.
Composite implements the selectable extension of the shared
VM Collection Contract. Its atomic move keeps
the identical child, parent, lifecycle, subscriptions, and Current reference.
Every child has one authoritative owning parent. Adding or inserting a child that belongs to another mutable composite or group atomically removes it from the old parent first. Duplicate identity and ancestor cycles are rejected. If the destination attach fails, old membership, index, and selection are restored without publishing a partial transfer.
The destination and staged old parent stay isolated until commit or rollback. Structural mutation attempted re-entrantly from an auto-construction or population hook is rejected before it can invalidate rollback state. Selection validation and assignment share the membership gate, and deferred selection rechecks that the candidate is still a child. The transaction also rechecks destination disposal after auto-construction before it commits membership. If user lifecycle code fails during compensation, VMx surfaces that rollback failure instead of claiming that lifecycle state was restored exactly (ADR-0118).
Old-parent disposal requested by an attachment hook waits for commit or rollback. A committed transfer publishes old removal and new addition before a throwing/result-based flavor surfaces any deferred disposal failure. When attachment already failed, that earlier error remains primary after rollback and disposal complete. A lazy population that already committed stays materialized rather than evaluating its factory again (ADR-0122).
6.2.5.3. Lifecycle And Messaging¶
The composite owns both child lifecycle and selection messaging:
construct()waits for every child before the composite reports constructeddestruct()clearsCurrentbefore destructing childrenCurrentchanges publish the parentCurrentproperty and the affected children'sIsCurrent- disposal requested by a current-changed callback is deferred until the selection notification finishes and does not deadlock the callback
AsyncSelection(true)routes selection work through the foreground dispatcher- a non-batched move emits one move event with both indices; a batched move is represented by the outer reset
6.2.5.4. Cross-Language Surface¶
| Concept | C# | Python | TypeScript | Swift | Rust |
|---|---|---|---|---|---|
| Type | CompositeVM<VM> |
CompositeVM[VM] |
CompositeVM<VM> |
CompositeVM<VM> |
CompositeVm<VM> |
| Modeled type | CompositeVMOfM<M, VM> |
CompositeVMOf[M, VM] |
CompositeVMOf<M, VM> |
CompositeVMOf<M, VM> |
ModeledCompositeVm<M, VM> |
| Selection slot | Current |
current |
current |
current |
current() |
| Initial selector hook | Current(selector) |
current(selector) |
current(selector) |
current(selector) |
current(selector) |
6.2.5.5. Example¶
var tabs = CompositeVM<ComponentVM<TabModel>>.Builder()
.Name("tab-bar")
.Services(hub, dispatcher)
.Children(() => new[] { home, settings })
.Build();
tabs = (
CompositeVM[ComponentVMOf[TabModel]]
.builder()
.name("tab-bar")
.services(hub, dispatcher)
.children(lambda: [home, settings])
.build()
)
const tabs = CompositeVM.builder<ComponentVMOf<TabModel>>()
.name("tab-bar")
.services(hub, dispatcher)
.children(() => [home, settings])
.build();
let tabs = try CompositeVM<ComponentVMOf<TabModel>>.builder()
.name("tab-bar")
.services(hub: hub, dispatcher: dispatcher)
.children { [home, settings] }
.build()
let tabs = CompositeVm::<ComponentVm<TabModel>>::builder()
.name("tab-bar")
.services(hub, dispatcher)
.children(|| vec![home.clone(), settings.clone()])
.build()?;
Rust ships both CompositeVm and ModeledCompositeVm; the completed Rust
parity ledger
records its removal and filtered-cursor edge evidence.
6.2.5.6. Common Pitfalls¶
- Using a composite for recursive trees.
HierarchicalVMcarries the tree semantics directly. - Forgetting that
Currentmust always be a contained child ornull. - Treating one child identity as simultaneous membership in multiple containers. Mutable attachment transfers ownership; aggregate slots must be released by replacing or rebuilding the aggregate instead.
- Assuming add-after-construct auto-constructs by default. It does not unless
AutoConstructOnAdd(true)is enabled. - Updating selection predicates without wiring current-changed triggers into commands that depend on selection.