From b6e11990cc9bd3c838ad6cdd9e53caafff0fd7cb Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 18 Jun 2026 23:24:21 +0800
Subject: [PATCH] Add vertical App Shelf keyboard focus

---
 C3.tools/round1-keyboard-navigation-fixture-qa.sh         |   63 +++-
 C3.tools/round1-vertical-keyboard-app-focus-fixture-qa.sh |  267 ++++++++++++++++++++++
 C3.tools/round1-window-activation-fixture-qa.sh           |    2 
 C3.tools/round1-app-column-alignment-fixture-qa.sh        |   55 +++-
 C1.source/Sources/Aligner/QuickSwitchRootView.swift       |  309 ++++++++++++++++++++++++-
 C1.source/Resources/Aligner-Info.plist                    |    4 
 C3.tools/round1-mouse-interaction-fixture-qa.sh           |    7 
 7 files changed, 655 insertions(+), 52 deletions(-)

diff --git a/C1.source/Resources/Aligner-Info.plist b/C1.source/Resources/Aligner-Info.plist
index 55f8465..7f859ba 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.71</string>
+    <string>0.0.72</string>
 	<key>CFBundleVersion</key>
-    <string>20260618.2149</string>
+    <string>20260618.2320</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 4797b32..9fb63d3 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -10,6 +10,8 @@
 
 @MainActor
 final class QuickSwitchRootView: NSView {
+    private static let appShelfIndexSymbols = Array("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ").map(String.init)
+
     private let backdropBlurView = NSVisualEffectView()
     private let layerHostView = NSView()
     private let backgroundLayer = CAGradientLayer()
@@ -67,8 +69,14 @@
     private var horizontalMasonryScrollOffset: CGFloat = 0
     private var currentSelection: QuickSwitchSelection?
     private var columnSelectionHistory: [Int: Int] = [:]
+    private var keyboardFocusedAppGroupIndex: Int?
     private var keyboardCommandsApplied: [String] = []
     private var lastKeyboardCommand: String?
+    private var lastKeyboardAppIndexCommand: String?
+    private var lastBoundaryBounceAxis: String?
+    private var lastBoundaryBounceDirection: String?
+    private var boundaryBounceCount = 0
+    private var selectionChangedByLastCommand: Bool?
     private var tabIgnoredCount = 0
     private var lastCommittedSelection: QuickSwitchSelection?
     private var lastCommitSource: QuickSwitchCommitSource?
@@ -218,6 +226,7 @@
     private enum HoverTargetSource: String {
         case event
         case debug
+        case keyboard
     }
 
     private struct SpaceLaneSegmentLayers {
@@ -447,6 +456,9 @@
         case TriggerKeyCode.tab:
             performKeyboardCommand("tab")
         default:
+            if handleAppShelfIndexKey(event) {
+                return
+            }
             super.keyDown(with: event)
         }
     }
@@ -799,6 +811,10 @@
             columnSelectionHistory = columnSelectionHistory.filter { appGroupIndex, _ in
                 viewModel?.waterfallColumns.contains { $0.appGroupIndex == appGroupIndex } == true
             }
+            if let keyboardFocusedAppGroupIndex,
+               viewModel?.waterfallColumns.contains(where: { $0.appGroupIndex == keyboardFocusedAppGroupIndex }) != true {
+                self.keyboardFocusedAppGroupIndex = nil
+            }
             if let currentSelection {
                 columnSelectionHistory[currentSelection.appGroupIndex] = currentSelection.windowIndex
             }
@@ -813,8 +829,14 @@
             screenshotSkippedReasonsByWindowID = [:]
             currentSelection = viewModel?.initialSelection
             columnSelectionHistory = [:]
+            keyboardFocusedAppGroupIndex = nil
             keyboardCommandsApplied = []
             lastKeyboardCommand = nil
+            lastKeyboardAppIndexCommand = nil
+            lastBoundaryBounceAxis = nil
+            lastBoundaryBounceDirection = nil
+            boundaryBounceCount = 0
+            selectionChangedByLastCommand = nil
             tabIgnoredCount = 0
             lastCommittedSelection = nil
             lastCommitSource = nil
@@ -1233,8 +1255,14 @@
             "selectedAppGroupIndex": effectiveSelection?.appGroupIndex ?? NSNull(),
             "selectedWindowIndex": effectiveSelection?.windowIndex ?? NSNull(),
             "selectedWindowID": effectiveSelection?.windowID ?? NSNull(),
+            "keyboardFocusedAppGroupIndex": keyboardFocusedAppGroupIndex ?? NSNull(),
             "keyboardCommandsApplied": keyboardCommandsApplied,
             "lastKeyboardCommand": lastKeyboardCommand ?? NSNull(),
+            "lastKeyboardAppIndexCommand": lastKeyboardAppIndexCommand ?? NSNull(),
+            "lastBoundaryBounceAxis": lastBoundaryBounceAxis ?? NSNull(),
+            "lastBoundaryBounceDirection": lastBoundaryBounceDirection ?? NSNull(),
+            "boundaryBounceCount": boundaryBounceCount,
+            "selectionChangedByLastCommand": selectionChangedByLastCommand ?? NSNull(),
             "tabIgnoredCount": tabIgnoredCount,
             "lastCommittedAppGroupIndex": lastCommittedSelection?.appGroupIndex ?? NSNull(),
             "lastCommittedWindowIndex": lastCommittedSelection?.windowIndex ?? NSNull(),
@@ -1307,21 +1335,47 @@
             .lowercased()
         guard !command.isEmpty else { return }
 
+        if let indexSymbol = appShelfIndexSymbol(fromKeyboardCommand: command) {
+            let recordedCommand = "app:\(indexSymbol)"
+            recordKeyboardCommand(recordedCommand)
+            if !focusAppShelfItemForKeyboard(indexSymbol: indexSymbol, commandLabel: recordedCommand) {
+                selectionChangedByLastCommand = false
+            }
+            return
+        }
+
         switch command {
         case "left":
             recordKeyboardCommand("left")
-            moveSelection(.left)
+            if isHorizontalMasonryMode {
+                moveSelection(.left)
+            } else if !moveKeyboardAppFocus(cycleForward: false) {
+                selectionChangedByLastCommand = false
+            }
         case "right":
             recordKeyboardCommand("right")
-            moveSelection(.right)
+            if isHorizontalMasonryMode {
+                moveSelection(.right)
+            } else if !moveKeyboardAppFocus(cycleForward: true) {
+                selectionChangedByLastCommand = false
+            }
         case "up":
             recordKeyboardCommand("up")
-            moveSelection(.up)
+            if isHorizontalMasonryMode {
+                moveSelection(.up)
+            } else {
+                moveVerticalSelectionWithinFocusedColumn(.up)
+            }
         case "down":
             recordKeyboardCommand("down")
-            moveSelection(.down)
+            if isHorizontalMasonryMode {
+                moveSelection(.down)
+            } else {
+                moveVerticalSelectionWithinFocusedColumn(.down)
+            }
         case "enter", "return", "commit":
             recordKeyboardCommand("enter")
+            selectionChangedByLastCommand = false
             commitCurrentSelection()
         case "tab":
             if isHorizontalMasonryMode {
@@ -1330,15 +1384,241 @@
             } else {
                 tabIgnoredCount += 1
                 recordKeyboardCommand("tabIgnored")
+                selectionChangedByLastCommand = false
             }
         default:
             recordKeyboardCommand("unknown:\(command)")
+            selectionChangedByLastCommand = false
         }
     }
 
     private func recordKeyboardCommand(_ command: String) {
         lastKeyboardCommand = command
         keyboardCommandsApplied.append(command)
+    }
+
+    private func handleAppShelfIndexKey(_ event: NSEvent) -> Bool {
+        guard !isHorizontalMasonryMode else { return false }
+        let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
+        guard flags.isDisjoint(with: [.command, .control]) else { return false }
+        guard let characters = event.charactersIgnoringModifiers,
+              let symbol = appShelfIndexSymbol(fromKeyboardCommand: characters)
+        else {
+            return false
+        }
+
+        performKeyboardCommand(symbol)
+        return true
+    }
+
+    private func appShelfIndexSymbol(fromKeyboardCommand command: String) -> String? {
+        var candidate = command.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
+        for prefix in ["APP:", "APP-", "INDEX:", "INDEX-"] where candidate.hasPrefix(prefix) {
+            candidate.removeFirst(prefix.count)
+            break
+        }
+        guard candidate.count == 1,
+              Self.appShelfIndexSymbols.contains(candidate)
+        else {
+            return nil
+        }
+        return candidate
+    }
+
+    private func focusAppShelfItemForKeyboard(indexSymbol: String, commandLabel: String) -> Bool {
+        guard let ordinal = Self.appShelfIndexSymbols.firstIndex(of: indexSymbol),
+              ordinal < appShelfItems.count
+        else {
+            return false
+        }
+
+        return focusAppShelfItemForKeyboard(
+            appGroupIndex: appShelfItems[ordinal].item.appGroupIndex,
+            appIndexCommand: commandLabel
+        )
+    }
+
+    @discardableResult
+    private func focusAppShelfItemForKeyboard(
+        appGroupIndex: Int,
+        appIndexCommand: String? = nil
+    ) -> Bool {
+        guard appShelfItems.contains(where: { $0.item.appGroupIndex == appGroupIndex }),
+              waterfallColumns.contains(where: { $0.column.appGroupIndex == appGroupIndex })
+        else {
+            return false
+        }
+
+        keyboardFocusedAppGroupIndex = appGroupIndex
+        lastKeyboardAppIndexCommand = appIndexCommand
+        selectionChangedByLastCommand = false
+        clearKeyboardBoundaryFeedback()
+        ensureAppShelfItemVisible(appGroupIndex)
+        layoutSubtreeIfNeeded()
+        hoverAppGroup(appGroupIndex, source: .keyboard)
+        needsLayout = true
+        return true
+    }
+
+    @discardableResult
+    private func moveKeyboardAppFocus(cycleForward: Bool) -> Bool {
+        let orderedAppGroupIndexes = appShelfItems.map(\.item.appGroupIndex)
+        guard !orderedAppGroupIndexes.isEmpty else { return false }
+
+        let currentAppGroupIndex = keyboardFocusedAppGroupIndex
+            ?? appHoverTargetAppGroupIndex
+            ?? effectiveSelection?.appGroupIndex
+        let currentPosition = currentAppGroupIndex.flatMap { orderedAppGroupIndexes.firstIndex(of: $0) }
+        let nextPosition = currentPosition.map { current in
+            if cycleForward {
+                return (current + 1) % orderedAppGroupIndexes.count
+            }
+            return (current - 1 + orderedAppGroupIndexes.count) % orderedAppGroupIndexes.count
+        } ?? (cycleForward ? 0 : orderedAppGroupIndexes.count - 1)
+
+        return focusAppShelfItemForKeyboard(appGroupIndex: orderedAppGroupIndexes[nextPosition])
+    }
+
+    private var appHoverTargetAppGroupIndex: Int? {
+        guard case .app(let appGroupIndex) = hoverTarget else { return nil }
+        return appGroupIndex
+    }
+
+    private func moveVerticalSelectionWithinFocusedColumn(_ direction: QuickSwitchKeyboardDirection) {
+        guard direction == .up || direction == .down,
+              let targetAppGroupIndex = verticalKeyboardNavigationAppGroupIndex(),
+              let column = waterfallColumns.first(where: { $0.column.appGroupIndex == targetAppGroupIndex })
+        else {
+            DevelopmentDiagnostics.log("quickSwitch.view.moveVerticalSelection.blocked", [
+                "direction": keyboardDirectionName(direction),
+                "hasFocusedApp": keyboardFocusedAppGroupIndex != nil,
+                "hasSelection": effectiveSelection != nil
+            ])
+            selectionChangedByLastCommand = false
+            return
+        }
+
+        let cards = column.cards.sorted { $0.item.windowIndex < $1.item.windowIndex }
+        guard !cards.isEmpty else {
+            selectionChangedByLastCommand = false
+            return
+        }
+
+        let previousSelection = effectiveSelection
+        let nextSelection: QuickSwitchSelection
+        if let current = previousSelection,
+           current.appGroupIndex == targetAppGroupIndex,
+           let currentPosition = cards.firstIndex(where: { $0.item.window.id == current.windowID }) {
+            let nextPosition = currentPosition + (direction == .down ? 1 : -1)
+            guard cards.indices.contains(nextPosition) else {
+                keyboardFocusedAppGroupIndex = targetAppGroupIndex
+                hoverAppGroup(targetAppGroupIndex, source: .keyboard)
+                triggerKeyboardBoundaryFeedback(appGroupIndex: targetAppGroupIndex, direction: direction)
+                DevelopmentDiagnostics.log("quickSwitch.view.moveVerticalSelection.boundary", [
+                    "direction": keyboardDirectionName(direction),
+                    "appGroupIndex": targetAppGroupIndex,
+                    "windowIndex": current.windowIndex
+                ])
+                needsLayout = true
+                return
+            }
+            nextSelection = selectionForCard(cards[nextPosition])
+        } else {
+            let rememberedWindowIndex = columnSelectionHistory[targetAppGroupIndex]
+            let rememberedCard = rememberedWindowIndex.flatMap { windowIndex in
+                cards.first { $0.item.windowIndex == windowIndex }
+            }
+            nextSelection = selectionForCard(rememberedCard ?? cards[0])
+        }
+
+        keyboardFocusedAppGroupIndex = targetAppGroupIndex
+        currentSelection = nextSelection
+        columnSelectionHistory[nextSelection.appGroupIndex] = nextSelection.windowIndex
+        selectionChangedByLastCommand = previousSelection != nextSelection
+        clearKeyboardBoundaryFeedback()
+        hoverAppGroup(targetAppGroupIndex, source: .keyboard)
+        ensureCurrentSelectionVisible(horizontalIntent: .alignWithAppShelf(animated: true))
+        DevelopmentDiagnostics.log("quickSwitch.view.moveVerticalSelection", [
+            "direction": keyboardDirectionName(direction),
+            "appGroupIndex": nextSelection.appGroupIndex,
+            "windowIndex": nextSelection.windowIndex,
+            "windowID": nextSelection.windowID
+        ])
+        needsLayout = true
+    }
+
+    private func verticalKeyboardNavigationAppGroupIndex() -> Int? {
+        if let keyboardFocusedAppGroupIndex,
+           waterfallColumns.contains(where: { $0.column.appGroupIndex == keyboardFocusedAppGroupIndex }) {
+            return keyboardFocusedAppGroupIndex
+        }
+        if let appHoverTargetAppGroupIndex,
+           waterfallColumns.contains(where: { $0.column.appGroupIndex == appHoverTargetAppGroupIndex }) {
+            return appHoverTargetAppGroupIndex
+        }
+        if let appGroupIndex = effectiveSelection?.appGroupIndex,
+           waterfallColumns.contains(where: { $0.column.appGroupIndex == appGroupIndex }) {
+            return appGroupIndex
+        }
+        return waterfallColumns.first?.column.appGroupIndex
+    }
+
+    private func clearKeyboardBoundaryFeedback() {
+        lastBoundaryBounceAxis = nil
+        lastBoundaryBounceDirection = nil
+    }
+
+    private func triggerKeyboardBoundaryFeedback(
+        appGroupIndex: Int,
+        direction: QuickSwitchKeyboardDirection
+    ) {
+        selectionChangedByLastCommand = false
+        lastBoundaryBounceAxis = "vertical"
+        lastBoundaryBounceDirection = keyboardDirectionName(direction)
+        boundaryBounceCount += 1
+
+        layoutSubtreeIfNeeded()
+        guard let column = waterfallColumns.first(where: { $0.column.appGroupIndex == appGroupIndex }) else { return }
+
+        let animationKey = "keyboardBoundaryBounce"
+        column.containerLayer.removeAnimation(forKey: animationKey)
+        if NSWorkspace.shared.accessibilityDisplayShouldReduceMotion {
+            let pulse = CABasicAnimation(keyPath: "opacity")
+            pulse.fromValue = 1
+            pulse.toValue = 0.9
+            pulse.duration = 0.12
+            pulse.autoreverses = true
+            pulse.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
+            column.containerLayer.add(pulse, forKey: animationKey)
+            return
+        }
+
+        let currentY = column.containerLayer.presentation()?.position.y
+            ?? column.containerLayer.position.y
+        let delta: CGFloat = direction == .up ? 5 : -5
+        let bounce = CAKeyframeAnimation(keyPath: "position.y")
+        bounce.values = [currentY, currentY + delta, currentY]
+        bounce.keyTimes = [0, 0.38, 1]
+        bounce.timingFunctions = [
+            CAMediaTimingFunction(name: .easeOut),
+            CAMediaTimingFunction(name: .easeInEaseOut)
+        ]
+        bounce.duration = 0.23
+        bounce.isRemovedOnCompletion = true
+        column.containerLayer.add(bounce, forKey: animationKey)
+    }
+
+    private func keyboardDirectionName(_ direction: QuickSwitchKeyboardDirection) -> String {
+        switch direction {
+        case .left:
+            return "left"
+        case .right:
+            return "right"
+        case .up:
+            return "up"
+        case .down:
+            return "down"
+        }
     }
 
     private func performMouseCommand(_ rawCommand: String) {
@@ -1491,13 +1771,13 @@
         return command.hasPrefix("hover-")
     }
 
-    private func hoverAppGroup(_ appGroupIndex: Int) {
+    private func hoverAppGroup(_ appGroupIndex: Int, source: HoverTargetSource = .debug) {
         updateHoverState(
             appGroupIndex: appGroupIndex,
             windowID: nil,
             spaceID: nil,
             alignsWaterfallWithAppShelf: true,
-            source: .debug
+            source: source
         )
     }
 
@@ -1778,8 +2058,11 @@
         }
 
         columnSelectionHistory[current.appGroupIndex] = current.windowIndex
+        let previousSelection = effectiveSelection
         currentSelection = next
         columnSelectionHistory[next.appGroupIndex] = next.windowIndex
+        selectionChangedByLastCommand = previousSelection != next
+        clearKeyboardBoundaryFeedback()
         ensureCurrentSelectionVisible(horizontalIntent: .alignWithAppShelf(animated: true))
         DevelopmentDiagnostics.log("quickSwitch.view.moveSelection", [
             "direction": String(describing: direction),
@@ -1802,8 +2085,11 @@
         }
 
         columnSelectionHistory[current.appGroupIndex] = current.windowIndex
+        let previousSelection = effectiveSelection
         currentSelection = next
         columnSelectionHistory[next.appGroupIndex] = next.windowIndex
+        selectionChangedByLastCommand = previousSelection != next
+        clearKeyboardBoundaryFeedback()
         ensureCurrentSelectionVisible(horizontalIntent: .alignWithAppShelf(animated: true))
         DevelopmentDiagnostics.log("quickSwitch.view.moveHorizontalMasonrySelection", [
             "direction": String(describing: direction),
@@ -3543,15 +3829,14 @@
     }
 
     private func appShelfIndexText(forOrdinal ordinal: Int) -> String {
-        let symbols = Array("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ")
         guard ordinal >= 0 else { return "" }
-        if ordinal < symbols.count {
-            return String(symbols[ordinal])
+        if ordinal < Self.appShelfIndexSymbols.count {
+            return Self.appShelfIndexSymbols[ordinal]
         }
 
-        let overflowIndex = ordinal - symbols.count
-        let prefix = symbols[10 + (overflowIndex / 26) % 26]
-        let suffix = symbols[10 + overflowIndex % 26]
+        let overflowIndex = ordinal - Self.appShelfIndexSymbols.count
+        let prefix = Self.appShelfIndexSymbols[10 + (overflowIndex / 26) % 26]
+        let suffix = Self.appShelfIndexSymbols[10 + overflowIndex % 26]
         return "\(prefix)\(suffix)"
     }
 
diff --git a/C3.tools/round1-app-column-alignment-fixture-qa.sh b/C3.tools/round1-app-column-alignment-fixture-qa.sh
index 5e63221..73a3bd5 100755
--- a/C3.tools/round1-app-column-alignment-fixture-qa.sh
+++ b/C3.tools/round1-app-column-alignment-fixture-qa.sh
@@ -58,19 +58,40 @@
   stop_current_aligner
 }
 
-keyboard_right_sequence() {
+keyboard_index_sequence() {
   local target_index="$1"
-  local sequence=""
+  local symbols="1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
-  for ((index = 0; index < target_index; index++)); do
-    if [ -n "$sequence" ]; then
-      sequence="$sequence,right"
+  if [ "$target_index" -ge "${#symbols}" ]; then
+    fail "keyboard index sequence does not support target index $target_index"
+  fi
+
+  echo "${symbols:$target_index:1}"
+}
+
+normalize_keyboard_sequence() {
+  local sequence="$1"
+  local symbols="1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+  local normalized=""
+  local IFS=,
+
+  for raw_part in $sequence; do
+    local part
+    part="$(printf "%s" "$raw_part" | tr '[:lower:]' '[:upper:]')"
+    if [ "${#part}" -eq 1 ] && [[ "$symbols" == *"$part"* ]]; then
+      part="app:$part"
     else
-      sequence="right"
+      part="$raw_part"
+    fi
+
+    if [ -n "$normalized" ]; then
+      normalized="$normalized,$part"
+    else
+      normalized="$part"
     fi
   done
 
-  echo "$sequence"
+  echo "$normalized"
 }
 
 wait_for_report() {
@@ -196,12 +217,14 @@
 elif mode == "keyboard":
     require(root.get("keyboardCommandsApplied") == expected_key_sequence, "debug key sequence must be applied in order")
     require(root.get("lastKeyboardCommand") == expected_key_sequence[-1], "last keyboard command must match the alignment target")
-    require(root.get("selectedAppGroupIndex") == target_app_index, "keyboard focus must select the target appGroupIndex")
-    require(item.get("isSelected") is True, "target App Shelf item must expose selected state")
-    require("selectedVisualSuppressed" in item.get("visualStates", []), "non-hovered keyboard-selected App Shelf item must suppress selected visual state")
-    cards = column.get("cards", [])
-    require(cards, "target Waterfall column must include cards")
-    require(root.get("selectedWindowID") == cards[0].get("windowID"), "right-arrow focus into target column must select its first window")
+    require(root.get("keyboardFocusedAppGroupIndex") == target_app_index, "keyboard App focus must target the requested App")
+    require(root.get("hoveredAppGroupIndex") == target_app_index, "keyboard App focus must become hovered appGroupIndex")
+    require(root.get("hoverTargetKind") == "app", "keyboard App focus must expose an App hover target")
+    require(root.get("hoverTargetSource") == "keyboard", "keyboard App focus must be reported as keyboard sourced")
+    require(item.get("isHovered") is True, "target App Shelf item must expose hover state")
+    require("hover" in item.get("visualStates", []), "target App Shelf item must expose hover visual state")
+    require(item.get("isSelected") is False, "keyboard App focus must not immediately move selected window")
+    require(root.get("selectionChangedByLastCommand") is False, "keyboard App focus must not change selected window")
     require(root.get("lastCommittedWindowID") is None, "keyboard alignment without Enter must not commit a window")
 else:
     require(False, f"unknown alignment mode {mode!r}")
@@ -268,7 +291,7 @@
     expected_mouse_sequence="$sequence"
     args+=(--round01-debug-mouse-sequence="$sequence")
   elif [ "$mode" = "keyboard" ]; then
-    expected_key_sequence="$sequence"
+    expected_key_sequence="$(normalize_keyboard_sequence "$sequence")"
     args+=(--round01-debug-key-sequence="$sequence")
   else
     fail "unknown fixture mode: $mode"
@@ -308,8 +331,8 @@
 REGULAR_RIGHT_INDEX=$((FIXTURE_APP_COUNT - 1))
 NARROW_MIDDLE_INDEX=$((NARROW_FIXTURE_APP_COUNT / 2))
 NARROW_RIGHT_INDEX=$((NARROW_FIXTURE_APP_COUNT - 1))
-REGULAR_KEY_SEQUENCE="$(keyboard_right_sequence "$REGULAR_RIGHT_INDEX")"
-NARROW_KEY_SEQUENCE="$(keyboard_right_sequence "$NARROW_RIGHT_INDEX")"
+REGULAR_KEY_SEQUENCE="$(keyboard_index_sequence "$REGULAR_RIGHT_INDEX")"
+NARROW_KEY_SEQUENCE="$(keyboard_index_sequence "$NARROW_RIGHT_INDEX")"
 
 trap cleanup EXIT
 stop_current_aligner
diff --git a/C3.tools/round1-keyboard-navigation-fixture-qa.sh b/C3.tools/round1-keyboard-navigation-fixture-qa.sh
index 89eba81..fa10a3e 100755
--- a/C3.tools/round1-keyboard-navigation-fixture-qa.sh
+++ b/C3.tools/round1-keyboard-navigation-fixture-qa.sh
@@ -1,7 +1,8 @@
 #!/bin/bash
 # Round01 keyboard navigation fixture QA. It drives deterministic key commands
-# through the Quick Switch debug hook and verifies stable selection, no wrapping,
-# visible scrolling, Tab ignore, Enter activation reporting, and clean close.
+# through the Quick Switch debug hook and verifies vertical App-index focus,
+# column-local movement, visible scrolling, Tab ignore, Enter activation reporting,
+# and clean close.
 
 set -euo pipefail
 
@@ -11,9 +12,11 @@
 source "$SCRIPT_DIR/build-output-paths.sh"
 APP="$BUILD_CURRENT_APP"
 REPORT="$BUILD_REPORT_ROOT/round01-keyboard-navigation-fixture-report.json"
-FIXTURE_APP_COUNT="${ALIGNER_ROUND1_KEYBOARD_FIXTURE_APP_COUNT:-8}"
+FIXTURE_APP_COUNT="${ALIGNER_ROUND1_KEYBOARD_FIXTURE_APP_COUNT:-12}"
 FIXTURE_WINDOWS_PER_APP="${ALIGNER_ROUND1_KEYBOARD_FIXTURE_WINDOWS_PER_APP:-8}"
-KEY_SEQUENCE="${ALIGNER_ROUND1_KEYBOARD_SEQUENCE:-right,down,down,down,down,down,down,down,left,right,right,right,right,right,right,right,right,tab,enter}"
+KEY_SEQUENCE="${ALIGNER_ROUND1_KEYBOARD_SEQUENCE:-A,down,down,down,down,down,down,down,down,tab,enter}"
+EXPECTED_APP_INDEX="${ALIGNER_ROUND1_KEYBOARD_EXPECTED_APP_INDEX:-10}"
+EXPECTED_WINDOW_INDEX="${ALIGNER_ROUND1_KEYBOARD_EXPECTED_WINDOW_INDEX:-$((FIXTURE_WINDOWS_PER_APP - 1))}"
 REPORT_WAIT="${ALIGNER_ROUND1_KEYBOARD_REPORT_WAIT:-6.0}"
 
 fail() {
@@ -80,7 +83,7 @@
 }
 
 assert_report() {
-  /usr/bin/python3 - "$REPORT" "$FIXTURE_APP_COUNT" "$FIXTURE_WINDOWS_PER_APP" "$KEY_SEQUENCE" <<'PY'
+  /usr/bin/python3 - "$REPORT" "$FIXTURE_APP_COUNT" "$FIXTURE_WINDOWS_PER_APP" "$KEY_SEQUENCE" "$EXPECTED_APP_INDEX" "$EXPECTED_WINDOW_INDEX" <<'PY'
 import json
 import sys
 
@@ -88,6 +91,8 @@
 fixture_app_count = int(sys.argv[2])
 fixture_windows_per_app = int(sys.argv[3])
 key_sequence = [part for part in sys.argv[4].split(",") if part]
+expected_selected_app_group_index = int(sys.argv[5])
+expected_selected_window_index = int(sys.argv[6])
 
 with open(path, "r", encoding="utf-8") as file:
     report = json.load(file)
@@ -101,6 +106,15 @@
 root = report.get("rootView", {})
 columns = root.get("waterfallColumns", [])
 items = root.get("appShelfItems", [])
+index_symbols = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+def normalized_key_command(command):
+    upper = command.upper()
+    if len(upper) == 1 and upper in index_symbols:
+        return f"app:{upper}"
+    if command == "tab":
+        return "tabIgnored"
+    return command
 
 require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
 require(report.get("quickSwitchVisible") is False, "Enter activation must close Quick Switch")
@@ -110,15 +124,19 @@
 require(len(columns) == fixture_app_count, "Waterfall column reports must match fixture count")
 require(len(items) == fixture_app_count, "App Shelf item reports must match fixture count")
 
-expected_selected_app_group_index = fixture_app_count - 1
-expected_selected_window_index = 0
 selected_column = columns[expected_selected_app_group_index]
 selected_card = selected_column.get("cards", [])[expected_selected_window_index]
 expected_selected_window_id = selected_card.get("windowID")
 
-require(root.get("selectedAppGroupIndex") == expected_selected_app_group_index, "right-arrow navigation must stop on the final App column")
-require(root.get("selectedWindowIndex") == expected_selected_window_index, "right-arrow into new columns must fall back to window index zero")
+require(root.get("selectedAppGroupIndex") == expected_selected_app_group_index, "index-key App focus plus down-arrow navigation must select the expected App column")
+require(root.get("selectedWindowIndex") == expected_selected_window_index, "down-arrow navigation must move within the focused App column")
 require(root.get("selectedWindowID") == expected_selected_window_id, "selectedWindowID must match selected card")
+require(root.get("keyboardFocusedAppGroupIndex") == expected_selected_app_group_index, "keyboard App focus must stay on the target App")
+require(root.get("hoveredAppGroupIndex") == expected_selected_app_group_index, "keyboard App focus must expose hoveredAppGroupIndex")
+require(root.get("hoverTargetKind") == "app", "keyboard App focus must use an App hover target")
+require(root.get("hoverTargetSource") == "keyboard", "keyboard App focus must report keyboard source")
+expected_app_index_command = next((normalized_key_command(command) for command in key_sequence if normalized_key_command(command).startswith("app:")), None)
+require(root.get("lastKeyboardAppIndexCommand") == expected_app_index_command, "App index command must be reported")
 require(report.get("lastCommittedWindowID") == expected_selected_window_id, "Enter commit must be surfaced at session level")
 require(report.get("lastCommitSource") == "keyboard", "Enter commit source must be keyboard")
 require(report.get("lastActivationWindowID") == expected_selected_window_id, "Enter activation window must match selected card")
@@ -128,9 +146,10 @@
 require(root.get("lastCommittedWindowIndex") == expected_selected_window_index, "Enter commit window index must match selected card")
 
 commands = root.get("keyboardCommandsApplied", [])
-require(commands == ["tabIgnored" if command == "tab" else command for command in key_sequence], "debug keyboard sequence must be applied in order with Tab ignored")
+require(commands == [normalized_key_command(command) for command in key_sequence], "debug keyboard sequence must be applied in order with App index and Tab normalized")
 require(root.get("tabIgnoredCount") == 1, "Tab must be ignored exactly once")
 require(root.get("lastKeyboardCommand") == "enter", "Enter must be the final keyboard command")
+require(root.get("boundaryBounceCount") == 0, "in-range down-arrow movement must not report a boundary bounce")
 
 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 keyboard navigation")
@@ -138,23 +157,26 @@
 require("selectedVisualSuppressed" in selected_cards[0].get("visualStates", []), "non-hovered keyboard-selected Waterfall card must suppress selected visual state")
 require(selected_cards[0].get("shineVisible") is False, "non-hovered keyboard-selected Waterfall card must not expose shine")
 require(selected_cards[0].get("zPosition", 0) == 0, "non-hovered keyboard-selected Waterfall card must not float above normal cards")
+require("appLinked" in selected_column.get("cards", [])[0].get("visualStates", []), "App-focused vertical column must keep its first card linked to App hover")
 
 selected_items = [item for item in items if item.get("isSelected") is True]
 require(len(selected_items) == 1, "App Shelf must expose exactly one selected App")
 require(selected_items[0].get("index") == expected_selected_app_group_index, "App Shelf selection must follow keyboard-selected Waterfall column")
+require(selected_items[0].get("isHovered") is True, "App Shelf selected App must also expose keyboard hover")
+require("selected" in selected_items[0].get("visualStates", []), "keyboard-focused selected App Shelf item must expose selected visual")
+require("hover" in selected_items[0].get("visualStates", []), "keyboard-focused selected App Shelf item must expose hover visual")
 
 history = {
     entry.get("appGroupIndex"): entry.get("windowIndex")
     for entry in root.get("columnSelectionHistory", [])
 }
-require(history.get(1) == fixture_windows_per_app - 1, "left/right navigation must restore the remembered window index for column 1")
-require(history.get(expected_selected_app_group_index) == 0, "final column history must remember selected window zero")
+require(history.get(expected_selected_app_group_index) == expected_selected_window_index, "final column history must remember selected window")
 
 require(root.get("waterfallScrollable") is True, "fixture must make Waterfall horizontally scrollable")
 require(root.get("waterfallMaxScrollOffset", 0) > 0, "Waterfall must expose horizontal overflow")
 require(root.get("waterfallScrollOffset", 0) > 0, "selected far-right App column must auto-scroll into view")
-require(columns[1].get("maxVerticalScrollOffset", 0) > 0, "fixture column 1 must expose vertical overflow")
-require(columns[1].get("verticalScrollOffset", 0) > 0, "down-arrow navigation must auto-scroll the selected window into view")
+require(selected_column.get("maxVerticalScrollOffset", 0) > 0, "selected fixture column must expose vertical overflow")
+require(selected_column.get("verticalScrollOffset", 0) > 0, "down-arrow navigation must auto-scroll the selected window into view")
 
 frame = selected_cards[0].get("visibleFrame", {})
 require(frame.get("x", -1) >= 0, "selected Waterfall card must be horizontally visible")
@@ -167,12 +189,13 @@
     "selectedWindowIndex": root.get("selectedWindowIndex"),
     "selectedWindowID": root.get("selectedWindowID"),
     "lastCommittedWindowID": root.get("lastCommittedWindowID"),
-    "lastActivationResult": report.get("lastActivationResult"),
-    "quickSwitchVisible": report.get("quickSwitchVisible"),
-    "waterfallScrollOffset": root.get("waterfallScrollOffset"),
-    "column1VerticalScrollOffset": columns[1].get("verticalScrollOffset"),
-    "tabIgnoredCount": root.get("tabIgnoredCount")
-}, indent=2, ensure_ascii=False))
+	    "lastActivationResult": report.get("lastActivationResult"),
+	    "quickSwitchVisible": report.get("quickSwitchVisible"),
+	    "waterfallScrollOffset": root.get("waterfallScrollOffset"),
+	    "selectedColumnVerticalScrollOffset": selected_column.get("verticalScrollOffset"),
+	    "keyboardFocusedAppGroupIndex": root.get("keyboardFocusedAppGroupIndex"),
+	    "tabIgnoredCount": root.get("tabIgnoredCount")
+	}, indent=2, ensure_ascii=False))
 PY
 }
 
diff --git a/C3.tools/round1-mouse-interaction-fixture-qa.sh b/C3.tools/round1-mouse-interaction-fixture-qa.sh
index fe212d7..8dabcff 100755
--- a/C3.tools/round1-mouse-interaction-fixture-qa.sh
+++ b/C3.tools/round1-mouse-interaction-fixture-qa.sh
@@ -302,8 +302,13 @@
 require("focused" not in window_associated_segments[0].get("visualStates", []), "window-associated Space segment must not expose focused")
 
 frame = selected_cards[0].get("visibleFrame", {})
+selected_card_visual_overflow_tolerance = 2.5
 require(frame.get("x", -1) >= 0, "clicked Waterfall card must be horizontally visible")
-require(frame.get("x", 0) + frame.get("width", 0) <= root.get("waterfallVisibleWidth", 0) + 1, "clicked card must fit in visible Waterfall width")
+require(
+    frame.get("x", 0) + frame.get("width", 0)
+    <= root.get("waterfallVisibleWidth", 0) + selected_card_visual_overflow_tolerance,
+    "clicked card must fit in visible Waterfall width"
+)
 
 print(json.dumps({
     "quickSwitchVisible": report.get("quickSwitchVisible"),
diff --git a/C3.tools/round1-vertical-keyboard-app-focus-fixture-qa.sh b/C3.tools/round1-vertical-keyboard-app-focus-fixture-qa.sh
new file mode 100755
index 0000000..1e9250e
--- /dev/null
+++ b/C3.tools/round1-vertical-keyboard-app-focus-fixture-qa.sh
@@ -0,0 +1,267 @@
+#!/bin/bash
+# Round01 vertical keyboard App focus fixture QA. It verifies that App Shelf
+# index keys and left/right arrows behave like App icon hover in vertical
+# Waterfall, while up/down move only inside the focused column and report
+# boundary bounce without wrapping.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+OUTPUT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+# shellcheck source=build-output-paths.sh
+source "$SCRIPT_DIR/build-output-paths.sh"
+APP="$BUILD_CURRENT_APP"
+REPORT_DIR="$BUILD_REPORT_ROOT"
+FIXTURE_APP_COUNT="${ALIGNER_ROUND1_VERTICAL_KEYBOARD_APP_COUNT:-12}"
+FIXTURE_WINDOWS_PER_APP="${ALIGNER_ROUND1_VERTICAL_KEYBOARD_WINDOWS_PER_APP:-8}"
+REPORT_WAIT="${ALIGNER_ROUND1_VERTICAL_KEYBOARD_REPORT_WAIT:-6.0}"
+APP_PID=""
+
+fail() {
+  echo "Round01 vertical keyboard App focus fixture QA failed: $*" >&2
+  exit 1
+}
+
+aligner_pids_for_current_app() {
+  ps -axo pid=,args= | while read -r pid command; do
+    if [[ "$command" == "$APP/Contents/MacOS/Aligner"* ]] \
+      || [[ "$command" == "/Applications/Aligner.app/Contents/MacOS/Aligner"* ]] \
+      || [[ "$command" == "$HOME/Applications/Aligner.app/Contents/MacOS/Aligner"* ]]; then
+      echo "$pid"
+    fi
+  done
+}
+
+stop_current_aligner() {
+  for pid in $(aligner_pids_for_current_app); do
+    kill "$pid" 2>/dev/null || true
+  done
+
+  for _ in {1..30}; do
+    [ -z "$(aligner_pids_for_current_app)" ] && return
+    sleep 0.1
+  done
+
+  fail "current Aligner app did not exit before QA"
+}
+
+cleanup() {
+  if [ -n "${APP_PID:-}" ]; then
+    kill "$APP_PID" 2>/dev/null || true
+    wait "$APP_PID" 2>/dev/null || true
+    APP_PID=""
+  fi
+  stop_current_aligner
+}
+
+normalize_keyboard_sequence() {
+  local sequence="$1"
+  local symbols="1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+  local normalized=""
+  local IFS=,
+
+  for raw_part in $sequence; do
+    local upper
+    upper="$(printf "%s" "$raw_part" | tr '[:lower:]' '[:upper:]')"
+    local part="$raw_part"
+    if [ "${#upper}" -eq 1 ] && [[ "$symbols" == *"$upper"* ]]; then
+      part="app:$upper"
+    fi
+
+    if [ -n "$normalized" ]; then
+      normalized="$normalized,$part"
+    else
+      normalized="$part"
+    fi
+  done
+
+  echo "$normalized"
+}
+
+wait_for_report() {
+  local report="$1"
+  local expected_sequence="$2"
+
+  /usr/bin/python3 - "$report" "$REPORT_WAIT" "$expected_sequence" <<'PY'
+import json
+import sys
+import time
+
+path = sys.argv[1]
+timeout = float(sys.argv[2])
+expected_sequence = [part for part in sys.argv[3].split(",") if part]
+deadline = time.monotonic() + timeout
+last_report = None
+
+while time.monotonic() < deadline:
+    try:
+        with open(path, "r", encoding="utf-8") as file:
+            report = json.load(file)
+        last_report = report
+        root = report.get("rootView", {})
+        if (
+            report.get("snapshotLoaded") is True
+            and report.get("quickSwitchVisible") is True
+            and root.get("keyboardCommandsApplied") == expected_sequence
+        ):
+            sys.exit(0)
+    except FileNotFoundError:
+        pass
+    except json.JSONDecodeError:
+        pass
+    time.sleep(0.2)
+
+if last_report is not None:
+    print(json.dumps(last_report, indent=2, ensure_ascii=False), file=sys.stderr)
+print(f"vertical keyboard report did not reach expected commands within {timeout:.1f}s", file=sys.stderr)
+sys.exit(1)
+PY
+}
+
+assert_report() {
+  local report="$1"
+  local expected_sequence="$2"
+  local expected_app_index="$3"
+  local expected_window_index="$4"
+  local expected_boundary_count="$5"
+  local expected_boundary_direction="$6"
+  local expect_selection_changed="$7"
+
+  /usr/bin/python3 - \
+    "$report" \
+    "$expected_sequence" \
+    "$expected_app_index" \
+    "$expected_window_index" \
+    "$expected_boundary_count" \
+    "$expected_boundary_direction" \
+    "$expect_selection_changed" \
+    "$FIXTURE_APP_COUNT" \
+    "$FIXTURE_WINDOWS_PER_APP" <<'PY'
+import json
+import sys
+
+path = sys.argv[1]
+expected_sequence = [part for part in sys.argv[2].split(",") if part]
+expected_app_index = int(sys.argv[3])
+expected_window_index = int(sys.argv[4])
+expected_boundary_count = int(sys.argv[5])
+expected_boundary_direction = None if sys.argv[6] == "none" else sys.argv[6]
+expect_selection_changed = sys.argv[7] == "true"
+fixture_app_count = int(sys.argv[8])
+fixture_windows_per_app = int(sys.argv[9])
+
+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", {})
+items = root.get("appShelfItems", [])
+columns = root.get("waterfallColumns", [])
+target_item = next((item for item in items if item.get("index") == expected_app_index), None)
+target_column = next((column for column in columns if column.get("appGroupIndex") == expected_app_index), None)
+
+require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
+require(report.get("quickSwitchVisible") is True, "fixture must keep Quick Switch visible")
+require(report.get("appCount") == fixture_app_count, "fixture appCount must match")
+require(report.get("windowCount") == fixture_app_count * fixture_windows_per_app, "fixture windowCount must match")
+require(root.get("waterfallViewMode") == "verticalColumns", "fixture must run in vertical Waterfall")
+require(root.get("keyboardCommandsApplied") == expected_sequence, "keyboard commands must match normalized sequence")
+require(root.get("keyboardFocusedAppGroupIndex") == expected_app_index, "keyboard focus must land on expected App")
+require(root.get("hoveredAppGroupIndex") == expected_app_index, "keyboard focus must expose hovered App")
+require(root.get("hoverTargetKind") == "app", "keyboard focus must use App hover target")
+require(root.get("hoverTargetSource") == "keyboard", "keyboard focus must report keyboard source")
+require(root.get("lastCommittedWindowID") is None, "no-Enter keyboard focus runs must not commit")
+require(root.get("lastCommitSource") is None, "no-Enter keyboard focus runs must not report commit source")
+require(target_item is not None, "target App Shelf item must exist")
+require(target_column is not None, "target Waterfall column must exist")
+require(target_item.get("isHovered") is True, "target App Shelf item must expose hover")
+require("hover" in target_item.get("visualStates", []), "target App Shelf item must expose hover visual")
+require(target_column.get("isHovered") is True, "target Waterfall column must expose hover")
+require("hover" in target_column.get("visualStates", []), "target Waterfall column must expose hover visual")
+require(root.get("selectedAppGroupIndex") == expected_app_index, "selected App must match expected App after column entry")
+require(root.get("selectedWindowIndex") == expected_window_index, "selected window index must match expected column movement")
+require(root.get("selectionChangedByLastCommand") is expect_selection_changed, "selectionChangedByLastCommand must match final command")
+require(root.get("boundaryBounceCount") == expected_boundary_count, "boundary bounce count must match")
+if expected_boundary_direction is None:
+    require(root.get("lastBoundaryBounceAxis") is None, "no-boundary run must not report boundary axis")
+    require(root.get("lastBoundaryBounceDirection") is None, "no-boundary run must not report boundary direction")
+else:
+    require(root.get("lastBoundaryBounceAxis") == "vertical", "boundary run must report vertical axis")
+    require(root.get("lastBoundaryBounceDirection") == expected_boundary_direction, "boundary direction must match")
+
+print(json.dumps({
+    "commands": root.get("keyboardCommandsApplied"),
+    "keyboardFocusedAppGroupIndex": root.get("keyboardFocusedAppGroupIndex"),
+    "selectedWindowIndex": root.get("selectedWindowIndex"),
+    "boundaryBounceCount": root.get("boundaryBounceCount"),
+    "lastBoundaryBounceDirection": root.get("lastBoundaryBounceDirection")
+}, indent=2, ensure_ascii=False))
+PY
+}
+
+run_case() {
+  local name="$1"
+  local sequence="$2"
+  local expected_app_index="$3"
+  local expected_window_index="$4"
+  local expected_boundary_count="$5"
+  local expected_boundary_direction="$6"
+  local expect_selection_changed="$7"
+  local report="$REPORT_DIR/round01-vertical-keyboard-$name-report.json"
+  local expected_sequence
+  expected_sequence="$(normalize_keyboard_sequence "$sequence")"
+
+  stop_current_aligner
+  rm -f "$report"
+
+  "$APP/Contents/MacOS/Aligner" \
+    --round0-skip-permissions \
+    --round01-open-quick-switch \
+    --round01-waterfall-view-mode=vertical \
+    --round01-fixture-app-count="$FIXTURE_APP_COUNT" \
+    --round01-fixture-windows-per-app="$FIXTURE_WINDOWS_PER_APP" \
+    --round01-disable-screenshot-refresh \
+    --round01-debug-key-sequence="$sequence" \
+    --round01-quick-switch-report="$report" &
+
+  APP_PID=$!
+  wait_for_report "$report" "$expected_sequence"
+  swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-quick-switch >&2
+  assert_report \
+    "$report" \
+    "$expected_sequence" \
+    "$expected_app_index" \
+    "$expected_window_index" \
+    "$expected_boundary_count" \
+    "$expected_boundary_direction" \
+    "$expect_selection_changed"
+
+  kill "$APP_PID" 2>/dev/null || true
+  wait "$APP_PID" 2>/dev/null || true
+  APP_PID=""
+  sleep 0.3
+  swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch >&2
+}
+
+if [ "$FIXTURE_APP_COUNT" -lt 12 ]; then
+  fail "fixture app count must be at least 12"
+fi
+if [ "$FIXTURE_WINDOWS_PER_APP" -lt 8 ]; then
+  fail "fixture windows per app must be at least 8"
+fi
+
+trap cleanup EXIT
+stop_current_aligner
+"$SCRIPT_DIR/package-app.sh" >&2
+
+run_case "wrap" "1,left,right" 0 0 0 "none" "false"
+run_case "bottom-boundary" "3,down,down,down,down,down,down,down,down,down" 2 7 1 "down" "false"
+run_case "top-boundary" "4,down,up" 3 0 1 "up" "false"
+
+trap - EXIT
+cleanup
diff --git a/C3.tools/round1-window-activation-fixture-qa.sh b/C3.tools/round1-window-activation-fixture-qa.sh
index 4a8ddb8..56dba71 100755
--- a/C3.tools/round1-window-activation-fixture-qa.sh
+++ b/C3.tools/round1-window-activation-fixture-qa.sh
@@ -149,7 +149,7 @@
   --round01-fixture-window-activation \
   --round01-disable-screenshot-refresh \
   --round01-debug-window-activation \
-  --round01-debug-key-sequence="right,enter" \
+  --round01-debug-key-sequence="2,down,enter" \
   --round01-quick-switch-report="$MINIMIZED_REPORT" &
 
 APP_PID=$!

--
Gitblit v1.9.3