Ariver
2026-06-04 933e0a66306b7bd53d67f48834cb7f56f5cd8d1b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
#!/bin/bash
# Round01 session snapshot QA. It opens the Quick Switch session through the app
# entrypoint and verifies that the session froze a real Space/Window snapshot
# into a UI-facing report.
 
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="$BUILD_REPORT_ROOT/round01-quick-switch-session-report.json"
OPEN_WAIT="${ALIGNER_ROUND1_SESSION_SNAPSHOT_OPEN_WAIT:-1.2}"
REPORT_WAIT="${ALIGNER_ROUND1_SESSION_SNAPSHOT_REPORT_WAIT:-6.0}"
 
fail() {
  echo "Round01 session snapshot QA failed: $*" >&2
  exit 1
}
 
aligner_pids_for_current_app() {
  ps -axo pid=,args= | while read -r pid command; do
    case "$command" in
      "$APP/Contents/MacOS/Aligner"*) echo "$pid" ;;
    esac
  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"
}
 
assert_report() {
  /usr/bin/python3 - "$REPORT" <<'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)
 
require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
require(report.get("displayCount", 0) >= 1, "displayCount must be >= 1")
require(report.get("spaceCount", 0) >= 1, "spaceCount must be >= 1")
require(report.get("appCount", -1) == report.get("columnCount", -2), "appCount must equal columnCount")
require(report.get("windowCount", 0) >= 1, "windowCount must be >= 1 on the QA desktop")
require(report.get("initialSelectionWindowID") is not None, "initialSelectionWindowID must exist when windows exist")
require(len(report.get("spaceLabels", [])) == report.get("spaceCount"), "spaceLabels count must match spaceCount")
require(report.get("snapshotRanOnMainThread") is False, "snapshot must not run on the main thread")
require(report.get("rootView", {}).get("viewClass") == "QuickSwitchRootView", "root view must be QuickSwitchRootView")
require(report.get("rootView", {}).get("isLayerBacked") is True, "root view must be layer-backed AppKit")
require(report.get("rootView", {}).get("waterfallFrame", {}).get("width", 0) > 0, "waterfall frame must be laid out")
root_view = report.get("rootView", {})
space_labels = report.get("spaceLabels", [])
lane_labels = root_view.get("spaceLaneLabels", [])
lane_segments = root_view.get("spaceLaneSegments", [])
lane_frame = root_view.get("spaceLaneFrame", {})
shelf_frame = root_view.get("appShelfFrame", {})
waterfall_frame = root_view.get("waterfallFrame", {})
require(root_view.get("spaceLaneSegmentCount") == report.get("spaceCount"), "Space Lane segment count must match spaceCount")
require(lane_labels == space_labels, "Space Lane labels must match report spaceLabels exactly")
require(len(set(lane_labels)) == len(lane_labels), "Space Lane labels must be unique")
require(len(lane_segments) == report.get("spaceCount"), "Space Lane segment reports must match spaceCount")
require([segment.get("label") for segment in lane_segments] == space_labels, "Space Lane segment reports must follow spaceLabels order")
require(all(isinstance(segment.get("windowCount"), int) for segment in lane_segments), "Space Lane segments must expose window counts")
require(all(isinstance(segment.get("appCount"), int) for segment in lane_segments), "Space Lane segments must expose app counts")
require(all(isinstance(segment.get("appNames"), list) for segment in lane_segments), "Space Lane segments must expose app names")
require(all(isinstance(segment.get("frame"), dict) for segment in lane_segments), "Space Lane segments must expose content frames")
require(all(isinstance(segment.get("visibleFrame"), dict) for segment in lane_segments), "Space Lane segments must expose visible frames")
current_labels = root_view.get("spaceLaneCurrentLabels", [])
fullscreen_labels = root_view.get("spaceLaneFullscreenLabels", [])
require(len(current_labels) >= 1, "Space Lane must mark at least one current Space")
require(set(current_labels).issubset(set(space_labels)), "current Space labels must be a subset of Space Lane labels")
require(set(fullscreen_labels).issubset(set(space_labels)), "fullscreen Space labels must be a subset of Space Lane labels")
require(len(fullscreen_labels) >= 1, "QA desktop must include at least one fullscreen Space in Space Lane")
fullscreen_segments = [segment for segment in lane_segments if segment.get("type") == "fullscreen"]
require([segment.get("label") for segment in fullscreen_segments] == fullscreen_labels, "fullscreen segment reports must match fullscreen labels")
require(all(segment.get("fullscreenMarkerVisible") is True for segment in fullscreen_segments), "fullscreen segments must expose fullscreen marker")
for segment in fullscreen_segments:
    require("fullscreen" in segment.get("visualStates", []), "fullscreen segments must expose fullscreen visual state")
for segment in lane_segments:
    if segment.get("isCurrent") is True:
        require("current" in segment.get("visualStates", []), "current segment must expose current visual state")
if set(current_labels).intersection(fullscreen_labels):
    for segment in lane_segments:
        if segment.get("label") in set(current_labels).intersection(fullscreen_labels):
            states = set(segment.get("visualStates", []))
            require({"current", "fullscreen"}.issubset(states), "current fullscreen segment must expose both visual states")
require(lane_frame.get("width", 0) > lane_frame.get("height", 0) * 4, "Space Lane must be a top horizontal region")
require(lane_frame.get("y", 0) > shelf_frame.get("y", 0), "Space Lane must be above App Shelf")
require(shelf_frame.get("y", 0) > waterfall_frame.get("y", 0), "App Shelf must be above Waterfall")
lane_visible_width = root_view.get("spaceLaneVisibleWidth", 0)
lane_content_width = root_view.get("spaceLaneContentWidth", 0)
lane_max_scroll_offset = root_view.get("spaceLaneMaxScrollOffset", 0)
require(lane_visible_width == lane_frame.get("width", 0), "Space Lane visible width must match frame width")
require(lane_content_width >= lane_visible_width, "Space Lane content width must cover visible width")
require(lane_max_scroll_offset == max(0, lane_content_width - lane_visible_width), "Space Lane max scroll offset must match content overflow")
if lane_content_width > lane_visible_width:
    require(root_view.get("spaceLaneScrollable") is True, "overflowing Space Lane must be scrollable")
    require(lane_max_scroll_offset > 0, "overflowing Space Lane must expose positive max scroll offset")
else:
    require(root_view.get("spaceLaneScrollable") is False, "non-overflowing Space Lane should not report scrollable")
app_shelf_items = root_view.get("appShelfItems", [])
app_shelf_names = root_view.get("appShelfNames", [])
require(root_view.get("appShelfItemCount") == report.get("appCount"), "App Shelf item count must match appCount")
require(app_shelf_names == report.get("appShelfNames", []), "App Shelf order must match report appShelfNames")
require(len(app_shelf_items) == report.get("appCount"), "App Shelf item reports must match appCount")
require([item.get("name") for item in app_shelf_items] == report.get("appShelfNames", []), "App Shelf item reports must follow appShelfNames order")
require(root_view.get("appShelfMaxRows") == 2, "App Shelf max rows must be 2")
require(1 <= root_view.get("appShelfRows", 0) <= 2, "App Shelf must use 1 or 2 rows when apps exist")
require(root_view.get("appShelfNormalIconSize") == 56, "App Shelf normal icon size must be 56pt")
require(root_view.get("appShelfHoverIconSize") == 68, "App Shelf hover icon size must be 68pt")
require(root_view.get("appShelfSelectedIconSize") == 68, "App Shelf selected icon size must be 68pt")
require(root_view.get("appShelfMinIconSize") == 36, "App Shelf min icon size must be 36pt")
icon_size = root_view.get("appShelfIconSize", 0)
require(36 <= icon_size <= 56, "App Shelf layout icon size must stay between 36pt and 56pt")
require(all(item.get("labelTruncationMode") == "middle" for item in app_shelf_items), "App Shelf labels must use middle truncation")
require(sum(1 for item in app_shelf_items if item.get("isSelected") is True) == 1, "App Shelf must expose one selected app")
require(all(isinstance(item.get("frame"), dict) for item in app_shelf_items), "App Shelf items must expose frames")
require(all(isinstance(item.get("iconFrame"), dict) for item in app_shelf_items), "App Shelf items must expose icon frames")
for item in app_shelf_items:
    if item.get("isSelected") is True:
        expected_icon_size = root_view.get("appShelfSelectedIconSize")
    elif item.get("isHovered") is True:
        expected_icon_size = root_view.get("appShelfHoverIconSize")
    else:
        expected_icon_size = icon_size
    require(abs(item.get("iconFrame", {}).get("width", -1) - expected_icon_size) < 0.01, "App Shelf icon frame width must match visual icon size")
    require(abs(item.get("iconFrame", {}).get("height", -1) - expected_icon_size) < 0.01, "App Shelf icon frame height must match visual icon size")
row_y_values = {round(item.get("frame", {}).get("y", 0), 2) for item in app_shelf_items}
require(len(row_y_values) == root_view.get("appShelfRows"), "App Shelf reported row count must match visible row y positions")
app_visible_width = root_view.get("appShelfVisibleWidth", 0)
app_content_width = root_view.get("appShelfContentWidth", 0)
app_max_scroll_offset = root_view.get("appShelfMaxScrollOffset", 0)
require(app_visible_width == shelf_frame.get("width", 0), "App Shelf visible width must match frame width")
require(app_content_width >= app_visible_width, "App Shelf content width must cover visible width")
require(app_max_scroll_offset == max(0, app_content_width - app_visible_width), "App Shelf max scroll offset must match content overflow")
if app_content_width > app_visible_width:
    require(root_view.get("appShelfScrollable") is True, "overflowing App Shelf must be scrollable")
else:
    require(root_view.get("appShelfScrollable") is False, "non-overflowing App Shelf should not report scrollable")
waterfall_columns = root_view.get("waterfallColumns", [])
waterfall_names = root_view.get("waterfallColumnNames", [])
require(root_view.get("waterfallColumnCount") == report.get("columnCount"), "Waterfall column count must match report columnCount")
require(root_view.get("waterfallColumnCount") == report.get("appCount"), "Waterfall column count must match appCount")
require(waterfall_names == app_shelf_names, "Waterfall column order must match App Shelf order")
require(len(waterfall_columns) == report.get("columnCount"), "Waterfall column reports must match columnCount")
require(root_view.get("waterfallClipsToBounds") is True, "Waterfall viewport must clip overflowing columns")
waterfall_visible_width = root_view.get("waterfallVisibleWidth", 0)
waterfall_content_width = root_view.get("waterfallContentWidth", 0)
waterfall_max_scroll_offset = root_view.get("waterfallMaxScrollOffset", 0)
require(waterfall_visible_width == waterfall_frame.get("width", 0), "Waterfall visible width must match frame width")
require(waterfall_content_width >= waterfall_visible_width, "Waterfall content width must cover visible width")
require(waterfall_max_scroll_offset == max(0, waterfall_content_width - waterfall_visible_width), "Waterfall max scroll offset must match content overflow")
if waterfall_content_width > waterfall_visible_width:
    require(root_view.get("waterfallScrollable") is True, "overflowing Waterfall must be scrollable")
else:
    require(root_view.get("waterfallScrollable") is False, "non-overflowing Waterfall should not report scrollable")
waterfall_card_count = 0
global_indexes = []
window_ids = []
low_information_card_keys = []
valid_thumbnail_strategies = {
    "skeleton",
    "skeletonPreferred",
    "pendingScreenshot",
    "realScreenshot",
    "skeletonFallback",
}
valid_screenshot_sources = {
    "pending",
    "notRequested",
    "realScreenshot",
    "skeletonFallback",
}
for expected_index, column in enumerate(waterfall_columns):
    require(column.get("appGroupIndex") == expected_index, "Waterfall appGroupIndex must be sequential")
    require(column.get("appName") == app_shelf_names[expected_index], "Waterfall column app name must match App Shelf order")
    require(isinstance(column.get("frame"), dict), "Waterfall columns must expose frames")
    require(isinstance(column.get("visibleFrame"), dict), "Waterfall columns must expose visible frames")
    require(isinstance(column.get("maxVerticalScrollOffset"), (int, float)), "Waterfall columns must expose vertical scroll range")
    require(isinstance(column.get("verticalScrollOffset"), (int, float)), "Waterfall columns must expose vertical scroll offset")
    cards = column.get("cards", [])
    require(len(cards) == column.get("windowCount"), "Waterfall card count must match column windowCount")
    require([card.get("windowIndex") for card in cards] == list(range(len(cards))), "Waterfall card windowIndex must be sequential per column")
    for card in cards:
        require(card.get("appGroupIndex") == expected_index, "Waterfall card appGroupIndex must match its column")
        require(isinstance(card.get("globalIndex"), int), "Waterfall cards must expose globalIndex")
        require(isinstance(card.get("windowID"), int), "Waterfall cards must expose windowID")
        window_ids.append(card.get("windowID"))
        if card.get("identifierSource") == "cgWindow" and card.get("titleLength") == 0:
            low_information_card_keys.append((
                column.get("bundleIdentifier"),
                card.get("titleHash"),
                card.get("titleLength"),
                card.get("primarySpaceID"),
                card.get("identifierSource"),
            ))
        require(isinstance(card.get("frame"), dict), "Waterfall cards must expose frames")
        require(isinstance(card.get("visibleFrame"), dict), "Waterfall cards must expose visible frames")
        require(card.get("thumbnailStrategy") in valid_thumbnail_strategies, "Waterfall cards must expose valid thumbnail strategy")
        require(card.get("screenshotSource") in valid_screenshot_sources, "Waterfall cards must expose valid screenshot source")
        if card.get("screenshotSource") == "skeletonFallback":
            require(card.get("screenshotFallbackReason") is not None, "skeleton fallback cards must expose fallback reason")
        if card.get("screenshotSource") == "notRequested":
            require(card.get("screenshotNotRequestedReason") is not None, "not-requested screenshot cards must expose reason")
        require(isinstance(card.get("thumbnailFrame"), dict), "Waterfall cards must expose thumbnail frames")
        require(isinstance(card.get("appIconFrame"), dict), "Waterfall cards must expose App icon frames")
        require(isinstance(card.get("visualStates"), list), "Waterfall cards must expose visual states")
        require(card.get("titleTruncationMode") == "middle", "Waterfall card titles must use middle truncation")
        global_indexes.append(card.get("globalIndex"))
    waterfall_card_count += len(cards)
require(waterfall_card_count == report.get("windowCount"), "Waterfall cards must cover report windowCount")
require(len(window_ids) == len(set(window_ids)), "Waterfall windowIDs must be unique")
require(
    len(low_information_card_keys) == len(set(low_information_card_keys)),
    "Waterfall must not expose duplicate low-information CG window candidates"
)
require(isinstance(root_view.get("screenshotEligibleCount"), int), "root view must expose screenshotEligibleCount")
require(isinstance(root_view.get("screenshotResolvedCount"), int), "root view must expose screenshotResolvedCount")
require(isinstance(root_view.get("screenshotPendingCount"), int), "root view must expose screenshotPendingCount")
require(isinstance(root_view.get("screenshotNotRequestedCount"), int), "root view must expose screenshotNotRequestedCount")
require(root_view.get("screenshotResolvedCount") + root_view.get("screenshotPendingCount") == root_view.get("screenshotEligibleCount"), "resolved + pending screenshots must match eligible count")
require(root_view.get("screenshotEligibleCount") + root_view.get("screenshotNotRequestedCount") == waterfall_card_count, "eligible + not-requested screenshots must cover all cards")
require(global_indexes == sorted(global_indexes), "Waterfall global indexes must be monotonic across columns")
selected_cards = [
    card
    for column in waterfall_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")
require("selected" in selected_cards[0].get("visualStates", []), "selected Waterfall card must expose selected visual state")
require(selected_cards[0].get("shineVisible") is True, "selected Waterfall card must expose shine layer")
require(selected_cards[0].get("zPosition", 0) > 0, "selected Waterfall card must be above normal cards")
require(isinstance(report.get("overlayOpenElapsedMilliseconds"), (int, float)), "overlay open timing must exist")
require(isinstance(report.get("snapshotStartElapsedMilliseconds"), (int, float)), "snapshot start timing must exist")
require(report["snapshotStartElapsedMilliseconds"] >= report["overlayOpenElapsedMilliseconds"], "snapshot must start after overlay open path")
require(isinstance(report.get("snapshotDurationMilliseconds"), (int, float)), "snapshot duration timing must exist")
 
print(json.dumps(report, indent=2, ensure_ascii=False))
PY
}
 
click_target_from_report() {
  /usr/bin/python3 - "$REPORT" <<'PY'
import json
import sys
 
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as file:
    report = json.load(file)
 
root = report.get("rootView", {})
for column in root.get("waterfallColumns", []):
    for card in column.get("cards", []):
        if card.get("identifierSource") == "cgWindow" and card.get("isMinimized") is False:
            print(f"{card.get('appGroupIndex')}:{card.get('windowIndex')}:{card.get('windowID')}")
            sys.exit(0)
 
print("no non-minimized cgWindow card available for click activation QA", file=sys.stderr)
sys.exit(1)
PY
}
 
wait_for_click_report() {
  /usr/bin/python3 - "$REPORT" "$REPORT_WAIT" <<'PY'
import json
import sys
import time
 
path = sys.argv[1]
timeout = float(sys.argv[2])
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
        if report.get("lastCommitSource") == "mouse" and report.get("lastActivationResult") is not None:
            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"click activation report did not complete within {timeout:.1f}s", file=sys.stderr)
sys.exit(1)
PY
}
 
assert_click_report() {
  /usr/bin/python3 - "$REPORT" "$1" <<'PY'
import json
import sys
 
path = sys.argv[1]
expected_window_id = int(sys.argv[2])
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)
 
require(report.get("lastCommitSource") == "mouse", "click activation source must be mouse")
require(report.get("lastActivationWindowID") == expected_window_id, "click activation must target the selected card windowID")
require(report.get("lastActivationResult") == "activated", "click activation must activate the target window")
print(json.dumps({
    "lastCommitSource": report.get("lastCommitSource"),
    "lastActivationWindowID": report.get("lastActivationWindowID"),
    "lastActivationResult": report.get("lastActivationResult"),
}, indent=2, ensure_ascii=False))
PY
}
 
wait_for_loaded_report() {
  /usr/bin/python3 - "$REPORT" "$REPORT_WAIT" <<'PY'
import json
import sys
import time
 
path = sys.argv[1]
timeout = float(sys.argv[2])
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
        if report.get("snapshotLoaded") is True:
            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"snapshot report did not become loaded within {timeout:.1f}s", file=sys.stderr)
sys.exit(1)
PY
}
 
stop_current_aligner
"$SCRIPT_DIR/package-app.sh" >&2
rm -f "$REPORT"
 
"$APP/Contents/MacOS/Aligner" \
  --round0-skip-permissions \
  --round01-open-quick-switch \
  --round01-quick-switch-report="$REPORT" &
 
APP_PID=$!
 
cleanup() {
  if kill -0 "$APP_PID" 2>/dev/null; then
    kill "$APP_PID" 2>/dev/null || true
    wait "$APP_PID" 2>/dev/null || true
  fi
}
trap cleanup EXIT
 
sleep "$OPEN_WAIT"
 
swift "$SCRIPT_DIR/window-logic-qa.swift" \
  --expect-quick-switch \
  --expect-single-aligner-overlay \
  --expect-overlay-on-mouse-screen \
  --expect-overlay-uses-screen-frame
 
wait_for_loaded_report
assert_report
CLICK_TARGET="$(click_target_from_report)"
 
cleanup
trap - EXIT
 
rm -f "$REPORT"
IFS=: read -r CLICK_APP_INDEX CLICK_WINDOW_INDEX CLICK_WINDOW_ID <<<"$CLICK_TARGET"
"$APP/Contents/MacOS/Aligner" \
  --round0-skip-permissions \
  --round01-open-quick-switch \
  --round01-disable-screenshot-refresh \
  --round01-debug-mouse-sequence="click-card:$CLICK_APP_INDEX:$CLICK_WINDOW_INDEX" \
  --round01-quick-switch-report="$REPORT" &
 
APP_PID=$!
trap cleanup EXIT
wait_for_click_report
assert_click_report "$CLICK_WINDOW_ID"
cleanup
trap - EXIT
 
swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch