Tryst::UI::Validator
Walks a Document before realize and collects ALL problems, so a broken build can be fixed in one pass instead of a cycle of "run, hit the next cryptic Tcl error, fix, repeat." Headless - no interpreter needed, since it only ever inspects the tree.
Two severities: problems that are "definitely broken" raise (folded into one ValidationError listing every one found); problems that are "probably a mistake" warn to stderr by default, or raise too under strict: true.
This class runs the checks that span the whole tree or relate arbitrary nodes to each other (dangling event targets, orphans, GridValidator.check_stray_cell, OverlayValidator.check_stray_overlay
- a stray grid-cell/overlay position can land on any node type, so neither can be type-dispatched through WidgetValidators the way GridValidator.call is). A specific widget/container's OWN contract (a grid's direct children all need cells) belongs in its own WidgetValidators-registered validator instead - GridValidator.call lands alongside :grid itself (see widget_types/grid.cr); a canvas has no equivalent forward check at all (unlike a grid, its children aren't required to all carry an overlay position - a bare arranged child is just as legitimate). ruby-tryst's TabValidator/PaneValidator have no port planned at all, since nothing in this port's scope needs tab/pane.
A WidgetType descriptor's own validator: (see WidgetTypes) needs no separate handling here at all - WidgetTypes.register forwards it into WidgetValidators directly, so it's dispatched through the exact same WidgetValidators.for_type call every other validator already goes through.
Constructors
Class methods
Validate a single subtree instead of the whole document - what Session#add runs before realizing an addition.
Rooted at the PARENT being added into, not at the new children: a grid cell added at a row/col an existing sibling already occupies is only visible to a check that can see both, and a children-only walk can't. Re-walking the siblings costs nothing (they're already known good, and it's one container).
Skips #check_orphans, the one check that's inherently whole-document - reachability is measured from whatever root got walked, so a subtree walk would report every node outside it as an orphan. That also makes strict: meaningless here (orphans are the only thing it escalates), so this doesn't take it.