From 343237725f65445bab306e3fe4cdbd10949d4d4b Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 13 Jun 2026 18:47:36 +0800
Subject: [PATCH] Fix Finder tab page activation

---
 C1.source/Sources/AlignerCore/Windows/FinderTabSpaceAttributionPolicy.swift    |   32 ++
 C3.tools/round1-finder-tabs-live-qa.sh                                         |  138 +++++++---
 C1.source/Sources/Aligner/QuickSwitchRootView.swift                            |   16 +
 C1.source/Resources/Aligner-Info.plist                                         |    4 
 C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift |  507 +++++++++++++++++++++++++++++++++++++--
 C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift                        |   52 ++++
 6 files changed, 675 insertions(+), 74 deletions(-)

diff --git a/C1.source/Resources/Aligner-Info.plist b/C1.source/Resources/Aligner-Info.plist
index fdeb485..6118a38 100644
--- a/C1.source/Resources/Aligner-Info.plist
+++ b/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>
diff --git a/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift b/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
index b1b1cd3..e03dddb 100644
--- a/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
+++ b/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 {
diff --git a/C1.source/Sources/Aligner/QuickSwitchRootView.swift b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
index 3d16422..c04faf7 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/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)
diff --git a/C1.source/Sources/AlignerCore/Windows/FinderTabSpaceAttributionPolicy.swift b/C1.source/Sources/AlignerCore/Windows/FinderTabSpaceAttributionPolicy.swift
index 0de281d..9c40e17 100644
--- a/C1.source/Sources/AlignerCore/Windows/FinderTabSpaceAttributionPolicy.swift
+++ b/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,
diff --git a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift b/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
index 042e94c..cbc65ff 100644
--- a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
+++ b/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",
diff --git a/C3.tools/round1-finder-tabs-live-qa.sh b/C3.tools/round1-finder-tabs-live-qa.sh
index 99fc856..1773b03 100755
--- a/C3.tools/round1-finder-tabs-live-qa.sh
+++ b/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

--
Gitblit v1.9.3