Diff engine for incremental UI updates.
Compares two UI trees (as Dala.Node structs) and produces a minimal
set of patches to transform the old tree into the new tree.
Patch format
Patches are tuples tagged with an action:
{:replace, id, node} # Replace entire node
{:update_props, id, props} # Update props on existing node (full replacement)
{:patch_node, id, mask, props} # Patch only changed fields (field-mask based)
{:insert, parent_id, index, node} # Insert new node
{:remove, id} # Remove nodeUsage
old_tree = Dala.Node.from_map(old_map, "root")
new_tree = Dala.Node.from_map(new_map, "root")
patches = Dala.Diff.diff(old_tree, new_tree)Node identity
Nodes are identified by the :id field in Dala.Node. This must be
stable across renders for proper reconciliation.
Field masks
When only a few props change, {:patch_node, id, mask, props} is emitted
instead of {:update_props, id, props}. The mask is a 16-bit bitmask
where bit N corresponds to field tag (N+1). If more than half the fields
changed, a full {:update_props, ...} is sent instead.
Summary
Functions
Compute a field mask and changed props map from old and new props.
Compute the diff between two UI trees.
Types
@type patch() :: {:replace, node_id(), Dala.Node.t()} | {:update_props, node_id(), map()} | {:patch_node, node_id(), non_neg_integer(), map()} | {:insert, node_id(), non_neg_integer(), Dala.Node.t()} | {:remove, node_id()}
Functions
@spec compute_field_mask(map(), map()) :: {non_neg_integer(), map()}
Compute a field mask and changed props map from old and new props.
Returns a {mask, changed_map} tuple where mask is a 16-bit bitmask
and changed_map contains only the props that differ.
@spec diff(Dala.Node.t() | map() | nil, Dala.Node.t() | map() | nil) :: [patch()]
Compute the diff between two UI trees.
Returns a list of patches to transform old_tree into new_tree.
Trees can be either Dala.Node structs or raw maps (as produced by
Dala.Ui.Widgets / render/1).