Detect the same-tuple overlaps that are reachable — the ones where the
dispatcher really does fall back on declaration order.
matchSorted walks scopes in strict priority (hotkey > active > ambient)
and only sorts by specificity within a scope. So a cross-scope tie isn't
a tie at all: an ambient tool losing a tuple to the active tool is the
documented design, not an accident. Likewise two registry tools sharing a
tuple — rect and ellipse both binding a bare drag — can never both be
in the active slot, so they never compete.
What's left, and what this reports:
a single tool colliding with itself (possible since bindings became
an array), which is ambiguous in whichever slot it occupies;
two ambient tools, all of which are live simultaneously and are
ordered only by registration;
two hotkey-capable tools, which can stack.
Not covered: the actions registry's defaultBindings, which the dispatcher
also folds into ambient/hotkey scope. They're assembled somewhere else
entirely (ActionsRegistry, not useTools), so catching tool-vs-action
collisions means giving this function a second input it doesn't have yet.
Detect the same-tuple overlaps that are reachable — the ones where the dispatcher really does fall back on declaration order.
matchSortedwalks scopes in strict priority (hotkey > active > ambient) and only sorts by specificity within a scope. So a cross-scope tie isn't a tie at all: an ambient tool losing a tuple to the active tool is the documented design, not an accident. Likewise two registry tools sharing a tuple —rectandellipseboth binding a baredrag— can never both be in the active slot, so they never compete.What's left, and what this reports:
Not covered: the actions registry's
defaultBindings, which the dispatcher also folds into ambient/hotkey scope. They're assembled somewhere else entirely (ActionsRegistry, notuseTools), so catching tool-vs-action collisions means giving this function a second input it doesn't have yet.