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.
Defined Under Namespace
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
-
.diff(old_tree, new_tree) ⇒ Array<Hash>
Diff two normalized trees, producing an array of patch operations.
- .exists?(tree, id) ⇒ Boolean
- .find(tree, id) ⇒ Object
- .find_all(tree, &predicate) ⇒ Object
- .find_first(tree, &predicate) ⇒ Object
- .ids(tree) ⇒ Object
-
.node_to_wire(node) ⇒ Hash
Convert a Node to a plain wire-ready Hash (recursive).
-
.normalize(tree, registry: nil) ⇒ Array<Node>
Normalize a tree for wire transport.
-
.normalize_view(tree, registry: nil) ⇒ Node
Normalize a top-level app view and require explicit windows.
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 }
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
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).
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.
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.
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 |