Ariver
2026-06-04 7a4f911c64b552081d957e826f32d90b2b11c858
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
#!/bin/bash
# Round01 three-zone linkage fixture QA. It verifies Space Lane focus drives
# App Shelf highlights/badges and Waterfall positioning.
 
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_LINKAGE_FIXTURE_APP_COUNT:-8}"
FIXTURE_WINDOWS_PER_APP="${ALIGNER_ROUND1_LINKAGE_FIXTURE_WINDOWS_PER_APP:-8}"
REPORT_WAIT="${ALIGNER_ROUND1_LINKAGE_REPORT_WAIT:-6.0}"
OVERLAY_WIDTH="${ALIGNER_ROUND1_LINKAGE_OVERLAY_WIDTH:-1180}"
LOCK_DIR="$BUILD_LOCK_ROOT/round1-three-zone-linkage-fixture-qa.lock"
APP_PID=""
LOCK_HELD=0
 
fail() {
  echo "Round01 three-zone linkage fixture QA failed: $*" >&2
  exit 1
}
 
REMOTE_APP_INDEX=$((FIXTURE_APP_COUNT - 1))
if [ "$FIXTURE_APP_COUNT" -lt 8 ]; then
  fail "fixture app count must be at least 8 to verify right-side remote App linkage"
fi
if [ "$FIXTURE_WINDOWS_PER_APP" -lt 8 ]; then
  fail "fixture windows per app must be at least 8 so every fixture App has a Space A1 window"
fi
 
acquire_lock() {
  if ! mkdir "$LOCK_DIR" 2>/dev/null; then
    fail "another round1-three-zone-linkage-fixture-qa.sh appears to be running; run packaging QA scripts serially"
  fi
  LOCK_HELD=1
}
 
release_lock() {
  if [ "$LOCK_HELD" -eq 1 ]; then
    rmdir "$LOCK_DIR" 2>/dev/null || true
    LOCK_HELD=0
  fi
}
 
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"
}
 
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
  release_lock
}
 
wait_for_report() {
  local report="$1"
  local expected_last_mouse="$2"
 
  /usr/bin/python3 - "$report" "$REPORT_WAIT" "$expected_last_mouse" <<'PY'
import json
import sys
import time
 
path = sys.argv[1]
timeout = float(sys.argv[2])
expected_last_mouse = sys.argv[3]
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("lastMouseCommand") == expected_last_mouse
        ):
            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"report did not reach expected mouse command {expected_last_mouse!r} within {timeout:.1f}s", file=sys.stderr)
sys.exit(1)
PY
}
 
run_report() {
  local report="$1"
  local sequence="$2"
  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" \
    --round0-skip-permissions \
    --round01-open-quick-switch \
    --round01-fixture-app-count="$app_count" \
    --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"
  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
  APP_PID=""
  sleep 0.3
  stop_current_aligner
}
 
assert_focus_report() {
  /usr/bin/python3 - "$1" "$FIXTURE_APP_COUNT" <<'PY'
import json
import sys
 
path = sys.argv[1]
fixture_app_count = 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)
 
root = report.get("rootView", {})
items = root.get("appShelfItems", [])
columns = root.get("waterfallColumns", [])
segments = root.get("spaceLaneSegments", [])
 
require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
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") == "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")
require(all(item.get("isSpaceFocused") is True for item in items), "all fixture apps must be highlighted for Space A1")
require(all("spaceFocused" in item.get("visualStates", []) for item in items), "App Shelf items must expose spaceFocused visual state")
require(all(item.get("spaceFocusWindowCount", 0) > 0 for item in items), "focused Space badge count must be positive for every fixture app")
require(all(item.get("badgeVisible") is True for item in items), "focused Space badges must be visible")
require(items[0].get("spaceFocusDirection") == "visible", "first related App must be visible")
require(items[-1].get("spaceFocusDirection") == "right", "last fixture App must be reported to the right before remote click")
require(str(items[-1].get("badgeText", "")).endswith(">"), "right-side remote App badge must include right direction marker")
 
require(len(columns) == fixture_app_count, "Waterfall column reports must match app count")
require(columns[0].get("isSpaceFocused") is True, "first Waterfall column must be marked focused for Space")
require(columns[0].get("spaceFocusDirection") == "visible", "first Waterfall column must be visible")
require(columns[-1].get("isSpaceFocused") is True, "last Waterfall column must be marked focused for Space")
require(columns[-1].get("spaceFocusDirection") == "right", "last Waterfall column must report right direction before remote click")
 
print(json.dumps({
    "activeSpaceFocusID": root.get("activeSpaceFocusID"),
    "focusedApps": root.get("spaceFocusAppGroupIndexes"),
    "lastRemoteDirection": items[-1].get("spaceFocusDirection"),
    "lastRemoteBadgeText": items[-1].get("badgeText")
}, indent=2, ensure_ascii=False))
PY
}
 
assert_remote_click_report() {
  /usr/bin/python3 - "$1" "$FIXTURE_APP_COUNT" <<'PY'
import json
import sys
 
path = sys.argv[1]
fixture_app_count = 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)
 
root = report.get("rootView", {})
items = root.get("appShelfItems", [])
columns = root.get("waterfallColumns", [])
last_index = fixture_app_count - 1
 
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")
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")
require(items[last_index].get("badgeVisible") is True, "focused Space badge must remain visible after remote click")
 
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
}
 
assert_app_shelf_scroll_report() {
  /usr/bin/python3 - "$1" "$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", {})
items = root.get("appShelfItems", [])
columns = root.get("waterfallColumns", [])
synced = root.get("lastAppShelfScrollSyncedAppGroupIndex")
 
require(root.get("appShelfScrollable") is True, "App Shelf must be scrollable in the scroll linkage fixture")
require(root.get("appShelfScrollOffset", 0) > 0, "debug App Shelf scroll must move App Shelf")
require(isinstance(synced, int), "App Shelf scroll must record the synced App group")
require(root.get("waterfallScrollOffset", 0) > 0, "App Shelf scroll must move Waterfall to the synced App column")
require(0 <= synced < len(items), "synced App group must point at a real App Shelf item")
require(columns[synced].get("spaceFocusDirection") == "none", "App Shelf scroll QA should not depend on Space focus state")
 
visible = columns[synced].get("visibleFrame", {})
require(visible.get("x", -10_000) < root.get("waterfallVisibleWidth", 0), "synced Waterfall column must enter the visible viewport")
require(visible.get("x", 10_000) + visible.get("width", 0) > 0, "synced Waterfall column must not remain fully offscreen")
 
print(json.dumps({
    "appShelfScrollOffset": root.get("appShelfScrollOffset"),
    "syncedAppGroupIndex": synced,
    "waterfallScrollOffset": root.get("waterfallScrollOffset")
}, indent=2, ensure_ascii=False))
PY
}
 
trap cleanup EXIT
 
acquire_lock
stop_current_aligner
"$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" "hover-space:1" "hover-space:1"
assert_focus_report "$FOCUS_REPORT"
 
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
assert_app_shelf_scroll_report "$SCROLL_REPORT"
 
swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch >&2
stop_current_aligner
release_lock
trap - EXIT