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/Tests/AlignerCoreTests/AlignerCoreTests.swift |  651 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 648 insertions(+), 3 deletions(-)

diff --git a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift b/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
index 9eca644..cbc65ff 100644
--- a/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
+++ b/C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
@@ -212,12 +212,25 @@
         let record = makeWindowRecord(
             title: "Closed Document.md",
             hasAXBacking: false,
-            isOnscreen: false,
-            spaceIDs: [5]
+            isOnscreen: false
         )
 
         XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(record))
         XCTAssertNil(WindowEnumerationPolicy.window(from: record))
+    }
+
+    func testWindowEnumerationPolicyKeepsCGOnlyOffscreenWindowsWithKnownSpaceIDs() {
+        let record = makeWindowRecord(
+            title: "Other Space Document.md",
+            hasAXBacking: false,
+            isOnscreen: false,
+            spaceIDs: [5]
+        )
+
+        let window = WindowEnumerationPolicy.window(from: record)
+
+        XCTAssertEqual(window?.title, "Other Space Document.md")
+        XCTAssertEqual(window?.spaceIDs, [5])
     }
 
     func testWindowEnumerationPolicyKeepsAXBackedOffscreenWindows() {
@@ -242,6 +255,334 @@
         let window = WindowEnumerationPolicy.window(from: record)
 
         XCTAssertEqual(window?.title, "Visible Document.md")
+    }
+
+    func testWindowEnumerationPolicyKeepsKnownCGOnlyOffscreenMainWindows() {
+        let hbuilderApp = AlignerApp(
+            bundleIdentifier: "io.dcloud.HBuilderX",
+            name: "HBuilderX",
+            category: .editorIDE
+        )
+        let record = WindowEnumerationRecord(
+            id: 55_388,
+            app: hbuilderApp,
+            title: "lm-app - HBuilder X 5.07",
+            size: CGSize(width: 1048, height: 796),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+        let emptyTitleRecord = WindowEnumerationRecord(
+            id: 55_389,
+            app: hbuilderApp,
+            title: "",
+            size: CGSize(width: 1048, height: 796),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+        let tinyRecord = WindowEnumerationRecord(
+            id: 55_390,
+            app: hbuilderApp,
+            title: "HBuilderX Utility",
+            size: CGSize(width: 80, height: 40),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+
+        XCTAssertEqual(WindowEnumerationPolicy.window(from: record)?.title, "lm-app - HBuilder X 5.07")
+        XCTAssertNil(WindowEnumerationPolicy.window(from: emptyTitleRecord))
+        XCTAssertNil(WindowEnumerationPolicy.window(from: tinyRecord))
+    }
+
+    func testWindowEnumerationPolicyExcludesClosedShottrCGPlaceholderButKeepsVisibleWindow() {
+        let shottrApp = AlignerApp(
+            bundleIdentifier: "cc.ffitch.shottr",
+            name: "Shottr",
+            category: .generic
+        )
+        let closedPlaceholderRecord = WindowEnumerationRecord(
+            id: 78,
+            app: shottrApp,
+            activationPolicy: .accessory,
+            title: "Shottr",
+            size: CGSize(width: 1710, height: 997),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+        let visibleRecord = WindowEnumerationRecord(
+            id: 79,
+            app: shottrApp,
+            activationPolicy: .accessory,
+            title: "Shottr",
+            size: CGSize(width: 1710, height: 997),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: true
+        )
+        let utilityRecord = WindowEnumerationRecord(
+            id: 102,
+            app: shottrApp,
+            activationPolicy: .accessory,
+            title: "Colors",
+            size: CGSize(width: 250, height: 397),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+
+        XCTAssertNil(WindowEnumerationPolicy.window(from: closedPlaceholderRecord))
+        XCTAssertEqual(WindowEnumerationPolicy.window(from: visibleRecord)?.title, "Shottr")
+        XCTAssertNil(WindowEnumerationPolicy.window(from: utilityRecord))
+    }
+
+    func testWindowEnumerationPolicyKeepsFinderCGOnlyOffscreenMainWindows() {
+        let finderApp = AlignerApp(
+            bundleIdentifier: "com.apple.finder",
+            name: "访达",
+            category: .finder
+        )
+        let record = WindowEnumerationRecord(
+            id: 83_664,
+            app: finderApp,
+            title: "Screencapture",
+            size: CGSize(width: 920, height: 436),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+        let emptyTitleRecord = WindowEnumerationRecord(
+            id: 83_665,
+            app: finderApp,
+            title: "",
+            size: CGSize(width: 920, height: 436),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+        let tinyRecord = WindowEnumerationRecord(
+            id: 83_666,
+            app: finderApp,
+            title: "Finder Utility",
+            size: CGSize(width: 80, height: 40),
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+
+        XCTAssertEqual(WindowEnumerationPolicy.window(from: record)?.title, "Screencapture")
+        XCTAssertNil(WindowEnumerationPolicy.window(from: emptyTitleRecord))
+        XCTAssertNil(WindowEnumerationPolicy.window(from: tinyRecord))
+    }
+
+    func testFinderTabSpaceAttributionInheritsFullscreenHostSpaceForMatchingFrame() {
+        let finderApp = AlignerApp(
+            bundleIdentifier: "com.apple.finder",
+            name: "访达",
+            category: .finder,
+            processIdentifier: 74678
+        )
+        let frame = CGRect(x: 0, y: 0, width: 960, height: 1080)
+        let host = WindowEnumerationRecord(
+            id: 33112,
+            app: finderApp,
+            title: "下载",
+            size: frame.size,
+            frame: frame,
+            subrole: .standard,
+            isFullscreen: true,
+            hasAXBacking: true,
+            spaceIDs: [843]
+        )
+        let inactiveTab = WindowEnumerationRecord(
+            id: 42286,
+            app: finderApp,
+            title: "Appcache",
+            size: frame.size,
+            frame: frame,
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+
+        let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([inactiveTab, host])
+
+        XCTAssertEqual(attributed[0].spaceIDs, [843])
+        XCTAssertTrue(attributed[0].isFullscreen)
+    }
+
+    func testFinderTabSpaceAttributionDoesNotGuessNoSpaceWindowsWithoutHostOverlap() {
+        let finderApp = AlignerApp(
+            bundleIdentifier: "com.apple.finder",
+            name: "访达",
+            category: .finder,
+            processIdentifier: 74678
+        )
+        let otherApp = AlignerApp(
+            bundleIdentifier: "com.example.Other",
+            name: "Other",
+            category: .generic,
+            processIdentifier: 74678
+        )
+        let hostFrame = CGRect(x: 0, y: 0, width: 960, height: 1080)
+        let farFrame = CGRect(x: 1200, y: 0, width: 500, height: 600)
+        let host = WindowEnumerationRecord(
+            id: 33112,
+            app: finderApp,
+            title: "下载",
+            size: hostFrame.size,
+            frame: hostFrame,
+            subrole: .standard,
+            isFullscreen: true,
+            hasAXBacking: true,
+            spaceIDs: [843]
+        )
+        let farFinderTab = WindowEnumerationRecord(
+            id: 42286,
+            app: finderApp,
+            title: "Appcache",
+            size: farFrame.size,
+            frame: farFrame,
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+        let nonFinderWindow = WindowEnumerationRecord(
+            id: 50222,
+            app: otherApp,
+            title: "Appcache",
+            size: hostFrame.size,
+            frame: hostFrame,
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+
+        let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([farFinderTab, nonFinderWindow, host])
+
+        XCTAssertEqual(attributed[0].spaceIDs, [])
+        XCTAssertFalse(attributed[0].isFullscreen)
+        XCTAssertEqual(attributed[1].spaceIDs, [])
+        XCTAssertFalse(attributed[1].isFullscreen)
+    }
+
+    func testFinderTabSpaceAttributionHandlesFinderSplitViewHostsByFrame() {
+        let finderApp = AlignerApp(
+            bundleIdentifier: "com.apple.finder",
+            name: "访达",
+            category: .finder,
+            processIdentifier: 74678
+        )
+        let leftFrame = CGRect(x: 0, y: 0, width: 960, height: 1080)
+        let rightFrame = CGRect(x: 960, y: 0, width: 960, height: 1080)
+        let leftHost = WindowEnumerationRecord(
+            id: 33112,
+            app: finderApp,
+            title: "下载",
+            size: leftFrame.size,
+            frame: leftFrame,
+            subrole: .standard,
+            isFullscreen: true,
+            hasAXBacking: true,
+            spaceIDs: [843]
+        )
+        let rightHost = WindowEnumerationRecord(
+            id: 41557,
+            app: finderApp,
+            title: "应用程序",
+            size: rightFrame.size,
+            frame: rightFrame,
+            subrole: .standard,
+            isFullscreen: true,
+            hasAXBacking: true,
+            spaceIDs: [843]
+        )
+        let leftInactiveTab = WindowEnumerationRecord(
+            id: 42286,
+            app: finderApp,
+            title: "Appcache",
+            size: leftFrame.size,
+            frame: leftFrame,
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+        let rightInactiveTab = WindowEnumerationRecord(
+            id: 61385,
+            app: finderApp,
+            title: "build",
+            size: rightFrame.size,
+            frame: rightFrame,
+            subrole: .standard,
+            hasAXBacking: false,
+            isOnscreen: false
+        )
+
+        let attributed = FinderTabSpaceAttributionPolicy.attributedRecords([
+            leftInactiveTab,
+            rightInactiveTab,
+            leftHost,
+            rightHost
+        ])
+
+        XCTAssertEqual(attributed[0].spaceIDs, [843])
+        XCTAssertEqual(attributed[1].spaceIDs, [843])
+        XCTAssertTrue(attributed[0].isFullscreen)
+        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() {
@@ -740,6 +1081,7 @@
                 "schemaVersion",
                 "quickSwitch.showMinimizedWindows",
                 "quickSwitch.showFullscreenWindows",
+                "quickSwitch.waterfallViewMode",
                 "appearance.theme",
                 "appearance.language",
                 "spaces.displayStrategy"
@@ -752,9 +1094,10 @@
     }
 
     func testDefaultPreferencesMatchRound0Decisions() {
-        XCTAssertEqual(PreferenceSchema.version, 1)
+        XCTAssertEqual(PreferenceSchema.version, 2)
         XCTAssertEqual(PreferenceSchema.defaults.showMinimizedWindows, true)
         XCTAssertEqual(PreferenceSchema.defaults.showFullscreenWindows, true)
+        XCTAssertEqual(PreferenceSchema.defaults.quickSwitchWaterfallViewMode, .verticalColumns)
         XCTAssertEqual(PreferenceSchema.defaults.theme, .system)
         XCTAssertEqual(PreferenceSchema.defaults.language, .system)
         XCTAssertEqual(PreferenceSchema.defaults.spaceDisplayStrategy, .includeVisibleAndFullscreen)
@@ -775,6 +1118,7 @@
         let preferences = AlignerPreferences(
             showMinimizedWindows: false,
             showFullscreenWindows: false,
+            quickSwitchWaterfallViewMode: .horizontalMasonry,
             theme: .dark,
             language: .simplifiedChinese,
             spaceDisplayStrategy: .currentSpaceOnly
@@ -812,12 +1156,14 @@
         userDefaults.set("not-a-theme", forKey: PreferenceKey.theme.storageKey)
         userDefaults.set("not-a-language", forKey: PreferenceKey.language.storageKey)
         userDefaults.set("not-a-space-strategy", forKey: PreferenceKey.spaceDisplayStrategy.storageKey)
+        userDefaults.set("not-a-waterfall-mode", forKey: PreferenceKey.quickSwitchWaterfallViewMode.storageKey)
 
         let preferences = UserDefaultsPreferenceStore(userDefaults: userDefaults).read()
 
         XCTAssertEqual(preferences.theme, PreferenceSchema.defaults.theme)
         XCTAssertEqual(preferences.language, PreferenceSchema.defaults.language)
         XCTAssertEqual(preferences.spaceDisplayStrategy, PreferenceSchema.defaults.spaceDisplayStrategy)
+        XCTAssertEqual(preferences.quickSwitchWaterfallViewMode, PreferenceSchema.defaults.quickSwitchWaterfallViewMode)
     }
 
     @MainActor
@@ -1041,6 +1387,50 @@
         XCTAssertEqual(snapshot.appGroups.single?.windows.map(\.primarySpaceID), [10, 11, 12, 12, nil])
     }
 
+    func testWindowSpaceMappingFillsUnresolvedWindowsFromSpaceInverseMap() {
+        let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
+            requestedWindowIDs: [10, 20, 30],
+            directSpaceIDsByWindowID: [10: [100]],
+            windowIDsBySpaceID: [
+                100: [10, 20],
+                101: [30]
+            ],
+            orderedSpaceIDs: [100, 101]
+        )
+
+        XCTAssertEqual(mapping[10], [100])
+        XCTAssertEqual(mapping[20], [100])
+        XCTAssertEqual(mapping[30], [101])
+    }
+
+    func testWindowSpaceMappingDoesNotOverwriteDirectSpaceAssignments() {
+        let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
+            requestedWindowIDs: [10],
+            directSpaceIDsByWindowID: [10: [101]],
+            windowIDsBySpaceID: [
+                100: [10]
+            ],
+            orderedSpaceIDs: [100, 101]
+        )
+
+        XCTAssertEqual(mapping[10], [101])
+    }
+
+    func testWindowSpaceMappingLeavesAmbiguousNoSpaceWindowsUnassigned() {
+        let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
+            requestedWindowIDs: [10, 20],
+            directSpaceIDsByWindowID: [:],
+            windowIDsBySpaceID: [
+                100: [10, 99]
+            ],
+            orderedSpaceIDs: [100]
+        )
+
+        XCTAssertEqual(mapping[10], [100])
+        XCTAssertNil(mapping[20])
+        XCTAssertNil(mapping[99])
+    }
+
     func testQuickSwitchSnapshotKeepsMinimizedAndFullscreenWindows() {
         let app = makeApp()
         let snapshot = QuickSwitchSnapshotBuilder.snapshot(
@@ -1085,6 +1475,261 @@
         XCTAssertEqual(viewModel.initialSelection, QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 1))
     }
 
+    func testQuickSwitchViewModelBuilderUsesNumericSpaceLabelsForSingleDisplay() {
+        let app = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [makeDisplay(spaceIDs: [10, 11, 12])],
+            windows: [
+                AlignerWindow(id: 1, app: app, title: "A1", spaceIDs: [10]),
+                AlignerWindow(id: 2, app: app, title: "A2", spaceIDs: [11])
+            ]
+        )
+
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot, currentSpaceIDs: [11])
+
+        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.map(\.label) }, ["01", "02", "03"])
+        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.filter(\.isCurrent).map(\.label) }, ["02"])
+        XCTAssertEqual(viewModel.waterfallColumns.flatMap { $0.windows.map(\.primarySpaceLabel) }, ["01", "02"])
+    }
+
+    func testQuickSwitchSpaceFilterProjectsAppShelfAndWaterfallWithoutChangingSpaceLane() {
+        let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+        let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [makeDisplay(spaceIDs: [10, 11])],
+            windows: [
+                AlignerWindow(id: 1, app: alpha, title: "A1", spaceIDs: [10]),
+                AlignerWindow(id: 2, app: alpha, title: "A2", spaceIDs: [11]),
+                AlignerWindow(id: 3, app: beta, title: "B1", spaceIDs: [11]),
+                AlignerWindow(id: 4, app: beta, title: "No Space")
+            ]
+        )
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+
+        let filtered = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+            from: viewModel,
+            lockedSpaceID: 11
+        )
+
+        XCTAssertEqual(filtered.lockedSpaceID, 11)
+        XCTAssertEqual(filtered.displays.flatMap { $0.spaces.map(\.id) }, [10, 11])
+        XCTAssertEqual(filtered.displays.flatMap { $0.spaces.map(\.windowCount) }, [1, 2])
+        XCTAssertEqual(filtered.appShelf.map(\.app.name), ["Alpha", "Beta"])
+        XCTAssertEqual(filtered.appShelf.map(\.windowCount), [1, 1])
+        XCTAssertEqual(filtered.appShelf.flatMap(\.primarySpaceIDs), [11, 11])
+        XCTAssertEqual(filtered.waterfallColumns.flatMap { $0.windows.map(\.window.id) }, [2, 3])
+        XCTAssertEqual(filtered.waterfallColumns.flatMap { $0.windows.map(\.windowIndex) }, [0, 0])
+        XCTAssertEqual(filtered.waterfallColumns.flatMap { $0.windows.map(\.globalIndex) }, [0, 1])
+        XCTAssertEqual(filtered.initialSelection, QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 2))
+    }
+
+    func testQuickSwitchSpaceFilterKeyboardNavigationStaysInsideFilteredCollection() {
+        let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+        let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [makeDisplay(spaceIDs: [10, 11])],
+            windows: [
+                AlignerWindow(id: 1, app: alpha, title: "Alpha A1", spaceIDs: [10]),
+                AlignerWindow(id: 2, app: alpha, title: "Alpha A2", spaceIDs: [11]),
+                AlignerWindow(id: 3, app: beta, title: "Beta A2", spaceIDs: [11]),
+                AlignerWindow(id: 4, app: beta, title: "Beta No Space")
+            ]
+        )
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+        let filtered = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+            from: viewModel,
+            lockedSpaceID: 11
+        )
+        let filteredSnapshot = QuickSwitchSnapshot(
+            displays: snapshot.displays,
+            appGroups: filtered.waterfallColumns.map { column in
+                QuickSwitchAppGroup(
+                    app: column.app,
+                    windows: column.windows.map {
+                        QuickSwitchWindowItem(window: $0.window, primarySpaceID: $0.primarySpaceID)
+                    }
+                )
+            }
+        )
+
+        XCTAssertEqual(filtered.initialSelection, QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 2))
+        XCTAssertEqual(
+            QuickSwitchFocusPolicy.nextSelection(
+                from: filtered.initialSelection!,
+                in: filteredSnapshot,
+                direction: .right
+            ),
+            QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3)
+        )
+        XCTAssertEqual(
+            QuickSwitchFocusPolicy.nextSelection(
+                from: QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3),
+                in: filteredSnapshot,
+                direction: .right
+            ),
+            QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3)
+        )
+        XCTAssertFalse(filteredSnapshot.appGroups.flatMap(\.windows).contains { $0.window.id == 1 || $0.window.id == 4 })
+    }
+
+    func testQuickSwitchSpaceFilterCanLockEmptySpaceWithoutChangingSpaceLane() {
+        let app = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [makeDisplay(spaceIDs: [10, 11])],
+            windows: [
+                AlignerWindow(id: 1, app: app, title: "A1", spaceIDs: [10])
+            ]
+        )
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+
+        let filtered = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+            from: viewModel,
+            lockedSpaceID: 11
+        )
+
+        XCTAssertEqual(filtered.lockedSpaceID, 11)
+        XCTAssertEqual(filtered.displays.flatMap { $0.spaces.map(\.id) }, [10, 11])
+        XCTAssertTrue(filtered.appShelf.isEmpty)
+        XCTAssertTrue(filtered.waterfallColumns.isEmpty)
+        XCTAssertNil(filtered.initialSelection)
+    }
+
+    func testQuickSwitchSpaceFilterRestoresPreferredSelectionWhenUnlocking() {
+        let app = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [makeDisplay(spaceIDs: [10, 11])],
+            windows: [
+                AlignerWindow(id: 1, app: app, title: "A1", spaceIDs: [10]),
+                AlignerWindow(id: 2, app: app, title: "A2", spaceIDs: [11])
+            ]
+        )
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+        let preferred = QuickSwitchSelection(appGroupIndex: 0, windowIndex: 1, windowID: 2)
+
+        let unlocked = QuickSwitchSpaceFilterPolicy.projectedViewModel(
+            from: viewModel,
+            lockedSpaceID: nil,
+            preferredSelection: preferred
+        )
+
+        XCTAssertNil(unlocked.lockedSpaceID)
+        XCTAssertEqual(unlocked.initialSelection, preferred)
+    }
+
+    func testQuickSwitchSpaceFilterDetectsOnlySingleFullscreenWindowSpacesAsDirectActivation() {
+        let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
+        let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
+        let displayUUID = "display-a"
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [
+                AlignerDisplay(
+                    uuid: displayUUID,
+                    physical: true,
+                    spaces: [
+                        AlignerSpace(id: 10, type: .user, displayUUID: displayUUID, index: 1),
+                        AlignerSpace(id: 11, type: .fullscreen, displayUUID: displayUUID, index: 2),
+                        AlignerSpace(id: 12, type: .fullscreen, displayUUID: displayUUID, index: 3)
+                    ]
+                )
+            ],
+            windows: [
+                AlignerWindow(id: 1, app: alpha, title: "Normal", spaceIDs: [10]),
+                AlignerWindow(id: 2, app: alpha, title: "Fullscreen", isFullscreen: true, spaceIDs: [11]),
+                AlignerWindow(id: 3, app: beta, title: "Split A", isFullscreen: true, spaceIDs: [12]),
+                AlignerWindow(id: 4, app: beta, title: "Split B", isFullscreen: true, spaceIDs: [12])
+            ]
+        )
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+
+        XCTAssertEqual(
+            QuickSwitchSpaceFilterPolicy.singleFullscreenSelection(inSpaceID: 11, viewModel: viewModel),
+            QuickSwitchSelection(appGroupIndex: 0, windowIndex: 1, windowID: 2)
+        )
+        XCTAssertNil(QuickSwitchSpaceFilterPolicy.singleFullscreenSelection(inSpaceID: 10, viewModel: viewModel))
+        XCTAssertNil(QuickSwitchSpaceFilterPolicy.singleFullscreenSelection(inSpaceID: 12, viewModel: viewModel))
+    }
+
+    func testQuickSwitchViewModelOrdersSplitViewAppNamesByWindowFrame() {
+        let leftApp = AlignerApp(bundleIdentifier: "com.example.Left", name: "Left App", category: .generic)
+        let rightApp = AlignerApp(bundleIdentifier: "com.example.Right", name: "Right App", category: .generic)
+        let displayUUID = "display-a"
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [
+                AlignerDisplay(
+                    uuid: displayUUID,
+                    physical: true,
+                    spaces: [
+                        AlignerSpace(id: 20, type: .fullscreen, displayUUID: displayUUID, index: 1)
+                    ]
+                )
+            ],
+            windows: [
+                AlignerWindow(
+                    id: 2,
+                    app: rightApp,
+                    title: "Right",
+                    isFullscreen: true,
+                    frame: CGRect(x: 960, y: 0, width: 960, height: 1080),
+                    spaceIDs: [20]
+                ),
+                AlignerWindow(
+                    id: 1,
+                    app: leftApp,
+                    title: "Left",
+                    isFullscreen: true,
+                    frame: CGRect(x: 0, y: 0, width: 960, height: 1080),
+                    spaceIDs: [20]
+                )
+            ]
+        )
+
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+        let space = viewModel.displays.single?.spaces.single
+
+        XCTAssertEqual(space?.splitViewAppNames, ["Left App", "Right App"])
+        XCTAssertEqual(space?.appNames, ["Left App", "Right App"])
+    }
+
+    func testQuickSwitchViewModelKeepsDuplicateAppNamesForSameAppSplitView() {
+        let finder = AlignerApp(bundleIdentifier: "com.apple.finder", name: "访达", category: .finder)
+        let displayUUID = "display-a"
+        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
+            displays: [
+                AlignerDisplay(
+                    uuid: displayUUID,
+                    physical: true,
+                    spaces: [
+                        AlignerSpace(id: 21, type: .fullscreen, displayUUID: displayUUID, index: 1)
+                    ]
+                )
+            ],
+            windows: [
+                AlignerWindow(
+                    id: 12,
+                    app: finder,
+                    title: "右侧访达",
+                    isFullscreen: true,
+                    frame: CGRect(x: 900, y: 0, width: 900, height: 1000),
+                    spaceIDs: [21]
+                ),
+                AlignerWindow(
+                    id: 11,
+                    app: finder,
+                    title: "左侧访达",
+                    isFullscreen: true,
+                    frame: CGRect(x: 0, y: 0, width: 900, height: 1000),
+                    spaceIDs: [21]
+                )
+            ]
+        )
+
+        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot)
+        let space = viewModel.displays.single?.spaces.single
+
+        XCTAssertEqual(space?.splitViewAppNames, ["访达", "访达"])
+        XCTAssertEqual(space?.appNames, ["访达"])
+    }
+
     func testQuickSwitchFocusPolicySelectsFirstStableWindowInitially() {
         let app = makeApp()
         let snapshot = QuickSwitchSnapshotBuilder.snapshot(

--
Gitblit v1.9.3