Search documentation of Plushie

# Plushie.Tree (Plushie v0.7.2)

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. Search and diff logic live in dedicated submodules; this module delegates to them.

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

## Summary

### Types

tree_node()

### Functions

diff(old_tree, new_tree)

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

exists?(tree, id)

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

find(tree, id)

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

find(tree, id, window_id)

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

find_all(node, fun)

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

find_first(node, fun)

Finds the first node in a tree for which `fun` returns truthy.

find_local(tree, local_id)

Finds a node by local ID.

find_local(tree, local_id, window_id)

Finds a node by local ID within a specific window.

ids(tree)

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

normalize(tree)

Normalizes a UI tree into the canonical node shape.

normalize(nodes, widget_states)

Normalizes a UI tree with explicit widget states.

text_of(node)

Extracts the display text from a node.

window_id_for(tree, target_id)

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, 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, 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_first(node, fun)

    @spec find_first(
      node :: tree_node() | nil,
      fun :: (tree_node() -> as_boolean(term()))
    ) ::
      tree_node() | nil

Finds the first node in a tree for which `fun` returns truthy.

Walks the tree depth-first and returns immediately on the first match, avoiding a full tree traversal. Returns `nil` if no node 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(tree)

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

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

# normalize(tree)

    @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.

# normalize(nodes, widget_states)

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

Normalizes a UI tree with explicit widget states.

Same as `normalize/1` but uses the provided `widget_states` map for stateful widget rendering instead of relying on the process dictionary.

# text_of(node)

    @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.