Ariver
2026-06-13 343237725f65445bab306e3fe4cdbd10949d4d4b
Fix Finder tab page activation
6 files modified
749 ■■■■■ changed files
C1.source/Resources/Aligner-Info.plist 4 ●●●● patch | view | raw | blame | history
C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift 507 ●●●●● patch | view | raw | blame | history
C1.source/Sources/Aligner/QuickSwitchRootView.swift 16 ●●●●● patch | view | raw | blame | history
C1.source/Sources/AlignerCore/Windows/FinderTabSpaceAttributionPolicy.swift 32 ●●●●● patch | view | raw | blame | history
C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift 52 ●●●●● patch | view | raw | blame | history
C3.tools/round1-finder-tabs-live-qa.sh 138 ●●●●● patch | view | raw | blame | history
C1.source/Resources/Aligner-Info.plist
@@ -17,9 +17,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>0.0.64</string>
    <string>0.0.65</string>
    <key>CFBundleVersion</key>
    <string>20260613.1606</string>
    <string>20260613.1843</string>
    <key>LSMinimumSystemVersion</key>
    <string>26.0</string>
    <key>NSHighResolutionCapable</key>
C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
@@ -14,6 +14,17 @@
    private let privateSpaceActivationBridge: PrivateSpaceActivationBridge?
    private var restorableAXWindowsByID: [UInt32: RestorableAXWindow] = [:]
    private struct FinderTabControl {
        let button: AXUIElement
        let tabGroup: AXUIElement?
    }
    private struct KeyboardShortcut {
        let name: String
        let keyCode: CGKeyCode
        let flags: CGEventFlags
    }
    init(
        spaceIDsByWindowIDProvider: @escaping ([UInt32]) -> [UInt32: [UInt64]],
        appCategorizer: any AppCategorizer = BundleIDAppCategorizer(),
@@ -118,7 +129,8 @@
            : .activationFailed
    }
    func activate(window: AlignerWindow) throws -> WindowActivationResult {
    func activate(window requestedWindow: AlignerWindow) throws -> WindowActivationResult {
        let window = refreshedFinderWindowForPreciseAction(requestedWindow, operation: "windowActivation")
        DevelopmentDiagnostics.log("windowActivation.activate.start", [
            "windowID": window.id,
            "identifierSource": String(describing: window.identifierSource),
@@ -268,7 +280,8 @@
            : .appActivatedOnly
    }
    func close(window: AlignerWindow) throws -> WindowCloseResult {
    func close(window requestedWindow: AlignerWindow) throws -> WindowCloseResult {
        let window = refreshedFinderWindowForPreciseAction(requestedWindow, operation: "windowClose")
        DevelopmentDiagnostics.log("windowClose.window.start", [
            "windowID": window.id,
            "identifierSource": String(describing: window.identifierSource),
@@ -345,6 +358,59 @@
            processIdentifier: matchedAXWindow.processIdentifier,
            attempt: "initial"
        )
    }
    private func refreshedFinderWindowForPreciseAction(
        _ window: AlignerWindow,
        operation: String
    ) -> AlignerWindow {
        guard isFinderCGOnlyPageCandidate(window) else { return window }
        guard let refreshedWindow = try? allWindows().first(where: { refreshed in
            refreshed.id == window.id
                && refreshed.identifierSource == window.identifierSource
                && refreshed.app.bundleIdentifier == window.app.bundleIdentifier
        }) else {
            DevelopmentDiagnostics.log("\(operation).finderRefresh.missing", [
                "windowID": window.id,
                "spaceIDCount": window.spaceIDs.count,
                "hasFrame": window.frame != nil
            ])
            return window
        }
        let mergedWindow = AlignerWindow(
            id: window.id,
            app: window.app.processIdentifier == nil ? refreshedWindow.app : window.app,
            title: window.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
                ? refreshedWindow.title
                : window.title,
            identifierSource: window.identifierSource,
            isMinimized: window.isMinimized || refreshedWindow.isMinimized,
            isFullscreen: window.isFullscreen || refreshedWindow.isFullscreen,
            isGhost: window.isGhost,
            frame: window.frame ?? refreshedWindow.frame,
            spaceIDs: window.spaceIDs.isEmpty ? refreshedWindow.spaceIDs : window.spaceIDs
        )
        if mergedWindow.spaceIDs != window.spaceIDs
            || mergedWindow.isFullscreen != window.isFullscreen
            || (window.frame == nil && refreshedWindow.frame != nil) {
            DevelopmentDiagnostics.log("\(operation).finderRefresh.merged", [
                "windowID": window.id,
                "originalSpaceIDCount": window.spaceIDs.count,
                "refreshedSpaceIDCount": refreshedWindow.spaceIDs.count,
                "mergedSpaceIDCount": mergedWindow.spaceIDs.count,
                "originalIsFullscreen": window.isFullscreen,
                "refreshedIsFullscreen": refreshedWindow.isFullscreen,
                "mergedIsFullscreen": mergedWindow.isFullscreen,
                "originalHasFrame": window.frame != nil,
                "refreshedHasFrame": refreshedWindow.frame != nil,
                "mergedHasFrame": mergedWindow.frame != nil
            ])
        }
        return mergedWindow
    }
    private func closeButton(for axWindow: AXWindowMetadata) -> AXUIElement? {
@@ -635,13 +701,22 @@
        let raiseBeforeResult = raiseWindowResult(host.element)
        let selectedTab = selectFinderTab(window, in: host)
        Thread.sleep(forTimeInterval: 0.05)
        let focusAfterResult = focusWindowResult(host.element, processIdentifier: processIdentifier)
        let raiseAfterResult = raiseWindowResult(host.element)
        let focusAfterResult: AXActionResult?
        let raiseAfterResult: AXActionResult?
        if selectedTab {
            // For Finder tab/page cards, re-focusing the original host AX window after
            // selecting the tab can switch Finder back to the host's previous tab.
            focusAfterResult = nil
            raiseAfterResult = nil
        } else {
            focusAfterResult = focusWindowResult(host.element, processIdentifier: processIdentifier)
            raiseAfterResult = raiseWindowResult(host.element)
        }
        let didFocusOrRaise = focusBeforeResult.success
            || raiseBeforeResult.success
            || focusAfterResult.success
            || raiseAfterResult.success
            || (focusAfterResult?.success ?? false)
            || (raiseAfterResult?.success ?? false)
        let activated = selectedTab
            && (hostPrivateActivationSucceeded || applicationActivated || didFocusOrRaise)
@@ -657,8 +732,8 @@
            "selectedTab": selectedTab,
            "focusBeforeAXError": String(describing: focusBeforeResult.error),
            "raiseBeforeAXError": String(describing: raiseBeforeResult.error),
            "focusAfterAXError": String(describing: focusAfterResult.error),
            "raiseAfterAXError": String(describing: raiseAfterResult.error),
            "focusAfterAXError": focusAfterResult.map { String(describing: $0.error) } ?? "skippedAfterTabSelection",
            "raiseAfterAXError": raiseAfterResult.map { String(describing: $0.error) } ?? "skippedAfterTabSelection",
            "activated": activated
        ])
@@ -692,8 +767,28 @@
                guard let windowID = metadata.windowID else { return false }
                return !Set(candidateSpacesByID[windowID] ?? []).isDisjoint(with: targetSpaceIDs)
            }
        let hostCandidates = spaceCompatibleCandidates.isEmpty ? candidates : spaceCompatibleCandidates
        let targetFrame = cgWindowFrame(for: window) ?? window.frame
        let spaceScopedCandidates = spaceCompatibleCandidates.isEmpty ? candidates : spaceCompatibleCandidates
        let selectableHostCandidates = spaceScopedCandidates.filter { metadata in
            finderHostCanRepresentTarget(window, in: metadata)
        }
        let hostCandidates: [AXWindowMetadata]
        if targetSpaceIDs.isEmpty {
            guard !selectableHostCandidates.isEmpty else {
                DevelopmentDiagnostics.log("windowActivation.finderTab.noSelectableHost", [
                    "windowID": window.id,
                    "candidateCount": candidates.count,
                    "targetTitleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
                    "targetTitleLength": window.title.count
                ])
                return nil
            }
            hostCandidates = selectableHostCandidates
        } else {
            hostCandidates = selectableHostCandidates.isEmpty ? spaceScopedCandidates : selectableHostCandidates
        }
        let targetFrame = matchingFrame(for: window)
        let scoredHosts = hostCandidates.compactMap { metadata -> (metadata: AXWindowMetadata, score: CGFloat)? in
            guard let score = frameOverlapScore(targetFrame, metadata.frame),
                  score >= 0.85
@@ -721,7 +816,33 @@
            }
        }
        return hostCandidates.count == 1 ? hostCandidates[0] : nil
        if hostCandidates.count == 1 {
            return hostCandidates[0]
        }
        DevelopmentDiagnostics.log("windowActivation.finderTab.hostAmbiguous", [
            "windowID": window.id,
            "candidateCount": hostCandidates.count,
            "spaceIDCount": targetSpaceIDs.count,
            "hasTargetFrame": targetFrame != nil,
            "targetTitleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
            "targetTitleLength": window.title.count
        ])
        return nil
    }
    private func finderHostCanRepresentTarget(
        _ window: AlignerWindow,
        in host: AXWindowMetadata
    ) -> Bool {
        let normalizedTargetTitle = normalizedWindowTitle(window.title)
        guard !normalizedTargetTitle.isEmpty else { return false }
        if normalizedWindowTitle(host.title ?? "") == normalizedTargetTitle {
            return true
        }
        return finderTabControl(matchingNormalizedTitle: normalizedTargetTitle, in: host.element) != nil
    }
    private func selectFinderTab(
@@ -735,7 +856,7 @@
            return true
        }
        guard let tabButton = finderTabButton(
        guard let tabControl = finderTabControl(
            matchingNormalizedTitle: normalizedTargetTitle,
            in: host.element
        ) else {
@@ -745,9 +866,27 @@
                "targetTitleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
                "targetTitleLength": window.title.count
            ])
            if let processIdentifier = window.app.processIdentifier,
               selectFinderWindowMenuItem(
                processIdentifier: processIdentifier,
                window: window,
                normalizedTargetTitle: normalizedTargetTitle
               ) {
                return true
            }
            if let processIdentifier = window.app.processIdentifier,
               selectFinderTabByCyclingTabs(
                processIdentifier: processIdentifier,
                host: host,
                window: window,
                normalizedTargetTitle: normalizedTargetTitle
               ) {
                return true
            }
            return false
        }
        let tabButton = tabControl.button
        let tabButtonRole = axStringAttribute(kAXRoleAttribute as String, for: tabButton)
        let tabButtonSubrole = axStringAttribute(kAXSubroleAttribute as String, for: tabButton)
        let pressResult = AXUIElementPerformAction(tabButton, kAXPressAction as CFString)
@@ -758,16 +897,60 @@
            "tabButtonSubrole": tabButtonSubrole,
            "axError": String(describing: pressResult)
        ])
        guard pressResult == .success else { return false }
        if pressResult == .success,
           finderHostTitleMatchesTarget(host, normalizedTargetTitle: normalizedTargetTitle) {
            return true
        }
        for _ in 0..<10 {
            Thread.sleep(forTimeInterval: 0.04)
            let selectedTitle = normalizedWindowTitle(
                axStringAttribute(kAXTitleAttribute as String, for: host.element) ?? ""
        if let tabGroup = tabControl.tabGroup {
            let selectedChildrenResult = AXUIElementSetAttributeValue(
                tabGroup,
                kAXSelectedChildrenAttribute as CFString,
                [tabButton] as CFArray
            )
            if selectedTitle == normalizedTargetTitle {
            DevelopmentDiagnostics.log("windowActivation.finderTab.setSelectedChildren", [
                "windowID": window.id,
                "hostWindowID": host.windowID,
                "axError": String(describing: selectedChildrenResult)
            ])
            if selectedChildrenResult == .success,
               finderHostTitleMatchesTarget(host, normalizedTargetTitle: normalizedTargetTitle) {
                return true
            }
        }
        let valueResult = AXUIElementSetAttributeValue(
            tabButton,
            kAXValueAttribute as CFString,
            kCFBooleanTrue
        )
        DevelopmentDiagnostics.log("windowActivation.finderTab.setValue", [
            "windowID": window.id,
            "hostWindowID": host.windowID,
            "axError": String(describing: valueResult)
        ])
        if valueResult == .success,
           finderHostTitleMatchesTarget(host, normalizedTargetTitle: normalizedTargetTitle) {
            return true
        }
        if let processIdentifier = window.app.processIdentifier,
           selectFinderWindowMenuItem(
            processIdentifier: processIdentifier,
            window: window,
            normalizedTargetTitle: normalizedTargetTitle
           ) {
            return true
        }
        if let processIdentifier = window.app.processIdentifier,
           selectFinderTabByCyclingTabs(
            processIdentifier: processIdentifier,
            host: host,
            window: window,
            normalizedTargetTitle: normalizedTargetTitle
           ) {
            return true
        }
        let currentHostTitle = axStringAttribute(kAXTitleAttribute as String, for: host.element) ?? ""
@@ -786,9 +969,16 @@
        matchingNormalizedTitle targetTitle: String,
        in root: AXUIElement
    ) -> AXUIElement? {
        finderTabControl(matchingNormalizedTitle: targetTitle, in: root)?.button
    }
    private func finderTabControl(
        matchingNormalizedTitle targetTitle: String,
        in root: AXUIElement
    ) -> FinderTabControl? {
        var visitedCount = 0
        func search(_ element: AXUIElement, depth: Int, insideTabGroup: Bool) -> AXUIElement? {
        func search(_ element: AXUIElement, depth: Int, tabGroup: AXUIElement?) -> FinderTabControl? {
            visitedCount += 1
            guard visitedCount <= 240, depth <= 9 else { return nil }
@@ -798,17 +988,18 @@
                ?? axStringAttribute(kAXDescriptionAttribute as String, for: element)
                ?? axStringAttribute(kAXValueAttribute as String, for: element)
            let isTabGroup = role == "AXTabGroup"
            let currentTabGroup = isTabGroup ? element : tabGroup
            let isTabControl = subrole == "AXTabButton"
                || (insideTabGroup && (role == kAXRadioButtonRole as String || role == kAXButtonRole as String))
                || (currentTabGroup != nil && (role == kAXRadioButtonRole as String || role == kAXButtonRole as String))
            if normalizedWindowTitle(title ?? "") == targetTitle,
               isTabControl {
                return element
                return FinderTabControl(button: element, tabGroup: currentTabGroup)
            }
            guard isTabGroup || insideTabGroup || depth < 7 else { return nil }
            guard isTabGroup || currentTabGroup != nil || depth < 7 else { return nil }
            for child in axChildren(of: element) {
                if let match = search(child, depth: depth + 1, insideTabGroup: insideTabGroup || isTabGroup) {
                if let match = search(child, depth: depth + 1, tabGroup: currentTabGroup) {
                    return match
                }
            }
@@ -816,7 +1007,240 @@
            return nil
        }
        return search(root, depth: 0, insideTabGroup: false)
        return search(root, depth: 0, tabGroup: nil)
    }
    private func finderHostTitleMatchesTarget(
        _ host: AXWindowMetadata,
        normalizedTargetTitle: String,
        attempts: Int = 10,
        delay: TimeInterval = 0.04
    ) -> Bool {
        for _ in 0..<attempts {
            Thread.sleep(forTimeInterval: delay)
            let selectedTitle = normalizedWindowTitle(
                axStringAttribute(kAXTitleAttribute as String, for: host.element) ?? ""
            )
            if selectedTitle == normalizedTargetTitle {
                return true
            }
        }
        return false
    }
    private func selectFinderWindowMenuItem(
        processIdentifier: Int32,
        window: AlignerWindow,
        normalizedTargetTitle: String
    ) -> Bool {
        let appElement = AXUIElementCreateApplication(processIdentifier)
        guard let menuBar = axElementAttribute(kAXMenuBarAttribute as String, for: appElement) else {
            DevelopmentDiagnostics.log("windowActivation.finderTab.windowMenuMissing", [
                "windowID": window.id,
                "pid": processIdentifier
            ])
            return false
        }
        for menuBarItem in axChildren(of: menuBar) where isWindowMenuBarItem(menuBarItem) {
            let openResult = AXUIElementPerformAction(menuBarItem, kAXPressAction as CFString)
            Thread.sleep(forTimeInterval: 0.08)
            guard openResult == .success,
                  let menuItem = finderWindowMenuItem(
                    matchingNormalizedTitle: normalizedTargetTitle,
                    in: menuBarItem
                  )
            else {
                continue
            }
            let pressResult = AXUIElementPerformAction(menuItem, kAXPressAction as CFString)
            DevelopmentDiagnostics.log("windowActivation.finderTab.windowMenuPress", [
                "windowID": window.id,
                "pid": processIdentifier,
                "axError": String(describing: pressResult)
            ])
            guard pressResult == .success else { return false }
            if finderFocusedWindowTitleMatches(
                processIdentifier: processIdentifier,
                window: window,
                normalizedTargetTitle: normalizedTargetTitle,
                initialDelay: 0.18,
                attempts: 18,
                delay: 0.06,
                requiredConsecutiveMatches: 3
            ) {
                DevelopmentDiagnostics.log("windowActivation.finderTab.windowMenuSucceeded", [
                    "windowID": window.id,
                    "pid": processIdentifier
                ])
                return true
            }
            DevelopmentDiagnostics.log("windowActivation.finderTab.windowMenuVerificationFailed", [
                "windowID": window.id,
                "pid": processIdentifier
            ])
        }
        DevelopmentDiagnostics.log("windowActivation.finderTab.windowMenuItemMissing", [
            "windowID": window.id,
            "pid": processIdentifier,
            "targetTitleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
            "targetTitleLength": window.title.count
        ])
        return false
    }
    private func isWindowMenuBarItem(_ element: AXUIElement) -> Bool {
        let title = normalizedWindowTitle(axStringAttribute(kAXTitleAttribute as String, for: element) ?? "")
        return title == "window" || title == "窗口"
    }
    private func finderWindowMenuItem(
        matchingNormalizedTitle targetTitle: String,
        in root: AXUIElement
    ) -> AXUIElement? {
        var visitedCount = 0
        func search(_ element: AXUIElement, depth: Int) -> AXUIElement? {
            visitedCount += 1
            guard visitedCount <= 500, depth <= 8 else { return nil }
            let role = axStringAttribute(kAXRoleAttribute as String, for: element)
            let title = axStringAttribute(kAXTitleAttribute as String, for: element)
                ?? axStringAttribute(kAXDescriptionAttribute as String, for: element)
            if role == kAXMenuItemRole as String,
               normalizedWindowTitle(title ?? "") == targetTitle {
                return element
            }
            for child in axChildren(of: element) {
                if let match = search(child, depth: depth + 1) {
                    return match
                }
            }
            return nil
        }
        return search(root, depth: 0)
    }
    private func finderFocusedWindowTitleMatches(
        processIdentifier: Int32,
        window: AlignerWindow,
        normalizedTargetTitle: String,
        initialDelay: TimeInterval,
        attempts: Int,
        delay: TimeInterval,
        requiredConsecutiveMatches: Int
    ) -> Bool {
        let appElement = AXUIElementCreateApplication(processIdentifier)
        var consecutiveMatches = 0
        var lastTitle: String?
        Thread.sleep(forTimeInterval: initialDelay)
        for _ in 0..<attempts {
            let focusedWindow = axElementAttribute(kAXFocusedWindowAttribute as String, for: appElement)
                ?? axElementAttribute(kAXMainWindowAttribute as String, for: appElement)
            let title = focusedWindow.flatMap {
                axStringAttribute(kAXTitleAttribute as String, for: $0)
            }
            lastTitle = title
            if normalizedWindowTitle(title ?? "") == normalizedTargetTitle {
                consecutiveMatches += 1
            } else {
                consecutiveMatches = 0
            }
            if consecutiveMatches >= requiredConsecutiveMatches {
                return true
            }
            Thread.sleep(forTimeInterval: delay)
        }
        DevelopmentDiagnostics.log("windowActivation.finderTab.focusedTitleMismatch", [
            "windowID": window.id,
            "pid": processIdentifier,
            "targetTitleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
            "targetTitleLength": window.title.count,
            "lastTitleHash": DevelopmentDiagnostics.stableFingerprint(lastTitle),
            "lastTitleLength": lastTitle?.count
        ])
        return false
    }
    private func selectFinderTabByCyclingTabs(
        processIdentifier: Int32,
        host: AXWindowMetadata,
        window: AlignerWindow,
        normalizedTargetTitle: String
    ) -> Bool {
        let maximumAttempts = 24
        for attempt in 1...maximumAttempts {
            for shortcut in Self.finderNextTabShortcuts {
                guard postKeyboardShortcut(to: processIdentifier, shortcut: shortcut) else {
                    DevelopmentDiagnostics.log("windowActivation.finderTab.cyclePostFailed", [
                        "windowID": window.id,
                        "hostWindowID": host.windowID,
                        "attempt": attempt,
                        "shortcut": shortcut.name
                    ])
                    return false
                }
                if finderHostTitleMatchesTarget(
                    host,
                    normalizedTargetTitle: normalizedTargetTitle,
                    attempts: 4,
                    delay: 0.035
                ) {
                    DevelopmentDiagnostics.log("windowActivation.finderTab.cycleSucceeded", [
                        "windowID": window.id,
                        "hostWindowID": host.windowID,
                        "attempt": attempt,
                        "shortcut": shortcut.name
                    ])
                    return true
                }
            }
        }
        DevelopmentDiagnostics.log("windowActivation.finderTab.cycleFailed", [
            "windowID": window.id,
            "hostWindowID": host.windowID,
            "attemptCount": maximumAttempts
        ])
        return false
    }
    private func postKeyboardShortcut(
        to processIdentifier: Int32,
        shortcut: KeyboardShortcut
    ) -> Bool {
        let source = CGEventSource(stateID: .combinedSessionState)
        guard let keyDown = CGEvent(
            keyboardEventSource: source,
            virtualKey: shortcut.keyCode,
            keyDown: true
        ),
              let keyUp = CGEvent(
                keyboardEventSource: source,
                virtualKey: shortcut.keyCode,
                keyDown: false
              )
        else {
            return false
        }
        keyDown.flags = shortcut.flags
        keyUp.flags = shortcut.flags
        keyDown.postToPid(processIdentifier)
        keyUp.postToPid(processIdentifier)
        return true
    }
    private func axChildren(of element: AXUIElement) -> [AXUIElement] {
@@ -841,6 +1265,18 @@
        return value as? String
    }
    private func axElementAttribute(_ attribute: String, for element: AXUIElement) -> AXUIElement? {
        var value: CFTypeRef?
        guard AXUIElementCopyAttributeValue(element, attribute as CFString, &value) == .success,
              let value,
              CFGetTypeID(value) == AXUIElementGetTypeID()
        else {
            return nil
        }
        return (value as! AXUIElement)
    }
    private func frameOverlapScore(_ lhs: CGRect?, _ rhs: CGRect?) -> CGFloat? {
        guard let lhs,
              let rhs,
@@ -859,7 +1295,12 @@
        let denominator = min(lhs.width * lhs.height, rhs.width * rhs.height)
        guard denominator > 0 else { return nil }
        return (intersection.width * intersection.height) / denominator
        let overlap = (intersection.width * intersection.height) / denominator
        let widthSimilarity = min(lhs.width, rhs.width) / max(lhs.width, rhs.width)
        let heightSimilarity = min(lhs.height, rhs.height) / max(lhs.height, rhs.height)
        let sizeSimilarity = min(widthSimilarity, heightSimilarity)
        guard sizeSimilarity >= 0.80 else { return nil }
        return min(overlap, sizeSimilarity)
    }
    private func cgWindowFingerprint(for window: AlignerWindow) -> AXWindowFingerprint? {
@@ -882,7 +1323,7 @@
    private func axWindowGeometryFingerprint(for window: AlignerWindow) -> AXWindowGeometryFingerprint? {
        guard let processIdentifier = window.app.processIdentifier else { return nil }
        let frame = cgWindowFrame(for: window) ?? window.frame
        let frame = matchingFrame(for: window)
        return AXWindowGeometryFingerprint(
            processIdentifier: processIdentifier,
            title: normalizedWindowTitle(window.title),
@@ -901,6 +1342,14 @@
        }
        return bounds(rawWindow[kCGWindowBounds as String])
    }
    private func matchingFrame(for window: AlignerWindow) -> CGRect? {
        if isFinderCGOnlyPageCandidate(window) {
            return window.frame ?? cgWindowFrame(for: window)
        }
        return cgWindowFrame(for: window) ?? window.frame
    }
    private func matchingAXTitleCandidateCount(
@@ -1435,6 +1884,12 @@
    }
    private static let commandWVirtualKeyCode: CGKeyCode = 13
    private static let rightBracketVirtualKeyCode: CGKeyCode = 30
    private static let tabVirtualKeyCode: CGKeyCode = 48
    private static let finderNextTabShortcuts: [KeyboardShortcut] = [
        KeyboardShortcut(name: "controlTab", keyCode: tabVirtualKeyCode, flags: .maskControl),
        KeyboardShortcut(name: "commandShiftRightBracket", keyCode: rightBracketVirtualKeyCode, flags: [.maskCommand, .maskShift])
    ]
}
private struct AXWindowMetadata {
C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -1347,6 +1347,15 @@
            if let card = waterfallCard(appGroupIndex: appGroupIndex, windowIndex: windowIndex) {
                clickWindowCard(card)
            }
        case "click-card-id" where parts.count == 2:
            guard let windowID = UInt32(parts[1]) else {
                recordMouseCommand("invalid:\(command)")
                return
            }
            recordMouseCommand("click-card-id:\(windowID)")
            if let card = waterfallCard(windowID: windowID) {
                clickWindowCard(card)
            }
        case "hover-card-close" where parts.count == 3:
            guard let appGroupIndex = Int(parts[1]), let windowIndex = Int(parts[2]) else {
                recordMouseCommand("invalid:\(command)")
@@ -3085,6 +3094,13 @@
            .first(where: { $0.item.windowIndex == windowIndex })
    }
    private func waterfallCard(windowID: UInt32) -> WaterfallCardLayers? {
        waterfallColumns
            .lazy
            .flatMap(\.cards)
            .first(where: { $0.item.window.id == windowID })
    }
    private func layoutSpaceLaneSegmentContents(_ segment: SpaceLaneSegmentLayers) {
        let bounds = segment.containerLayer.bounds
        let splitAppNames = splitViewAppNames(for: segment.space)
C1.source/Sources/AlignerCore/Windows/FinderTabSpaceAttributionPolicy.swift
@@ -4,6 +4,7 @@
public enum FinderTabSpaceAttributionPolicy {
    private static let finderBundleIdentifier = "com.apple.finder"
    private static let minimumOverlapRatio: CGFloat = 0.85
    private static let minimumSizeSimilarityRatio: CGFloat = 0.80
    private static let minimumBestScoreGap: CGFloat = 0.05
    public static func attributedRecords(_ records: [WindowEnumerationRecord]) -> [WindowEnumerationRecord] {
@@ -52,8 +53,7 @@
            sameProcess(candidate, host)
        }
        let scoredHosts = compatibleHosts.compactMap { host -> (record: WindowEnumerationRecord, score: CGFloat)? in
            guard let score = overlapScore(candidate.frame, host.frame),
                  score >= minimumOverlapRatio
            guard let score = frameCompatibilityScore(candidate.frame, host.frame)
            else {
                return nil
            }
@@ -79,6 +79,18 @@
        let secondBest = scoredHosts[1]
        return best.score - secondBest.score >= minimumBestScoreGap ? best.record : nil
    }
    private static func frameCompatibilityScore(_ lhs: CGRect?, _ rhs: CGRect?) -> CGFloat? {
        guard let overlap = overlapScore(lhs, rhs),
              overlap >= minimumOverlapRatio,
              let sizeSimilarity = sizeSimilarityScore(lhs, rhs),
              sizeSimilarity >= minimumSizeSimilarityRatio
        else {
            return nil
        }
        return min(overlap, sizeSimilarity)
    }
    private static func sameProcess(
@@ -115,6 +127,22 @@
        return (intersection.width * intersection.height) / denominator
    }
    private static func sizeSimilarityScore(_ lhs: CGRect?, _ rhs: CGRect?) -> CGFloat? {
        guard let lhs,
              let rhs,
              lhs.width > 0,
              lhs.height > 0,
              rhs.width > 0,
              rhs.height > 0
        else {
            return nil
        }
        let widthSimilarity = min(lhs.width, rhs.width) / max(lhs.width, rhs.width)
        let heightSimilarity = min(lhs.height, rhs.height) / max(lhs.height, rhs.height)
        return min(widthSimilarity, heightSimilarity)
    }
    private static func copy(
        _ record: WindowEnumerationRecord,
        isFullscreen: Bool,
C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
@@ -533,6 +533,58 @@
        XCTAssertTrue(attributed[1].isFullscreen)
    }
    func testFinderTabSpaceAttributionRejectsContainingHostsWithDifferentSize() {
        let finderApp = AlignerApp(
            bundleIdentifier: "com.apple.finder",
            name: "访达",
            category: .finder,
            processIdentifier: 74678
        )
        let splitRightFrame = CGRect(x: 960, y: 0, width: 960, height: 1080)
        let fullDisplayFrame = CGRect(x: 0, y: 0, width: 1920, height: 1080)
        let splitHost = WindowEnumerationRecord(
            id: 33112,
            app: finderApp,
            title: "下载",
            size: splitRightFrame.size,
            frame: splitRightFrame,
            subrole: .standard,
            isFullscreen: true,
            hasAXBacking: true,
            spaceIDs: [843]
        )
        let containingHost = WindowEnumerationRecord(
            id: 41557,
            app: finderApp,
            title: "应用程序",
            size: fullDisplayFrame.size,
            frame: fullDisplayFrame,
            subrole: .standard,
            isFullscreen: true,
            hasAXBacking: true,
            spaceIDs: [1112]
        )
        let inactiveTab = WindowEnumerationRecord(
            id: 42286,
            app: finderApp,
            title: "Appcache",
            size: splitRightFrame.size,
            frame: splitRightFrame,
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )
        let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([
            inactiveTab,
            containingHost,
            splitHost
        ])
        XCTAssertEqual(attributed[0].spaceIDs, [843])
        XCTAssertTrue(attributed[0].isFullscreen)
    }
    func testWindowEnumerationPolicyKeepsMinimizedAndFullscreenCGOnlyOffscreenWindows() {
        let minimized = makeWindowRecord(
            title: "Minimized Document.md",
C3.tools/round1-finder-tabs-live-qa.sh
@@ -14,7 +14,7 @@
RUN_ID="$(date +%Y%m%d_%H%M%S)"
REPORT_DIR="$BUILD_REPORT_ROOT/round01-finder-tabs-live-$RUN_ID"
DEV_LOG="$HOME/Library/Logs/Aligner/aligner-dev.log"
REPORT_WAIT="${ALIGNER_ROUND1_FINDER_TABS_REPORT_WAIT:-7.0}"
REPORT_WAIT="${ALIGNER_ROUND1_FINDER_TABS_REPORT_WAIT:-35.0}"
fail() {
  echo "Round01 Finder tabs live QA failed: $*" >&2
@@ -128,17 +128,23 @@
    --round0-skip-permissions \
    --round01-open-quick-switch \
    --round01-disable-screenshot-refresh \
    --round01-quick-switch-auto-hide-after=0.8 \
    --round01-quick-switch-quit-after=1.3 \
    --round01-quick-switch-report="$report" >"$app_log" 2>&1
  wait_for_report "$report" "loaded"
    --round01-quick-switch-auto-hide-after=20.0 \
    --round01-quick-switch-quit-after=21.0 \
    --round01-quick-switch-report="$report" >"$app_log" 2>&1 &
  local app_pid="$!"
  if ! wait_for_report "$report" "loaded"; then
    kill "$app_pid" 2>/dev/null || true
    wait "$app_pid" 2>/dev/null || true
    return 1
  fi
  kill "$app_pid" 2>/dev/null || true
  wait "$app_pid" 2>/dev/null || true
}
run_click() {
  local app_group_index="$1"
  local window_index="$2"
  local report="$3"
  local app_log="$4"
  local window_id="$1"
  local report="$2"
  local app_log="$3"
  stop_current_aligner
  rm -f "$report"
  ALIGNER_DIAGNOSTICS_INCLUDE_SENSITIVE=1 \
@@ -146,10 +152,16 @@
    --round0-skip-permissions \
    --round01-open-quick-switch \
    --round01-disable-screenshot-refresh \
    --round01-debug-mouse-sequence="click-card:$app_group_index:$window_index" \
    --round01-quick-switch-quit-after=1.8 \
    --round01-quick-switch-report="$report" >"$app_log" 2>&1
  wait_for_report "$report" "activated"
    --round01-debug-mouse-sequence="click-card-id:$window_id" \
    --round01-quick-switch-quit-after=34.0 \
    --round01-quick-switch-report="$report" >"$app_log" 2>&1 &
  local app_pid="$!"
  if ! wait_for_report "$report" "activated"; then
    kill "$app_pid" 2>/dev/null || true
    wait "$app_pid" 2>/dev/null || true
    return 1
  fi
  wait "$app_pid" 2>/dev/null || true
}
focused_finder_window_report() {
@@ -255,17 +267,19 @@
PY
}
select_target() {
select_targets() {
  local snapshot="$1"
  local report="$2"
  local attributed_ids_csv="$3"
  local output="$4"
  /usr/bin/python3 - "$snapshot" "$report" "$attributed_ids_csv" "$output" <<'PY'
import json
import os
import sys
snapshot_path, report_path, ids_csv, output_path = sys.argv[1:5]
attributed_ids = {int(value) for value in ids_csv.split(",") if value}
max_targets = max(1, int(os.environ.get("ALIGNER_ROUND1_FINDER_TABS_MAX_TARGETS", "4")))
with open(snapshot_path, "r", encoding="utf-8") as file:
    snapshot = json.load(file)
with open(report_path, "r", encoding="utf-8") as file:
@@ -314,25 +328,55 @@
    item[1].get("globalIndex", 10**9),
    item[1].get("windowID", 10**9),
))
column, card, title = cards[0]
target = {
    "windowID": card["windowID"],
    "appGroupIndex": column["appGroupIndex"],
    "windowIndex": card["windowIndex"],
    "primarySpaceID": card.get("primarySpaceID"),
    "title": title,
    "titleLength": len(title),
    "titleHash": card.get("titleHash") or snapshot_windows.get(card.get("windowID"), {}).get("titleHash"),
    "visibleFrame": card.get("visibleFrame"),
}
targets = []
seen_window_ids = set()
for column, card, title in cards:
    window_id = card["windowID"]
    if window_id in seen_window_ids:
        continue
    seen_window_ids.add(window_id)
    targets.append({
        "windowID": window_id,
        "appGroupIndex": column["appGroupIndex"],
        "windowIndex": card["windowIndex"],
        "primarySpaceID": card.get("primarySpaceID"),
        "title": title,
        "titleLength": len(title),
        "titleHash": card.get("titleHash") or snapshot_windows.get(window_id, {}).get("titleHash"),
        "visibleFrame": card.get("visibleFrame"),
    })
    if len(targets) >= max_targets:
        break
require(targets, "Quick Switch report must expose at least one selectable Finder tab/page target")
with open(output_path, "w", encoding="utf-8") as file:
    json.dump(target, file, indent=2, ensure_ascii=False)
    json.dump(targets, file, indent=2, ensure_ascii=False)
print(f"TARGET_COUNT={len(targets)}")
PY
}
target_at_index() {
  local targets="$1"
  local index="$2"
  local output="$3"
  /usr/bin/python3 - "$targets" "$index" "$output" <<'PY'
import json
import sys
targets_path, index_text, output_path = sys.argv[1:4]
index = int(index_text)
with open(targets_path, "r", encoding="utf-8") as file:
    targets = json.load(file)
if index < 0 or index >= len(targets):
    print(f"target index {index} is out of range", file=sys.stderr)
    sys.exit(4)
target = targets[index]
with open(output_path, "w", encoding="utf-8") as file:
    json.dump(target, file, indent=2, ensure_ascii=False)
print(f"TARGET_WINDOW_ID={target['windowID']}")
print(f"APP_GROUP_INDEX={target['appGroupIndex']}")
print(f"WINDOW_INDEX={target['windowIndex']}")
print(f"TARGET_SPACE_ID={target['primarySpaceID']}")
print(f"TARGET_SPACE_ID={target.get('primarySpaceID')}")
PY
}
@@ -416,11 +460,7 @@
SNAPSHOT_DEV_LOG="$REPORT_DIR/snapshot-dev.log"
INITIAL_REPORT="$REPORT_DIR/initial-report.json"
INITIAL_APP_LOG="$REPORT_DIR/initial-app.log"
CLICK_REPORT="$REPORT_DIR/click-report.json"
CLICK_APP_LOG="$REPORT_DIR/click-app.log"
CLICK_DEV_LOG="$REPORT_DIR/click-dev.log"
TARGET_JSON="$REPORT_DIR/target.json"
FOCUSED_JSON="$REPORT_DIR/focused-finder.json"
TARGETS_JSON="$REPORT_DIR/targets.json"
SNAPSHOT_LOG_OFFSET="$(dev_log_size)"
run_snapshot_dump "$SNAPSHOT_JSON" "$SNAPSHOT_STDERR"
@@ -429,16 +469,25 @@
ATTRIBUTED_IDS="$(extract_attributed_ids "$SNAPSHOT_DEV_LOG")"
run_report "$INITIAL_REPORT" "$INITIAL_APP_LOG"
eval "$(select_target "$SNAPSHOT_JSON" "$INITIAL_REPORT" "$ATTRIBUTED_IDS" "$TARGET_JSON")"
eval "$(select_targets "$SNAPSHOT_JSON" "$INITIAL_REPORT" "$ATTRIBUTED_IDS" "$TARGETS_JSON")"
CLICK_LOG_OFFSET="$(dev_log_size)"
run_click "$APP_GROUP_INDEX" "$WINDOW_INDEX" "$CLICK_REPORT" "$CLICK_APP_LOG"
sleep 0.8
extract_dev_log_since "$CLICK_LOG_OFFSET" "$CLICK_DEV_LOG"
focused_finder_window_report "$FOCUSED_JSON"
for ((target_index = 0; target_index < TARGET_COUNT; target_index++)); do
  TARGET_JSON="$REPORT_DIR/target-$target_index.json"
  CLICK_REPORT="$REPORT_DIR/click-report-$target_index.json"
  CLICK_APP_LOG="$REPORT_DIR/click-app-$target_index.log"
  CLICK_DEV_LOG="$REPORT_DIR/click-dev-$target_index.log"
  FOCUSED_JSON="$REPORT_DIR/focused-finder-$target_index.json"
assert_click_report "$CLICK_REPORT" "$TARGET_JSON"
assert_focused_finder_title "$TARGET_JSON" "$FOCUSED_JSON"
  eval "$(target_at_index "$TARGETS_JSON" "$target_index" "$TARGET_JSON")"
  CLICK_LOG_OFFSET="$(dev_log_size)"
  run_click "$TARGET_WINDOW_ID" "$CLICK_REPORT" "$CLICK_APP_LOG"
  sleep 0.8
  extract_dev_log_since "$CLICK_LOG_OFFSET" "$CLICK_DEV_LOG"
  focused_finder_window_report "$FOCUSED_JSON"
  assert_click_report "$CLICK_REPORT" "$TARGET_JSON"
  assert_focused_finder_title "$TARGET_JSON" "$FOCUSED_JSON"
done
stop_current_aligner
@@ -447,6 +496,7 @@
Log directory: $REPORT_DIR
Covered:
- Finder CG-only tab/page records inherit a high-confidence fullscreen Space.
- Quick Switch click targets the exact Finder tab/page windowID.
- Finder focused window title matches the clicked card after activation.
- Quick Switch clicks target exact Finder tab/page windowIDs.
- Finder focused window title matches each clicked card after activation.
- Multiple Finder tab/page targets are exercised in one run to catch intermittent host mismatch.
EOF