Plushie.Tree (Plushie v0.6.0)

Copy Markdown View Source

Utilities for working with Plushie UI trees.

A UI tree is a plain map (or list of maps) with the shape:

%{
  id: "unique-id",
  type: "button",
  props: %{label: "Click me"},
  children: [...]
}

This module provides normalization, tree search, and diffing for incremental renderer updates.

Normalization is strict. Missing required fields, duplicate sibling IDs, and malformed children raise immediately instead of being silently repaired.

Summary

Functions

Compares two normalized trees and returns a list of patch operations.

Returns true if a node with the given id exists in the tree.

Finds the node in a tree whose :id exactly matches the given scoped ID.

Finds a node by exact scoped ID within a specific window.

Finds all nodes in a tree for which fun returns truthy.

Finds a node by local ID.

Finds a node by local ID within a specific window.

Returns a flat list of all node IDs in the tree (depth-first order).

Normalizes a UI tree into the canonical node shape.

Extracts the display text from a node.

Returns the window ID that owns an exact scoped target ID.

Types

tree_node()

@type tree_node() :: %{
  id: String.t(),
  type: String.t(),
  props: %{required(atom()) => term()},
  children: [tree_node()]
}

Functions

diff(old_tree, new_tree)

@spec diff(old_tree :: map() | nil, new_tree :: map() | nil) :: [map()]

Compares two normalized trees and returns a list of patch operations.

Patch operations:

  • %{op: "replace_node", path: [int], node: map} -- replace entire subtree
  • %{op: "update_props", path: [int], props: map} -- merge props at path
  • %{op: "insert_child", path: [int], index: int, node: map} -- insert child
  • %{op: "remove_child", path: [int], index: int} -- remove child at index

Path is a list of child indices from the root. An empty path [] means the root node.

exists?(tree, id)

@spec exists?(tree :: map() | nil, id :: String.t()) :: boolean()

Returns true if a node with the given id exists in the tree.

Checks exact scoped IDs only.

find(tree, target_id)

@spec find(tree :: tree_node(), id :: String.t()) :: tree_node() | nil

Finds the node in a tree whose :id exactly matches the given scoped ID.

This does exact matching only. It does not fall back to local-ID guessing. If the same ID appears in multiple windows, this raises and you must use find/3 with a window ID.

find(tree, target_id, window_id)

@spec find(tree :: tree_node(), id :: String.t(), window_id :: String.t()) ::
  tree_node() | nil

Finds a node by exact scoped ID within a specific window.

Searches only inside the window whose id matches window_id. Returns the node map, or nil if not found.

find_all(node, fun)

@spec find_all(node :: tree_node() | nil, fun :: (tree_node() -> as_boolean(term()))) ::
  [tree_node()]

Finds all nodes in a tree for which fun returns truthy.

Walks the entire tree depth-first and accumulates all matches.

find_local(tree, local_id)

@spec find_local(tree :: tree_node(), id :: String.t()) :: tree_node() | nil

Finds a node by local ID.

This matches the last segment of each node ID. It is for callers that intentionally want local widget IDs instead of full scoped paths.

Raises if more than one node has the same local ID.

find_local(tree, local_id, window_id)

@spec find_local(tree :: tree_node(), id :: String.t(), window_id :: String.t()) ::
  tree_node() | nil

Finds a node by local ID within a specific window.

Searches only inside the window whose id matches window_id. Raises if more than one node in that window has the same local ID.

ids(arg1)

@spec ids(tree :: map() | nil) :: [String.t()]

Returns a flat list of all node IDs in the tree (depth-first order).

normalize(nodes)

@spec normalize(tree :: nil | tree_node() | [tree_node()] | struct()) :: tree_node()

Normalizes a UI tree into the canonical node shape.

Accepts:

  • nil -- returns an empty root container
  • a single node map -- normalizes and returns it
  • a list of node maps -- wraps them in a synthetic root container

Every normalized node has :id, :type, :props, and :children. Prop values are encoded for the wire format. Missing :props and :children default to %{} and []. Missing required fields or malformed shapes raise.

text_of(arg1)

@spec text_of(node :: tree_node()) :: String.t() | nil

Extracts the display text from a node.

Returns the "content" prop for text-like widgets, or nil if no display text is available.

window_id_for(tree, target_id)

@spec window_id_for(tree :: tree_node(), id :: String.t()) :: String.t() | nil

Returns the window ID that owns an exact scoped target ID.

Returns nil when the target is not inside any window. Raises if the same target appears in more than one window.