Ariver
2026-06-20 cba722f8a207982ade9e941eca3e6070e8705749
Commit single-window app shortcuts on first key
2 files modified
185 ■■■■■ changed files
C1.source/Sources/Aligner/QuickSwitchRootView.swift 25 ●●●● patch | view | raw | blame | history
C3.tools/round1-window-index-progressive-filter-fixture-qa.sh 160 ●●●●● patch | view | raw | blame | history
C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -1790,11 +1790,21 @@
        if focusAppShelfItemForKeyboard(indexSymbol: symbol, commandLabel: recordedCommand),
           let ordinal = Self.appShelfIndexSymbols.firstIndex(of: symbol),
           ordinal < appShelfItems.count {
            setWindowIndexKeyPending(
                appSymbol: symbol,
                appGroupIndex: appShelfItems[ordinal].item.appGroupIndex,
                startedAt: now
            )
            let appGroupIndex = appShelfItems[ordinal].item.appGroupIndex
            if let card = singleVisibleWaterfallCard(appGroupIndex: appGroupIndex) {
                recordKeyboardCommand("windowSingle:\(symbol)")
                lastWindowIndexKeyCommand = "windowSingle:\(symbol)"
                lastWindowIndexKeyCommitCode = symbol
                lastWindowShortcutPreCommitFilterSnapshot = nil
                clearWindowIndexKeyPending()
                commitKeyboardWindowCard(card, trigger: "windowIndexSingleApp")
            } else {
                setWindowIndexKeyPending(
                    appSymbol: symbol,
                    appGroupIndex: appGroupIndex,
                    startedAt: now
                )
            }
        } else {
            selectionChangedByLastCommand = false
            clearWindowIndexKeyPending()
@@ -6426,6 +6436,11 @@
        waterfallColumns.flatMap(\.cards)
    }
    private func singleVisibleWaterfallCard(appGroupIndex: Int) -> WaterfallCardLayers? {
        let cards = visibleWaterfallCards.filter { $0.item.appGroupIndex == appGroupIndex }
        return cards.count == 1 ? cards[0] : nil
    }
    private var windowShortcutFilterDimOpacity: Float {
        resolvedTheme == .dark ? 0.44 : 0.38
    }
C3.tools/round1-window-index-progressive-filter-fixture-qa.sh
@@ -27,6 +27,7 @@
REPORT_HORIZONTAL_DARK="$BUILD_REPORT_ROOT/round01-window-index-progressive-filter-horizontal-dark-report.json"
REPORT_COMMIT="$BUILD_REPORT_ROOT/round01-window-index-progressive-filter-commit-report.json"
REPORT_INVALID="$BUILD_REPORT_ROOT/round01-window-index-progressive-filter-invalid-report.json"
REPORT_SINGLE_WINDOW="$BUILD_REPORT_ROOT/round01-window-index-progressive-filter-single-window-report.json"
SUMMARY_DIR="$BUILD_TMP_ROOT/round01-window-index-progressive-filter-fixture-qa"
APP_PID=""
@@ -140,11 +141,16 @@
  echo "app:$APP_SYMBOL,windowInvalid:${APP_SYMBOL}Z"
}
single_window_expected_sequence() {
  echo "app:$APP_SYMBOL,windowSingle:$APP_SYMBOL"
}
run_fixture() {
  local report="$1"
  local theme="$2"
  local mode="$3"
  local key_sequence="$4"
  local windows_per_app="${5:-$FIXTURE_WINDOWS_PER_APP}"
  stop_current_aligner
  set_theme "$theme"
@@ -154,7 +160,7 @@
    --round0-skip-permissions \
    --round01-open-quick-switch \
    --round01-fixture-app-count="$FIXTURE_APP_COUNT" \
    --round01-fixture-windows-per-app="$FIXTURE_WINDOWS_PER_APP" \
    --round01-fixture-windows-per-app="$windows_per_app" \
    --round01-disable-screenshot-refresh \
    --round01-waterfall-view-mode="$mode" \
    --round01-debug-key-sequence="$key_sequence" \
@@ -306,6 +312,51 @@
if last_report is not None:
    print(json.dumps(last_report, indent=2, ensure_ascii=False), file=sys.stderr)
print(f"invalid report did not reach invalid state within {timeout:.1f}s", file=sys.stderr)
sys.exit(1)
PY
}
wait_for_single_window_report() {
  local report="$1"
  local expected_mode="$2"
  local expected_sequence="$3"
  /usr/bin/python3 - "$report" "$REPORT_WAIT" "$expected_mode" "$expected_sequence" "$APP_SYMBOL" <<'PY'
import json
import sys
import time
path = sys.argv[1]
timeout = float(sys.argv[2])
expected_mode = sys.argv[3]
expected_sequence = [part for part in sys.argv[4].split(",") if part]
app_symbol = sys.argv[5]
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 root.get("waterfallViewMode") == expected_mode
            and root.get("keyboardCommandsApplied") == expected_sequence
            and root.get("lastWindowIndexKeyCommand") == f"windowSingle:{app_symbol}"
            and root.get("lastCommitSource") == "keyboard"
        ):
            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"single-window report did not reach direct commit within {timeout:.1f}s", file=sys.stderr)
sys.exit(1)
PY
}
@@ -689,6 +740,108 @@
  SUMMARY_FILES+=("$summary_file")
}
assert_single_window_report() {
  local report="$1"
  local case_name="single-window-first-key-commit-horizontal"
  local summary_file="$SUMMARY_DIR/$case_name.json"
  /usr/bin/python3 - \
    "$report" \
    "$case_name" \
    "horizontalMasonry" \
    "$(single_window_expected_sequence)" \
    "$FIXTURE_APP_COUNT" \
    "$TARGET_APP_INDEX" \
    "$APP_SYMBOL" \
    "$summary_file" <<'PY'
import json
import numbers
import sys
path = sys.argv[1]
case_name = sys.argv[2]
expected_mode = sys.argv[3]
expected_sequence = [part for part in sys.argv[4].split(",") if part]
fixture_app_count = int(sys.argv[5])
target_app_index = int(sys.argv[6])
app_symbol = sys.argv[7]
summary_file = sys.argv[8]
with open(path, "r", encoding="utf-8") as file:
    report = json.load(file)
def require(condition, message):
    if not condition:
        print(f"{case_name}: {message}", file=sys.stderr)
        print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
        sys.exit(1)
def is_number(value):
    return isinstance(value, numbers.Real) and not isinstance(value, bool)
root = report.get("rootView", {})
columns = root.get("waterfallColumns", [])
cards = [card for column in columns for card in column.get("cards", [])]
target_column = next((column for column in columns if column.get("appGroupIndex") == target_app_index), None)
require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
require(report.get("appCount") == fixture_app_count, "fixture appCount must match")
require(report.get("windowCount") == fixture_app_count, "single-window fixture must have one window per App")
require(root.get("waterfallViewMode") == expected_mode, "single-window run must use horizontal Waterfall")
require(root.get("keyboardCommandsApplied") == expected_sequence, "single-window key sequence must direct-commit")
require(root.get("appShelfIndexKeyDownCount") == 1, "single-window direct commit must use one physical keyDown")
require(root.get("lastAppShelfIndexKeySymbol") == app_symbol, "last physical symbol must be the App key")
require(root.get("lastWindowIndexKeyCommand") == f"windowSingle:{app_symbol}", "single-window direct command must be reported")
require(root.get("lastWindowIndexKeyCommitCode") == app_symbol, "single-window commit code must be the one-key shortcut")
require(root.get("lastCommitSource") == "keyboard", "single-window direct commit must be keyboard sourced")
require(root.get("windowIndexKeyPendingAppGroupIndex") is None, "single-window direct commit must not leave pending App group")
require(root.get("windowIndexKeyPendingAppSymbol") is None, "single-window direct commit must not leave pending App symbol")
require(root.get("windowShortcutFilterActive") is False, "single-window direct commit must not enter progressive filter")
require(root.get("windowShortcutFilterPhase") == "none", "single-window direct commit must keep filter phase none")
require(root.get("windowShortcutFilterMatchedCount") == 0, "single-window direct commit must not leave matched filter count")
require(root.get("windowShortcutFilterDimmedCount") == 0, "single-window direct commit must not leave dimmed filter count")
require(root.get("lastWindowShortcutPreCommitFilterSnapshot") is None, "single-window direct commit must not expose two-key pre-commit snapshot")
require(target_column is not None, "target App column must exist")
target_cards = target_column.get("cards", [])
require(len(target_cards) == 1, "target App must have exactly one visible window")
target_card = target_cards[0]
target_window_id = int(target_card.get("windowID"))
require(root.get("lastCommittedAppGroupIndex") == target_app_index, "direct commit must target expected App")
require(root.get("lastCommittedWindowIndex") == 0, "direct commit must target the only window")
require(root.get("lastCommittedWindowID") == target_window_id, "direct commit must target expected window ID")
require(root.get("selectedAppGroupIndex") == target_app_index, "selection must land on target App")
require(root.get("selectedWindowIndex") == 0, "selection must land on the only window")
require(root.get("selectedWindowID") == target_window_id, "selectedWindowID must match target window")
for card in cards:
    opacity = card.get("windowShortcutFilterOpacity")
    states = card.get("visualStates", [])
    require(card.get("windowShortcutFilterState") == "none", "single-window direct commit must not leave card filter state")
    require(is_number(opacity), "cards must expose numeric filter opacity")
    require(float(opacity) >= 0.99, "single-window direct commit must not dim cards")
    require("shortcutDimmed" not in states, "single-window direct commit must not leave shortcutDimmed state")
    require("shortcutMatched" not in states, "single-window direct commit must not leave shortcutMatched state")
    require("shortcutTarget" not in states, "single-window direct commit must not leave shortcutTarget state")
summary = {
    "case": case_name,
    "status": "passed",
    "mode": root.get("waterfallViewMode"),
    "report": path,
    "commands": root.get("keyboardCommandsApplied"),
    "targetWindowID": target_window_id,
    "lastCommitSource": root.get("lastCommitSource"),
    "lastWindowIndexKeyCommitCode": root.get("lastWindowIndexKeyCommitCode"),
    "cardCount": len(cards)
}
with open(summary_file, "w", encoding="utf-8") as file:
    json.dump(summary, file, indent=2, ensure_ascii=False)
PY
  SUMMARY_FILES+=("$summary_file")
}
assert_pending_summaries_consistent() {
  local baseline="$1"
  local candidate="$2"
@@ -775,6 +928,11 @@
trap cleanup EXIT
stop_current_aligner
run_fixture "$REPORT_SINGLE_WINDOW" "light" "horizontal" "$(pending_key_sequence)" 1
wait_for_single_window_report "$REPORT_SINGLE_WINDOW" "$(mode_report_value horizontal)" "$(single_window_expected_sequence)"
assert_single_window_report "$REPORT_SINGLE_WINDOW"
finish_case
run_fixture "$REPORT_HORIZONTAL_LIGHT" "light" "horizontal" "$(pending_key_sequence)"
wait_for_pending_report "$REPORT_HORIZONTAL_LIGHT" "$(mode_report_value horizontal)" "$(pending_expected_sequence)" "pending-horizontal-light"
assert_pending_report "$REPORT_HORIZONTAL_LIGHT" "pending-horizontal-light" "light" "$(mode_report_value horizontal)"