ArrowRight on a collapsed parent expands it. On an already-open
parent it moves the active-descendant to the first child. On a leaf
it is a no-op.
ArrowLeft on an open node collapses it. On a closed node (or
leaf) with a parent it moves the active-descendant to the parent.
On a root leaf it is a no-op.
Move actions internally verify that ad.highlightByValue actually
changed activeId (e.g. disabled skip rejection), and downgrade to
'noop' when it didn't - so consumers bound to (movedToChild) /
(movedToParent) only see state-change-truthful emissions.