On this page
Recursive, schema-neutral traversal helpers for the authoritative layer tree of pixel and role leaves and nested groups, mirroring the client's layer-tree model.
#server.src.pixelweaver.layer_tree
#server.src.pixelweaver.layer_tree
Recursive traversal helpers for the authoritative layer tree.
The server layer model mirrors the client (src/lib/layers/layer-tree.svelte.ts): canvas.layers is an ordered list (bottom-to-top) whose entries are either pixel/role LEAF layers or GROUP dicts. A group carries type == "group", a children list, and an expanded flag; leaves carry type == "pixel" (absent type is treated as a leaf for forward-compatibility with pre-tree saves).
A FLAT list is simply a tree with every leaf at the root -- which is why projects saved before groups existed load untouched (the load-time migration is a no-op transform: a flat list already IS a valid root-level tree).
Every function here is schema-neutral: it takes the raw list[dict] layer list (or a CanvasState's .layers) and never mutates unless documented.
#is_group
def is_group(layer: dict[str, Any]) -> boolWhether layer is a group node (has children).
#iter_layers
def iter_layers(layers: list[dict[str, Any]]) -> Iterator[dict[str, Any]]Yield every node depth-first (a group is yielded before its children).
#iter_leaves
def iter_leaves(layers: list[dict[str, Any]]) -> Iterator[dict[str, Any]]Yield only non-group LEAF layers, depth-first (bottom-to-top order).
#leaf_ids
def leaf_ids(layers: list[dict[str, Any]]) -> list[str]Every leaf layer id in depth-first order.
#all_ids
def all_ids(layers: list[dict[str, Any]]) -> list[str]Every node id (groups + leaves) in depth-first order.
#find_layer
def find_layer(layers: list[dict[str, Any]], layer_id: str) -> dict[str, Any] | NoneFind a layer dict by id anywhere in the tree (groups included).
#LayerContext
Where a layer sits: the node, its live sibling list, its index, and the id of the enclosing group (None for a root-level layer).
#find_context
def find_context(layers: list[dict[str, Any]], layer_id: str, parent_id: str | None=None) -> LayerContext | NoneFind a layer and its parent array by id, searching the whole tree.
Mirrors the client findLayerContext: siblings is the LIVE list the layer lives in (mutating it mutates the tree), parent_id is the enclosing group's id or None at the root.
#resolve_children
def resolve_children(layers: list[dict[str, Any]], parent_id: str | None) -> list[dict[str, Any]]Return the live child list for parent_id (None => root list).
Hard error if parent_id names a missing layer or a non-group. A group with a missing children key is initialized to [] so callers can splice into the live list.
#any_role_leaf
def any_role_leaf(layers: list[dict[str, Any]]) -> boolWhether any LEAF layer in the tree is role-kind.
#validate_tree
def validate_tree(layers: Any) -> NoneHard error on structurally corrupt layer-tree data (load-time guard).
A flat list is valid (all leaves at root). Corruption = a non-dict node, a node missing a string id, a group whose children is not a list, a leaf carrying a children list, or a duplicate id anywhere in the tree.