From 7a4f911c64b552081d957e826f32d90b2b11c858 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 04 Jun 2026 19:38:13 +0800
Subject: [PATCH] Make quick switch clicks activate targets

---
 C1.source/Sources/Aligner/QuickSwitchRootView.swift |   42 ++++++++++++-
 C1.source/Resources/Aligner-Info.plist              |    4 
 C3.tools/round1-mouse-interaction-fixture-qa.sh     |   26 +++++---
 C3.tools/round1-three-zone-linkage-fixture-qa.sh    |   84 +++++++++++++++++++++++++---
 4 files changed, 131 insertions(+), 25 deletions(-)

diff --git a/C1.source/Resources/Aligner-Info.plist b/C1.source/Resources/Aligner-Info.plist
index 1c6dda1..dc25940 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.25</string>
+	<string>0.0.26</string>
 	<key>CFBundleVersion</key>
-	<string>20260604.1854</string>
+	<string>20260604.1927</string>
 	<key>LSMinimumSystemVersion</key>
 	<string>26.0</string>
 	<key>NSHighResolutionCapable</key>
diff --git a/C1.source/Sources/Aligner/QuickSwitchRootView.swift b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
index f179043..046f96a 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -359,6 +359,9 @@
             if isHoverCommand(command) {
                 hoverResponseLatencyMilliseconds.append(elapsed)
             }
+            if lastCommitSource == .mouse {
+                break
+            }
         }
     }
 
@@ -723,7 +726,17 @@
             "spaceID": segment.space.id,
             "label": segment.space.label
         ])
-        focusSpace(segment.space.id, persistent: true)
+        lastSpaceLaneClickSpaceID = segment.space.id
+
+        guard let firstCard = firstWaterfallCard(inSpaceID: segment.space.id) else {
+            DevelopmentDiagnostics.log("quickSwitch.view.clickSpaceLane.emptySpaceUnsupported", [
+                "spaceID": segment.space.id,
+                "label": segment.space.label
+            ])
+            return
+        }
+
+        commitWindowCard(firstCard, trigger: "spaceLane")
     }
 
     private func focusSpace(_ spaceID: UInt64, persistent: Bool) {
@@ -774,12 +787,14 @@
             columnSelectionHistory[nextSelection.appGroupIndex] = nextSelection.windowIndex
             lastAppShelfClickChangedSelection = previousSelection != nextSelection
             ensureWaterfallSelectionVisible(nextSelection, horizontalIntent: .alignWithAppShelf(animated: true))
+            updateHoverState(appGroupIndex: appGroupIndex, windowID: nil, spaceID: nil)
+            commitWindowCard(firstCard, trigger: "appShelf")
         } else {
             lastAppShelfClickChangedSelection = false
             ensureWaterfallColumnVisible(column)
+            updateHoverState(appGroupIndex: appGroupIndex, windowID: nil, spaceID: nil)
+            needsLayout = true
         }
-        updateHoverState(appGroupIndex: appGroupIndex, windowID: nil, spaceID: nil)
-        needsLayout = true
     }
 
     private func scrollAppShelf(to targetOffset: CGFloat) {
@@ -821,12 +836,17 @@
 
     private func clickWindowCard(_ card: WaterfallCardLayers) {
         hoverWindowCard(card)
+        commitWindowCard(card, trigger: "windowCard")
+    }
+
+    private func commitWindowCard(_ card: WaterfallCardLayers, trigger: String) {
         let selection = QuickSwitchSelection(
             appGroupIndex: card.item.appGroupIndex,
             windowIndex: card.item.windowIndex,
             windowID: card.item.window.id
         )
-        DevelopmentDiagnostics.log("quickSwitch.view.clickWindowCard", [
+        DevelopmentDiagnostics.log("quickSwitch.view.commitMouseWindow", [
+            "trigger": trigger,
             "appGroupIndex": selection.appGroupIndex,
             "windowIndex": selection.windowIndex,
             "windowID": selection.windowID,
@@ -1231,7 +1251,7 @@
         glassLayer.addSublayer(laneLayer)
         laneLayer.addSublayer(spaceLaneContentLayer)
 
-        shelfLayer.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.60).cgColor
+        shelfLayer.backgroundColor = NSColor.clear.cgColor
         shelfLayer.cornerRadius = 16
         shelfLayer.masksToBounds = true
         glassLayer.addSublayer(shelfLayer)
@@ -1934,6 +1954,18 @@
             .sorted()
     }
 
+    private func firstWaterfallCard(inSpaceID spaceID: UInt64) -> WaterfallCardLayers? {
+        for column in waterfallColumns.sorted(by: { $0.column.appGroupIndex < $1.column.appGroupIndex }) {
+            if let card = column.cards
+                .sorted(by: { $0.item.windowIndex < $1.item.windowIndex })
+                .first(where: { $0.item.primarySpaceID == spaceID }) {
+                return card
+            }
+        }
+
+        return nil
+    }
+
     private func spaceFocusWindowCount(for appGroupIndex: Int) -> Int {
         guard let activeSpaceFocusID else { return 0 }
 
diff --git a/C3.tools/round1-mouse-interaction-fixture-qa.sh b/C3.tools/round1-mouse-interaction-fixture-qa.sh
index d0a6375..ce9793e 100755
--- a/C3.tools/round1-mouse-interaction-fixture-qa.sh
+++ b/C3.tools/round1-mouse-interaction-fixture-qa.sh
@@ -15,7 +15,7 @@
 APP_ONLY_REPORT="$BUILD_REPORT_ROOT/round01-mouse-app-only-fixture-report.json"
 FIXTURE_APP_COUNT="${ALIGNER_ROUND1_MOUSE_FIXTURE_APP_COUNT:-8}"
 FIXTURE_WINDOWS_PER_APP="${ALIGNER_ROUND1_MOUSE_FIXTURE_WINDOWS_PER_APP:-8}"
-MOUSE_SEQUENCE="${ALIGNER_ROUND1_MOUSE_SEQUENCE:-hover-card:1:7,hover-app:5,click-app:7,hover-card:7:0,click-card:7:0}"
+MOUSE_SEQUENCE="${ALIGNER_ROUND1_MOUSE_SEQUENCE:-hover-card:1:7,hover-app:5,hover-card:7:0,click-card:7:0}"
 APP_ONLY_MOUSE_SEQUENCE="${ALIGNER_ROUND1_MOUSE_APP_ONLY_SEQUENCE:-hover-app:5,click-app:7}"
 REPORT_WAIT="${ALIGNER_ROUND1_MOUSE_REPORT_WAIT:-6.0}"
 WINDOW_WAIT="${ALIGNER_ROUND1_MOUSE_WINDOW_WAIT:-3.0}"
@@ -47,7 +47,8 @@
 }
 
 wait_for_committed_hidden_report() {
-  /usr/bin/python3 - "$REPORT" "$REPORT_WAIT" <<'PY'
+  local report="${1:-$REPORT}"
+  /usr/bin/python3 - "$report" "$REPORT_WAIT" <<'PY'
 import json
 import sys
 import time
@@ -177,15 +178,21 @@
 target_app_group_index = int(last_click_parts[1])
 
 require(report.get("snapshotLoaded") is True, "app-only snapshotLoaded must be true")
-require(report.get("quickSwitchVisible") is True, "hover/click App Shelf must not close Quick Switch")
-require(report.get("lastCommittedWindowID") is None, "hover/click App Shelf must not commit a window")
+require(report.get("quickSwitchVisible") is False, "click App Shelf must close Quick Switch after committing its first window")
 require(root.get("mouseCommandsApplied") == mouse_sequence, "app-only mouse sequence must be applied in order")
 require(0 <= target_app_group_index < fixture_app_count, "click-app target App group must be inside fixture range")
 target_column = columns[target_app_group_index]
 target_first_card = target_column.get("cards", [])[0]
+expected_window_id = target_first_card.get("windowID")
 require(root.get("selectedAppGroupIndex") == target_app_group_index, "click App Shelf must move keyboard selection to target App")
 require(root.get("selectedWindowIndex") == target_first_card.get("windowIndex"), "click App Shelf must select the first target-column window")
 require(root.get("selectedWindowID") == target_first_card.get("windowID"), "click App Shelf must focus the first target-column window")
+require(root.get("lastCommittedWindowID") == expected_window_id, "click App Shelf must commit the first target-column window")
+require(report.get("lastCommittedWindowID") == expected_window_id, "session must surface the App Shelf commit")
+require(root.get("lastCommitSource") == "mouse", "root App Shelf commit source must be mouse")
+require(report.get("lastCommitSource") == "mouse", "session App Shelf commit source must be mouse")
+require(report.get("lastActivationWindowID") == expected_window_id, "App Shelf activation window must be the first target-column window")
+require(report.get("lastActivationResult") == "activated", "debug activation must activate the App Shelf first window")
 require(root.get("lastClickedAppGroupIndex") == target_app_group_index, "click App Shelf must record target App group")
 require(root.get("lastAppShelfClickChangedSelection") is True, "click App Shelf must explicitly report selection moved to target column")
 require(root.get("waterfallScrollOffset", 0) > 0, "clicking far-right App Shelf item must position its Waterfall column")
@@ -197,6 +204,8 @@
 print(json.dumps({
     "appOnlyQuickSwitchVisible": report.get("quickSwitchVisible"),
     "selectedAppGroupIndex": root.get("selectedAppGroupIndex"),
+    "lastCommittedWindowID": root.get("lastCommittedWindowID"),
+    "lastActivationResult": report.get("lastActivationResult"),
     "lastClickedAppGroupIndex": root.get("lastClickedAppGroupIndex"),
     "waterfallScrollOffset": root.get("waterfallScrollOffset")
 }, indent=2, ensure_ascii=False))
@@ -260,10 +269,8 @@
 require(report.get("lastActivationWindowID") == expected_window_id, "session activation window must match clicked card")
 require(report.get("lastActivationResult") == "activated", "debug activation must activate a non-minimized clicked card")
 
-require(root.get("lastClickedAppGroupIndex") == expected_app_group_index, "clicking App Shelf must record target App group")
-require(root.get("lastAppShelfClickChangedSelection") is True, "clicking App Shelf must move keyboard selection before the card click")
 require(root.get("waterfallScrollable") is True, "fixture must make Waterfall horizontally scrollable")
-require(root.get("waterfallScrollOffset", 0) > 0, "clicking far-right App must scroll its Waterfall column into view")
+require(root.get("waterfallScrollOffset", 0) > 0, "clicking far-right card must scroll its Waterfall column into view")
 
 selected_cards = [card for column in columns for card in column.get("cards", []) if card.get("isSelected") is True]
 require(len(selected_cards) == 1, "Waterfall must expose exactly one selected card after click")
@@ -312,6 +319,7 @@
   --round01-fixture-app-count="$FIXTURE_APP_COUNT" \
   --round01-fixture-windows-per-app="$FIXTURE_WINDOWS_PER_APP" \
   --round01-disable-screenshot-refresh \
+  --round01-debug-window-activation \
   --round01-debug-mouse-sequence="$APP_ONLY_MOUSE_SEQUENCE" \
   --round01-quick-switch-report="$APP_ONLY_REPORT" &
 
@@ -324,9 +332,9 @@
 }
 trap cleanup EXIT
 
-wait_for_visible_loaded_report "$APP_ONLY_REPORT"
+wait_for_committed_hidden_report "$APP_ONLY_REPORT"
 assert_app_only_report
-wait_for_window_logic "$WINDOW_WAIT" --expect-quick-switch >&2
+wait_for_window_logic "$WINDOW_WAIT" --expect-no-quick-switch >&2
 kill "$APP_PID" 2>/dev/null || true
 wait "$APP_PID" 2>/dev/null || true
 stop_current_aligner
diff --git a/C3.tools/round1-three-zone-linkage-fixture-qa.sh b/C3.tools/round1-three-zone-linkage-fixture-qa.sh
index a3f4935..4fcd850 100755
--- a/C3.tools/round1-three-zone-linkage-fixture-qa.sh
+++ b/C3.tools/round1-three-zone-linkage-fixture-qa.sh
@@ -121,6 +121,7 @@
   local expected_last_mouse="$3"
   local app_count="${4:-$FIXTURE_APP_COUNT}"
   local windows_per_app="${5:-$FIXTURE_WINDOWS_PER_APP}"
+  local quick_switch_expectation="${6:-visible}"
 
   rm -f "$report"
   "$APP/Contents/MacOS/Aligner" \
@@ -130,13 +131,18 @@
     --round01-fixture-windows-per-app="$windows_per_app" \
     --round01-debug-overlay-width="$OVERLAY_WIDTH" \
     --round01-disable-screenshot-refresh \
+    --round01-debug-window-activation \
     --round01-debug-mouse-sequence="$sequence" \
     --round01-quick-switch-report="$report" &
 
   APP_PID=$!
 
   wait_for_report "$report" "$expected_last_mouse"
-  swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-quick-switch >&2
+  if [ "$quick_switch_expectation" = "hidden" ]; then
+    swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch >&2
+  else
+    swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-quick-switch >&2
+  fi
 
   kill "$APP_PID" 2>/dev/null || true
   wait "$APP_PID" 2>/dev/null || true
@@ -168,16 +174,17 @@
 segments = root.get("spaceLaneSegments", [])
 
 require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
-require(root.get("activeSpaceFocusID") == 1, "Space Lane focus must set activeSpaceFocusID")
-require(root.get("spaceLaneFocusedSpaceID") == 1, "click-space must persist focused Space")
-require(root.get("lastSpaceLaneClickSpaceID") == 1, "last clicked Space must be recorded")
+require(root.get("activeSpaceFocusID") == 1, "Space Lane hover must set activeSpaceFocusID")
+require(root.get("spaceLaneHoveredSpaceID") == 1, "hover-space must expose hovered Space")
+require(root.get("spaceLaneFocusedSpaceID") is None, "hover-space must not persist clicked Space focus")
+require(root.get("lastSpaceLaneClickSpaceID") is None, "hover-space must not record a Space click")
 require(root.get("lastSpaceFocusedAppGroupIndex") == 0, "first related App column must be appGroupIndex 0")
 require(root.get("spaceFocusAppGroupIndexes") == list(range(fixture_app_count)), "all fixture apps must be marked related to focused Space")
 require(root.get("waterfallScrollOffset") == 0, "first related App column should remain visible at offset zero")
 
 focused_segments = [segment for segment in segments if segment.get("isFocused") is True]
 require(len(focused_segments) == 1, "exactly one Space Lane segment must be focused")
-require(focused_segments[0].get("label") == "A1", "focused Space should be A1")
+require(focused_segments[0].get("label") == "01", "focused Space should be 01")
 require("focused" in focused_segments[0].get("visualStates", []), "focused Space segment must expose focused visual state")
 
 require(len(items) == fixture_app_count, "App Shelf fixture item count must match app count")
@@ -226,11 +233,17 @@
 columns = root.get("waterfallColumns", [])
 last_index = fixture_app_count - 1
 
-require(root.get("activeSpaceFocusID") == 1, "Space focus must persist after clicking App Shelf item")
+require(report.get("quickSwitchVisible") is False, "remote App click must close Quick Switch after committing its first window")
+require(root.get("activeSpaceFocusID") == 1, "Space hover preview should still be reflected in the pre-commit report")
 require(root.get("lastClickedAppGroupIndex") == last_index, "remote App click must be recorded")
 require(root.get("lastAppShelfClickChangedSelection") is True, "App Shelf click must move keyboard selection to the remote App column")
 require(root.get("selectedAppGroupIndex") == last_index, "remote App click must focus the remote App column")
-require(root.get("lastCommittedWindowID") is None, "App Shelf click must not commit a window")
+expected_window_id = columns[last_index].get("cards", [])[0].get("windowID")
+require(root.get("lastCommittedWindowID") == expected_window_id, "App Shelf click must commit the first remote window")
+require(report.get("lastCommittedWindowID") == expected_window_id, "session must surface the remote App commit")
+require(report.get("lastActivationWindowID") == expected_window_id, "activation window must be the first remote window")
+require(report.get("lastActivationResult") == "activated", "debug activation must activate the first remote window")
+require(report.get("lastCommitSource") == "mouse", "remote App commit source must be mouse")
 require(root.get("waterfallScrollOffset", 0) > 0, "remote App click must scroll Waterfall horizontally")
 require(items[last_index].get("spaceFocusDirection") == "visible", "remote App must become visible after click")
 require(columns[last_index].get("spaceFocusDirection") == "visible", "remote Waterfall column must become visible after click")
@@ -239,8 +252,57 @@
 print(json.dumps({
     "activeSpaceFocusID": root.get("activeSpaceFocusID"),
     "lastClickedAppGroupIndex": root.get("lastClickedAppGroupIndex"),
+    "lastCommittedWindowID": root.get("lastCommittedWindowID"),
+    "lastActivationResult": report.get("lastActivationResult"),
     "waterfallScrollOffset": root.get("waterfallScrollOffset"),
     "remoteDirectionAfterClick": items[last_index].get("spaceFocusDirection")
+}, indent=2, ensure_ascii=False))
+PY
+}
+
+assert_space_click_report() {
+  /usr/bin/python3 - "$1" <<'PY'
+import json
+import sys
+
+path = sys.argv[1]
+
+with open(path, "r", encoding="utf-8") as file:
+    report = json.load(file)
+
+def require(condition, message):
+    if not condition:
+        print(message, file=sys.stderr)
+        print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
+        sys.exit(1)
+
+root = report.get("rootView", {})
+columns = sorted(root.get("waterfallColumns", []), key=lambda column: column.get("appGroupIndex", 0))
+target_space_id = 1
+first_space_card = None
+for column in columns:
+    cards = sorted(column.get("cards", []), key=lambda card: card.get("windowIndex", 0))
+    first_space_card = next((card for card in cards if card.get("primarySpaceID") == target_space_id), None)
+    if first_space_card is not None:
+        break
+
+require(first_space_card is not None, "fixture must include at least one window in Space A1")
+expected_window_id = first_space_card.get("windowID")
+
+require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
+require(report.get("quickSwitchVisible") is False, "click-space must close Quick Switch after committing a window in that Space")
+require(root.get("lastSpaceLaneClickSpaceID") == target_space_id, "click-space must record the clicked Space")
+require(root.get("spaceLaneFocusedSpaceID") is None, "click-space must not reuse persistent focus as its behavior")
+require(root.get("lastCommittedWindowID") == expected_window_id, "click-space must commit the first window in the clicked Space")
+require(report.get("lastCommittedWindowID") == expected_window_id, "session must surface the Space click commit")
+require(report.get("lastActivationWindowID") == expected_window_id, "activation window must match the first window in clicked Space")
+require(report.get("lastActivationResult") == "activated", "debug activation must activate the clicked Space window")
+require(report.get("lastCommitSource") == "mouse", "click-space commit source must be mouse")
+
+print(json.dumps({
+    "clickedSpaceID": root.get("lastSpaceLaneClickSpaceID"),
+    "lastCommittedWindowID": root.get("lastCommittedWindowID"),
+    "lastActivationResult": report.get("lastActivationResult")
 }, indent=2, ensure_ascii=False))
 PY
 }
@@ -292,13 +354,17 @@
 "$SCRIPT_DIR/package-app.sh" >&2
 
 FOCUS_REPORT="$REPORT_DIR/round01-three-zone-linkage-focus-report.json"
+SPACE_CLICK_REPORT="$REPORT_DIR/round01-three-zone-linkage-space-click-report.json"
 REMOTE_REPORT="$REPORT_DIR/round01-three-zone-linkage-remote-report.json"
 SCROLL_REPORT="$REPORT_DIR/round01-three-zone-linkage-scroll-report.json"
 
-run_report "$FOCUS_REPORT" "click-space:1" "click-space:1"
+run_report "$FOCUS_REPORT" "hover-space:1" "hover-space:1"
 assert_focus_report "$FOCUS_REPORT"
 
-run_report "$REMOTE_REPORT" "click-space:1,click-app:$REMOTE_APP_INDEX" "click-app:$REMOTE_APP_INDEX"
+run_report "$SPACE_CLICK_REPORT" "click-space:1" "click-space:1" "$FIXTURE_APP_COUNT" "$FIXTURE_WINDOWS_PER_APP" hidden
+assert_space_click_report "$SPACE_CLICK_REPORT"
+
+run_report "$REMOTE_REPORT" "hover-space:1,click-app:$REMOTE_APP_INDEX" "click-app:$REMOTE_APP_INDEX" "$FIXTURE_APP_COUNT" "$FIXTURE_WINDOWS_PER_APP" hidden
 assert_remote_click_report "$REMOTE_REPORT"
 
 run_report "$SCROLL_REPORT" "scroll-app-shelf:99999" "scroll-app-shelf:99999" 60 1

--
Gitblit v1.9.3