Skip to main content

Module layout

Module layout 

Source
Expand description

Phase E.4.B Phase 4 — pure layout-tree helpers (Rust port of layoutTree.ts). Pure layout-tree helper functions for the srv E.4.B reducer.

Phase 4 of docs/specs/srv-phase-e4b-formal-spec-2026-05-03.md. Ports the 11 pure-ish action handlers from frontend/layout/lib/layoutTree.ts to Rust. These functions take &mut LayoutNode (the tree root) and operation params; they have no I/O and no side effects. The reducer arms (Phase 5) will call these.

Test oracle: the 30 tests in frontend/layout/tests/layoutTree.test.ts (shipped in PR #686) — the Rust implementations must produce identical state transitions for identical inputs.

Structs§

InsertCandidate 🔒
Candidate for insertion location, collected during tree traversal.

Enums§

LayoutError

Constants§

DEFAULT_MAX_CHILDREN
Maximum children before insert_node’s heuristic descends. Mirrors frontend/layout/lib/layoutTree.ts::DEFAULT_MAX_CHILDREN.
DEFAULT_NODE_SIZE
Default flex size for newly-inserted nodes. Mirrors frontend/layout/lib/types.ts::DefaultNodeSize.

Functions§

activate_stack_member
Make block_id the visible tab of the pane whose stack holds it. Returns false when no pane’s stack holds it. SPEC_PANE_TABS_REDUCER_COMMANDS_2026_09_18.md §3.2 (LayoutStackActivate).
balance_node
Port of balanceNode in frontend/layout/lib/layoutNode.ts. Recursively normalizes the tree: validates each node, alternates child flex direction, hoists redundant single-child-branch chains, drops empty branches, and collapses a branch with a single leaf child into that leaf.
collect_insert_candidates 🔒
delete_node
Remove the node with the given id from the tree. Returns Ok(()) on success, or Err(NodeNotFound) if node_id is not in the tree. Callers are responsible for clearing focused_node_id if needed (spec §7.1). Collapses single-child parents.
delete_recursive 🔒
Recursive delete. Returns true when the node was found and removed, false when not found.
enforce_minimized_locks
Write-point enforcement of the minimize lock (the invariant-at-the-write companion to prune_dangling_block_refs, per SPEC_LAYOUT_MINIMIZE_LOCKED_STATE_REDESIGN_2026_07_16.md): snap every locked node’s size back to its recorded minimizedLockedSize, returning the delta to the nearest unlocked sibling (next preferred, then previous) so the parent’s flex-unit budget is conserved. It no longer has a frontend twin: since minimize became a display mode (#2197) the frontend drops minimizedLockedSize when it loads a tree (rebuildMinimizedSet in frontend/layout/lib/layoutMinimize.ts), so only a tree that pass has not migrated yet carries a size to snap to. Returns the number of snapped nodes (0 = tree already honored the locks — the common case, cheap to call unconditionally on every write).
ensure_group_node 🔒
If node is a leaf (has data), promote it to a group by wrapping its data in an intermediate child. Mirrors TypeScript addIntermediateNode in frontend/layout/lib/layoutTree.ts.
find_leaf_containing_block
Find the (first) leaf that has block_id as its visible block OR as any member of its block_stack. Unlike [find_node_id_by_block], which only matches the visible block, this finds the pane a background tab lives in. Returns the leaf node itself.
find_next_insert_location 🔒
Find the best insertion location using the TypeScript scoring heuristic (Math.pow(depth, index + maxChildren)). Collects all candidates, then sorts by ascending score. Mirrors findNextInsertLocation in frontend/layout/lib/layoutNode.ts.
find_node_by_id
Find a node by id (immutable).
find_node_by_id_mut
Find a node by id (mutable).
find_parent_id 🔒
first_dangling_leaf_id 🔒
First leaf (depth-first) whose data.block_id is set but not present in live_block_ids. data.block_id is only ever non-empty on leaves — container/group nodes have data: None (see ensure_group_node).
insert_node
Insert node using the TypeScript scoring heuristic (up to DEFAULT_MAX_CHILDREN per node before descending). If the target location is a leaf, it is promoted to a group first via ensure_group_node. Mirrors insertNode / addChildAt in frontend/layout/lib/layoutTree.ts.
insert_node_at_index
Insert node at the location identified by index_arr — an ordered sequence of child indices (e.g. [0, 2] = root.children[0].children at index 3). The node is inserted AFTER the position identified by the last index.
is_effectively_minimized
True when a node renders as minimized: a locked leaf, or a branch whose every child is effectively minimized (a stacked chip strip). Mirrors isEffectivelyMinimized in frontend/layout/lib/layoutInvariants.ts. Reducer guards use THIS (not is_node_locked): a fully-collapsed branch has no branch-level marker in the display-mode model, but its stored size becomes load-bearing again the moment a child restores — so it must be just as untargetable as a minimized leaf.
is_node_locked
A minimize-locked node: a minimized leaf (minimizedSize), a slipped header (slipMinimize), or a dissolved column (columnDissolve). The fields are written by the frontend minimize subsystem and round-trip through the untyped extra catch-all on this side. Mirrors isNodeLocked in frontend/layout/lib/layoutMinimize.ts.
leaf_members
A leaf’s members: its block_stack, or [block_id] for a leaf with no stack (mirrors the frontend’s effectiveStack, layoutStack.ts).
move_node
Move the node identified by node_id to be the child at index under the node identified by new_parent_id. Resets the moved node’s size to DEFAULT_NODE_SIZE unless it stays under the same parent.
move_stack_member
Move block_id to position relative to target_block_id: a same-leaf reorder when both are already members of one leaf’s stack, otherwise a cross-leaf move (removed from its source leaf via remove_stack_member, placed into target_block_id’s leaf via a position-aware push). Returns false, leaving the tree untouched, when either block doesn’t exist in the tree.
next_visible_member
Which tab becomes visible once the one at removed_idx of the ORIGINAL stack is gone: the nearest eligible member to its right, else the nearest to its left. remaining is the stack with it removed; eligible filters (e.g. “still exists”). The one rule every removal path uses — frontend removeMemberFromStack mirrors it (frontend/layout/lib/stackMembers.ts). SPEC_PANE_TABS_REDUCER_COMMANDS_2026_09_18.md §3.6.
prune_dangling_block_refs
Removes every leaf whose data.block_id is not in live_block_ids, using the same collapse semantics as an explicit user delete (delete_node’s single-child-parent promotion). Returns the number of leaves pruned (0 = tree was already clean — the common case, so this is cheap to call unconditionally on every write).
prune_dangling_stack_members 🔒
Recursively drop dead entries from every leaf’s block_stack. The active member (data.block_id) is never removed here — by the time this runs, the caller’s whole-leaf pruning loop has already guaranteed it’s live. Returns the number of stack entries removed.
push_stack_member
Add block_id to the tab stack of the pane holding target_block_id (as its visible tab or a background member). A pane with no stack yet becomes a two-member stack. Already a member → only (re)activated if asked. Returns false when no pane holds target_block_id. SPEC_PANE_TABS_REDUCER_COMMANDS_2026_09_18.md §3.2 (LayoutStackPush).
push_stack_member_at 🔒
Position-aware variant of push_stack_member: adds block_id to the stack of the leaf holding target_block_id, at position relative to target_block_id instead of always appending. Promotes a single-block leaf (empty block_stack) into a real stack first, exactly like push_stack_member does.
reactivate_dangling_active_stack_members 🔒
Recursively repair leaves whose ACTIVE block (data.block_id) is dangling but a live sibling still exists in block_stack: drop the dead entry from the stack and reactivate the first remaining live member (block_id/active_block_id both updated). Leaves with no live sibling are left with block_id still pointing at the dangling id — the caller’s whole-leaf pass then correctly deletes them, same as before this function existed. Returns the number of leaves repaired.
remove_node_from_parent 🔒
remove_stack_member
Remove block_id from the stack of the leaf holding it, WITHOUT removing the leaf, when that leaf has other members. If it was the visible tab, the right-hand neighbour becomes visible (the new last member when it was rightmost) — the same rule as the frontend’s removeMemberFromStack (frontend/layout/lib/stackMembers.ts). Returns false and changes nothing when block_id is in no leaf or is its leaf’s only member (removing it means removing the leaf — the caller’s job). SPEC_PANE_TABS_REDUCER_COMMANDS_2026_09_18.md §3.4.
reorder_within_leaf 🔒
Splice block_id to position relative to target_block_id within the SAME leaf’s stack. Both must already be members of that leaf (checked by the caller, move_stack_member). Only touches block_id/active_block_id when activate is set — a reorder alone never changes which member is visible.
replace_node
Replace the node at target_id with new_node, preserving the target’s size. If target is root, replaces root-in-place (caller’s Option<LayoutNode> doesn’t change reference — the fields are updated on the root node).
resize_nodes
Apply resize operations. Validates all sizes first (0.0–100.0 range); if any is invalid, returns an error WITHOUT applying any ops (atomically rejected — matches the frontend’s early-return semantic from PR #686).
reverse_flex_direction 🔒
Reverse a flex direction (Row ↔ Column). Mirrors frontend/layout/lib/layoutNode.ts::reverseFlexDirection.
source_leaf_emptied_by_move
The node id of block_id’s leaf, when move_stack_member(tree, block_id, target_block_id, ..) would delete that leaf: block_id is its only member and target_block_id lives in a different leaf. None for every other move (reorder, multi-member source, missing blocks, self-target).
split_horizontal
Horizontal split: insert new_node before/after target_id.
split_impl 🔒
split_vertical
Vertical split: insert new_node before/after target_id.
swap_nodes
Swap two nodes (by id). Their sizes travel with them (swap positions but preserve the size each node had). Neither can be the root.
validate_layout_invariants
Layout doctor — structural invariant validation, mirroring validateLayoutInvariants in frontend/layout/lib/layoutInvariants.ts (keep the check lists in sync). Returns human-readable violation strings; empty = healthy. Never mutates. Reducer choke points call this after prune/enforce and tracing::error! any findings, so a corrupted tree is attributed to the write that produced it instead of being reconstructed later from db_layout archaeology (issue #2179).
validate_node 🔒
Validate the leaf-XOR-branch invariant. Mirrors validateNode in frontend/layout/lib/layoutNode.ts: a node must have either data or children, never both and never neither. (In TS the “empty children array” case is a separate check; here an empty children vec with no data is the same “neither” case and is rejected by the single XOR below.)