# `Plushie.Tree`
[🔗](https://github.com/plushie-ui/plushie-elixir/blob/v0.6.0/lib/plushie/tree.ex#L1)

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.

# `tree_node`

```elixir
@type tree_node() :: %{
  id: String.t(),
  type: String.t(),
  props: %{required(atom()) =&gt; term()},
  children: [tree_node()]
}
```

# `diff`

```elixir
@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?`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@spec find_all(node :: tree_node() | nil, fun :: (tree_node() -&gt; 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`

```elixir
@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`

```elixir
@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`

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

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

# `normalize`

```elixir
@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`

```elixir
@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`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
