src/switcher/state/BruteForceWindowMatchSpecs.md
BruteForceWindowMatch is the pure decision kernel for AXUIElement.windowsByBruteForce: given one remote
AX element found during the brute-force scan for requested wids, is it a window's ROOT element, or one of
that window's descendants that happens to resolve to the same wid?
The brute-force scan (bruteForceElements) walks remote-token AXUIElementIDs and asks each candidate
"do you belong to wid W?" via _AXUIElementGetWindow. The trap: that call returns the CONTAINING
window's id for a window's DESCENDANTS too — every button, outline, tab bar, and menu of window W also
answers "W". Those descendants routinely sit at a LOWER AXUIElementID than the window element itself, so
a scan that stopped at the first wid match returned a descendant (role AXOutline / AXGroup /
AXTabGroup / AXMenuButton, subrole nil). WindowAdmissionResolver then rejected it for lacking a window
subrole, and the window disappeared from the switcher entirely.
That is the #5849 regression: the v11.4 WindowServer migration dropped the old fallback's subrole filter,
so the scan started stopping on descendants. Reporters saw a visible, normal Finder window (owning
element AXWindow, but preceded in the scan by an AXOutline sharing its wid) vanish, and Telegram's
"Main menu" (AXMenuButton) stand in for the real Telegram window.
isTargetWindowRoot is true iff BOTH hold:
candidateWid == targetWid.candidateRole == kAXWindowRole ("AXWindow").Gate on ROLE, not subrole. A real window's subrole is judged downstream by WindowAdmissionResolver
(AXStandardWindow OR AXDialog, plus app-specific carve-outs), and filtering by standard subrole here
would drop apps with nonstandard trees. Role AXWindow is the narrowest gate that still lets the scan
skip descendants and land on the root.
windowsByBruteForce is the thin impure adapter: it reads the wid first (cheap), reads the role only after
the wid belongs to the remaining target set (so IPC for the role is paid only on requested windows'
descendants), then routes the verdict here. A false for a descendant means "keep scanning"; a root removes
that wid from the set, and the traversal stops only when every requested root is found or its budget expires.
isPlausibleInactiveTab)The same file also gates the OTHER brute-force scan — Applications.discoverInactiveTabs, which hunts an
app's inactive tabs, since no CGS list holds them. That scan is run for ONE window's missing tab titles and
matches candidates on TITLE alone, which cannot tell two windows apart at all: two Finder windows browsing
the same folders each have a tab called "lwouis" (#5785: tab titles are not window titles, and no reliable
tab→window mapping exists).
Captured live (2026-08-01 QA): the scan run for window A adopted a tab of window B. Nothing broke immediately — a non-representative member is simply hidden — but when the user later switched to that tab it became the REPRESENTATIVE of A's group, so every real member of A stopped being drawn and A vanished from the switcher, with the default pick landing past where it had been.
The on-screen gate added for the same collision does not reach this one: an inactive tab of another window is not on screen either, so it looks exactly like one of ours.
The caller must source those frames from the WindowServer, not from the tracked window list. The rule can
only reject what it knows about, and at launch this scan routinely runs before the app's second window has
been tracked: the list is then empty, every candidate sits on top of nothing, and the tabs of a window we had
not seen yet are adopted as the requester's. Two real windows end up in one tab group, the second hidden
inside it and no longer offered (live 2026-08-25: requester=#52149@(80,600) others=[], then two tabs at
(80,80) adopted). The WindowServer's on-screen list is complete from the first scan. Read requester=/
others= in the scan log before believing this gate did anything.
isPlausibleInactiveTab rejects a candidate that sits exactly on another of this app's tracked windows
while not sitting on the requester — a tab is positioned by its parent, so that frame names its parent. The
test is one-sided on purpose. Merge All Windows never converges the absorbed windows' frames (they keep their
own cascade positions, frozen), so demanding a match with the requester would make a merged group's tabs
permanently un-adoptable; those frozen frames sit on top of nothing, so they are waved through. Position only,
not size: a tabbed window's members diverge in size as the tab bar resizes them, the same reason
TabGroupResolver.framePartitions keys on position.
Mirrors BruteForceWindowMatchTests.swift 1:1.
AXWindow → true.AXOutline → false (the scan
continues to the AXWindow that follows it).AXMenuButton → false.AXGroup: wid matches, role AXGroup → false.AXWindow but wid differs → false.[(wid W, AXOutline), (wid W, AXWindow)]; firstIndex(isTargetWindowRoot) selects index 1, proving
the earlier AXOutline no longer wins.isPlausibleInactiveTab, the 2026-08-01 cross-window adoption)