Module: Plushie::Tree

Defined in:
lib/plushie/tree.rb,
lib/plushie/tree/diff.rb,
lib/plushie/tree/search.rb

Overview

Utilities for working with UI trees.

Provides normalization (Tree), search (Tree::Search), and diffing (Tree::Diff) for Node trees. Search and diff are also available directly on Tree via delegation.

See Also:

  • "Patch"

Defined Under Namespace

Modules: Diff, Search

Constant Summary collapse

MAX_DEPTH =

Maximum tree depth before raising. Protects against infinite recursion from circular widget compositions.

256
DEPTH_WARNING =

Depth at which a warning is emitted (approaching MAX_DEPTH).

200
VALID_ID_PATTERN =

Printable ASCII range (0x21-0x7E), excludes space and control characters.

/\A[\x21-\x7e]+\z/

Class Method Summary collapse

Class Method Details

.diff(old_tree, new_tree) ⇒ Array<Hash>

Diff two normalized trees, producing an array of patch operations.

Each op is a Hash with string keys matching the wire protocol: { "op" => "replace_node", "path" => [...], "node" => {...} } { "op" => "update_props", "path" => [...], "props" => {...} } { "op" => "insert_child", "path" => [...], "index" => n, "node" => {...} } { "op" => "remove_child", "path" => [...], "index" => n }

Parameters:

  • old_tree (Node, nil)

    previous normalized tree

  • new_tree (Node, nil)

    current normalized tree

Returns:

  • (Array<Hash>)

    patch operations



98
# File 'lib/plushie/tree.rb', line 98

def self.diff(old_tree, new_tree) = Diff.diff(old_tree, new_tree)

.exists?(tree, id) ⇒ Boolean

Returns:

  • (Boolean)

See Also:



22
23
# File 'lib/plushie/tree.rb', line 22

def self.exists?(tree, id) = Search.exists?(tree, id)
# @see Tree::Search#ids

.find(tree, id) ⇒ Object



20
21
# File 'lib/plushie/tree.rb', line 20

def self.find(tree, id) = Search.find(tree, id)
# @see Tree::Search#exists?

.find_all(tree, &predicate) ⇒ Object



28
# File 'lib/plushie/tree.rb', line 28

def self.find_all(tree, &predicate) = Search.find_all(tree, &predicate)

.find_first(tree, &predicate) ⇒ Object



26
27
# File 'lib/plushie/tree.rb', line 26

def self.find_first(tree, &predicate) = Search.find_first(tree, &predicate)
# @see Tree::Search#find_all

.ids(tree) ⇒ Object



24
25
# File 'lib/plushie/tree.rb', line 24

def self.ids(tree) = Search.ids(tree)
# @see Tree::Search#find_first

.node_to_wire(node) ⇒ Hash

Convert a Node to a plain wire-ready Hash (recursive).

Parameters:

Returns:

  • (Hash)


104
105
106
107
108
109
110
111
# File 'lib/plushie/tree.rb', line 104

def self.node_to_wire(node)
  {
    "id" => node.id,
    "type" => node.type,
    "props" => wire_ready_props?(node) ? node.props : Encode.encode_props(node.props),
    "children" => node.children.map { |c| node_to_wire(c) }
  }
end

.normalize(tree, registry: nil) ⇒ Array<Node>

Normalize a tree for wire transport. Converts symbol prop values to strings via Encode, resolves scoped IDs, and validates tree structure.

When a canvas widget registry is provided, canvas widget placeholders are detected and rendered with stored state.

Parameters:

  • tree (Node, Array<Node>)
  • registry (Hash, nil) (defaults to: nil)

    canvas widget registry for state lookup

Returns:

  • (Array<Node>)

    normalized tree (always an array)



52
53
54
55
56
57
58
59
# File 'lib/plushie/tree.rb', line 52

def self.normalize(tree, registry: nil)
  return [Node.new(id: "root", type: "container")] if tree.nil?

  trees = (tree.is_a?(Array) ? tree : [tree]).compact
  normalized = trees.map { |node| normalize_node(node, "", registry, nil, 0) }
  check_duplicate_ids!(normalized)
  normalized.map { |node| post_normalize(node) }
end

.normalize_view(tree, registry: nil) ⇒ Node

Normalize a top-level app view and require explicit windows.

A nil tree is treated as "no UI": returns a root container with no child windows so the renderer still has a structurally valid snapshot to diff. Useful for transition, loading, or error states where the app has nothing to display yet.

Parameters:

  • tree (Node, Array<Node>, nil)
  • registry (Hash, nil) (defaults to: nil)

Returns:

  • (Node)

    normalized synthetic root or window node



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/plushie/tree.rb', line 71

def self.normalize_view(tree, registry: nil)
  return Node.new(id: "root", type: "root", children: []) if tree.nil?

  windows = normalize(tree, registry: registry)

  if windows.empty? || !windows.all? { |node| node.type == "window" }
    raise ArgumentError, "view must return a window node or an array of window nodes"
  end

  Node.new(id: "root", type: "root", children: windows)
end