genesis_tree 0.4.0
genesis_tree: ^0.4.0 copied to clipboard
A framework-agnostic Seed/Branch keyed-reconcile tree engine — Flutter's element/reconciliation model extracted to pure, bare-VM Dart.
0.4.0 #
Stable promotion of 0.4.0-dev.1: the same content, published on the stable rung on Nico's promotion call (2026-09-12). The wave is additive over 0.3.1; the 0.4.0-dev.1 entry below carries the full list.
Changelog #
0.4.0-dev.1 #
Prerelease on the dev rung: this wave is additive over 0.3.1 (nothing published in 0.3.1 is removed or changed) and its API is still moving; stable 0.4.0 is a separate, human-promoted act.
- Add the provider composition family to the tree composition layer: the dependency-free provider primitives (
Provider,ProviderScope,ProviderTreeContext,AvailabilityRegistry,LifecycleProvider) and their contract suite, lifted out ofgrid_engine's private sources and exported publicly. The provider kind-swap check is an independentStateErrorin every build mode. - Add the lifecycle phase guard (
TreeLifecyclePhase,TreeLifecyclePhaseGuard,TreeOwner.lifecyclePhaseGuard): one lifecycle phase definition for tree drivers, composed with the owner flush marker; retained-context dependency registration before mutation is rejected. - Add the proxy provider ladder (
ProxyProviderthroughProxyProvider6): one- through six-input proxy providers that derive owned ambient values through the existing provider lifecycle and availability registry, sharing replacement and teardown ownership across providers. - Add lifecycle participation for long-lived non-node values (
TreeLifecycleParticipant,TreeSnapshotReader,TreeWatchingReader): call-scoped snapshot and watching readers driven through the shared phase guard, with mounted identity retained across reconciliation; only participants the provider constructed are disposed. Retained reader use and invalid in-place participant replacement fail loudly in every build mode. - Add dependency supersession scopes (
TreeDependencyScope):TreeLifecycleParticipant.didChangeDependenciesreceives a retainable scope that becomes non-current on the next pass or on teardown, and the provider invalidates scopes before participant disposal for owned and adopted participants alike, so continuation staleness stays out of the synchronous build path. - Fix: mid-flush dirties are guarded by build ancestry, so a cascade-dirtied descendant no longer trips the one-build-per-branch invariant.
0.3.1 #
- Three tree invariants were debug-only assertions and vanished from release builds; release builds now enforce all three, throwing
StateErrorwith the message the assertion carried. (1)TreeOwner.flushrejects a branch re-dirtied after it was already built in the pass — a branch that callssetStatefrom inside its ownbuildused to drain forever in release; the pass is bounded at one build per branch. (2)Branch.updateChildrenrejects duplicate non-null sibling keys before it touches the old child list, so a rejected reconcile leaves the mounted tree unchanged. (3)Branch.updaterejects a seed that failscanUpdateinstead of swapping in an incompatible config. No API change; code that relied on catchingAssertionErrorfrom these three paths now catchesStateError.
0.3.0 #
- Breaking: aspect-scoped inherited dependencies.
InheritedBranch.addDependentis nowaddDependent(Branch branch, {Object? aspect}), anddependOnInheritedSeedOfExactType<T>()gained an optional{Object? aspect}onBranch,TreeContext, andSproutContext. Migration for a subclass that overridesaddDependent: add the named parameter and forward it —void addDependent(Branch branch, {Object? aspect}) => super.addDependent(branch, aspect: aspect);— which preserves the base provider's rejection of an aspect it has no vocabulary for. An externalTreeContextimplementation adds{Object? aspect}to itsdependOnInheritedSeedOfExactTypeand passes it straight through to the handle it wraps. Call sites that pass no aspect are unchanged. - Add
InheritedModelSeed<T, A>andInheritedModelBranch<T, A>: an ambient value whose dependents may subscribe to a single ASPECT of it. A dependent that passesaspect:is invalidated only whenupdateShouldNotifyDependentreports the change as touching one of the aspects it asked for; omitting the aspect keeps the whole-value dependencyInheritedSeedalways gave. Lookup is unchanged — still the nearest provider of exact value-typeT— so a plainInheritedSeed<T>provider throwsArgumentErrorfor a non-null aspect, and a model provider throws for an aspect of the wrong type. Experimental, like the rest of the composition layer.
0.2.0 #
- Breaking:
debugFillPropertiesnow receives aDiagnosticsBuilder— replaceproperties.add(...)list calls with the builderadd(); wire format unchanged.
0.1.6 #
- Add
genesis_foundation ^0.1.1below the tree spine and re-export its dependency-free diagnostics protocol and typed snapshot contract. The first-classKeytypes remain owned bygenesis_tree.
0.1.5 #
- Docs only, no API changes. The README gains "The artifact layer —
deliberately not shipped":
genesis_treereconciles desired state into live identity and stops there — what that identity spawns and owns is the consumer's, unbundled into four pieces (artifacts / owner / protocol / affordance scopes), withgenesis_typesettingandgenesis_perceptionas the worked examples. The composition-layer list also catches up to 0.1.4 (MultiChildSeed, theSingleChildSeed/Nestchain).
0.1.4 #
-
Add the single-child chain vocabulary.
SingleChildStatelessSeedandSingleChildStatefulSeed(withSingleChildState) are the single-child analogues ofStatelessSeed/StatefulSeed: their build receives the downstream child to embed (buildWithChild).Neststacks a list of them into a vertical spine, each wrapping the next down to one leafchild— theNested/MultiProvidershape, generic (no inherited-value semantics baked in).MultiChildSeedfans out horizontally;Nestcomposes vertically. Fullyconst-constructible: the children are referenced as authored and never reconstructed — each link's downstream is supplied at the branch layer — so an unchangedNestprunes its whole chain on reconcile, while a change to the leaf or any link propagates through it. EXPERIMENTAL. -
Add
getInheritedSeedOfExactType<T>()— the dependency-free counterpart ofdependOnInheritedSeedOfExactType<T>(). Same nearest-ancestor lookup, same value result, but the caller is not registered as a dependent: the returned value is a snapshot, and a later change to the provided value does not rebuild the reader. Use it for one-shot reads — grabbing an ambient service inState.initState, inside an effect, during teardown — and keep the depend variant wherever the branch must rebuild on change. Available onBranch,TreeContext, andSproutContext. Breaking for externalTreeContextimplementers (a new interface member); handles that wrap the canonical handle just delegate it. -
StatefulBranchnow asserts (debug-only) whendependOnInheritedSeedOfExactTypeis called duringinitStateordispose. IninitStatethe natural cache-the-result pattern goes stale when the provider changes (initState never re-runs) — read dependency-free with the new variant, or cache-and-track indidChangeDependencies. Indisposethe branch is unmounting and can never observe a change. -
There is deliberately no "get the provider element" lookup (Flutter's
getElementForInheritedWidgetOfExactType): the capability handle never exposes aBranch.
0.1.3 #
- Breaking: a first-class
Keyvalue-type.Seed.key(andBranch.key,TreeContext.key) is now typedKey?instead ofObject?. Two concrete kinds ship:ValueKey<T>(value)(value equality; the type parameter is part of identity, soValueKey<int>(1) != ValueKey<num>(1)) andObjectKey(value)(identity equality), plus an ergonomicconst Key(String)factory that builds aValueKey<String>. The typed key gives reconciliation identity intent and type-safety and a shared identity story for keyed list reconcile. Keyis open (abstract, not sealed): domains extend it with their own key kinds. There is deliberately noGlobalKey— cross-tree lookup is refused so the tree stays one-way (cross-boundary references pass handles through the parent) — and noLocalKeylayer (vacuous without a global key).- Migration: replace
key: 'id'withkey: ValueKey('id')(orKey('id')), andseed.key == 'id'comparisons withseed.key == ValueKey('id'). - Add
MultiChildSeed/MultiChildBranchto the (experimental) composition layer: a config-declared multi-child container that keyed-reconciles itsList<Seed> childrenviaBranch.updateChildren— theMultiChildRenderObjectElementanalogue, beside the single-childStatelessSeed/StatefulSeed/Sprout. Matched children (keyed by key, unkeyed by position) keep their branch identity across rebuilds; new children mount, removed children unmount, and child order followschildren. The identical-config skip fast path and the duplicate-sibling-key debug guard are inherited fromBranch. Additive; the spine is unchanged.
0.1.2 #
- Add a debug assertion that sibling keys are unique within
updateChildren(debug-mode only; surfaces duplicate-key reconcile bugs earlier, no release-mode behavior change).
0.1.1 #
- Docs: package documentation (README, dartdoc) made self-contained for pub.flutter-io.cn.
- Reworded the
TreeContextuse-after-unmountStateErrormessage (text only; no behavior change).
0.1.0 #
-
Initial release: the Seed/Branch keyed-reconcile engine — TreeContext (a separate capability handle), TreeOwner, the composition layer (Stateless/Stateful/State, InheritedSeed, Watch, Sprout), and the identical-config skip fast path.
Pre-1.0 and experimental; APIs may change before 1.0.