From 0045898e8cd7b2d8972bb24c8ac27e514bdd8c70 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 11 Jun 2026 16:46:31 +0800
Subject: [PATCH] Implement Quick Switch Space filtering

---
 C3.tools/round1-session-snapshot-qa.sh |   93 +++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 87 insertions(+), 6 deletions(-)

diff --git a/C3.tools/round1-session-snapshot-qa.sh b/C3.tools/round1-session-snapshot-qa.sh
index d45033c..981dd57 100755
--- a/C3.tools/round1-session-snapshot-qa.sh
+++ b/C3.tools/round1-session-snapshot-qa.sh
@@ -55,6 +55,18 @@
         print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
         sys.exit(1)
 
+def near(lhs, rhs, tolerance=0.01):
+    return abs((lhs or 0) - (rhs or 0)) <= tolerance
+
+def is_blue(color, min_alpha=0.30):
+    return (
+        isinstance(color, dict)
+        and color.get("blue", 0) >= 0.85
+        and color.get("green", 0) >= 0.35
+        and color.get("red", 1) <= 0.18
+        and color.get("alpha", 0) >= min_alpha
+    )
+
 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")
@@ -70,9 +82,24 @@
 space_labels = report.get("spaceLabels", [])
 lane_labels = root_view.get("spaceLaneLabels", [])
 lane_segments = root_view.get("spaceLaneSegments", [])
+root_bounds = root_view.get("bounds", {})
+glass_frame = root_view.get("glassFrame", {})
+safe_area_top_inset = root_view.get("safeAreaTopInset", 0)
+safe_area_fill_frame = root_view.get("safeAreaFillFrame", {})
 lane_frame = root_view.get("spaceLaneFrame", {})
 shelf_frame = root_view.get("appShelfFrame", {})
 waterfall_frame = root_view.get("waterfallFrame", {})
+require(near(glass_frame.get("x"), root_bounds.get("x")), "Quick Switch glass layer x must match root bounds")
+require(near(glass_frame.get("y"), root_bounds.get("y")), "Quick Switch glass layer y must match root bounds")
+require(near(glass_frame.get("width"), root_bounds.get("width")), "Quick Switch glass layer width must match root bounds")
+require(near(glass_frame.get("height"), root_bounds.get("height")), "Quick Switch glass layer height must match root bounds")
+require(root_view.get("glassCornerRadius") == 0, "Quick Switch glass layer must not draw an outer rounded frame")
+require(root_view.get("glassBorderWidth") == 0, "Quick Switch glass layer must not draw an outer border")
+require(root_view.get("glassShadowOpacity") == 0, "Quick Switch glass layer must not draw an outer shadow")
+require(safe_area_top_inset >= 0, "safeAreaTopInset must be non-negative")
+if safe_area_top_inset > 0.5:
+    require(near(safe_area_fill_frame.get("height"), safe_area_top_inset), "safe-area fill height must match safeAreaTopInset")
+    require(near(safe_area_fill_frame.get("y") + safe_area_fill_frame.get("height"), root_bounds.get("height")), "safe-area fill must sit at the top edge")
 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")
@@ -83,6 +110,30 @@
 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")
+require(all(isinstance(segment.get("borderColor"), dict) for segment in lane_segments), "Space Lane segments must expose border colors")
+require(all(isinstance(segment.get("windowBlockColors"), list) for segment in lane_segments), "Space Lane segments must expose window block colors")
+occupied_segments = [segment for segment in lane_segments if segment.get("windowCount", 0) > 0]
+empty_segments = [segment for segment in lane_segments if segment.get("windowCount", 0) == 0]
+require(all(is_blue(segment.get("borderColor"), min_alpha=0.34) for segment in occupied_segments), "occupied Space segments must use blue borders")
+require(all(not is_blue(segment.get("borderColor"), min_alpha=0.34) for segment in empty_segments), "empty Space segments must not use blue borders")
+occupied_floating_segments = [segment for segment in occupied_segments if segment.get("type") != "fullscreen"]
+require(all(segment.get("windowBlockCount", 0) > 0 for segment in occupied_floating_segments), "occupied non-fullscreen Spaces must expose window blocks")
+for segment in occupied_floating_segments:
+    require(all(is_blue(color, min_alpha=0.50) for color in segment.get("windowBlockColors", [])), "occupied Space window blocks must be blue")
+same_display_gaps = []
+cross_display_gaps = []
+for previous, current in zip(lane_segments, lane_segments[1:]):
+    previous_frame = previous.get("visibleFrame", {})
+    current_frame = current.get("visibleFrame", {})
+    gap = current_frame.get("x", 0) - (previous_frame.get("x", 0) + previous_frame.get("width", 0))
+    if previous.get("displayUUID") == current.get("displayUUID"):
+        same_display_gaps.append(gap)
+    else:
+        cross_display_gaps.append(gap)
+if cross_display_gaps:
+    require(all(gap >= 24 for gap in cross_display_gaps), "Space Lane must leave a visible gap between displays")
+    if same_display_gaps:
+        require(min(cross_display_gaps) > max(same_display_gaps) + 8, "display gap must be clearly larger than same-display Space gaps")
 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")
@@ -92,8 +143,19 @@
 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")
+require(all(segment.get("fullscreenMarkerKind") == "cornerBracket" for segment in fullscreen_segments), "fullscreen segments must use corner bracket marker")
+require(all(segment.get("windowBlockCount") == 0 for segment in fullscreen_segments), "fullscreen segments must not expose floating-window blocks")
+non_fullscreen_segments = [segment for segment in lane_segments if segment.get("type") != "fullscreen"]
+require(all(segment.get("fullscreenMarkerKind") == "none" for segment in non_fullscreen_segments), "non-fullscreen segments must not expose fullscreen marker")
 for segment in fullscreen_segments:
     require("fullscreen" in segment.get("visualStates", []), "fullscreen segments must expose fullscreen visual state")
+    marker_frame = segment.get("fullscreenMarkerFrame", {})
+    app_frame = segment.get("appFrame", {})
+    require(isinstance(marker_frame, dict), "fullscreen segments must expose marker frame")
+    require(isinstance(app_frame, dict), "fullscreen segments must expose centered App label frame")
+    marker_mid_y = marker_frame.get("y", 0) + marker_frame.get("height", 0) / 2
+    app_mid_y = app_frame.get("y", 0) + app_frame.get("height", 0) / 2
+    require(abs(marker_mid_y - app_mid_y) <= 2, "fullscreen App label must be vertically centered in marker")
 for segment in lane_segments:
     if segment.get("isCurrent") is True:
         require("current" in segment.get("visualStates", []), "current segment must expose current visual state")
@@ -116,6 +178,12 @@
     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")
+    if lane_segments:
+        first_frame = lane_segments[0].get("visibleFrame", {})
+        last_frame = lane_segments[-1].get("visibleFrame", {})
+        left_blank = first_frame.get("x", 0)
+        right_blank = lane_visible_width - (last_frame.get("x", 0) + last_frame.get("width", 0))
+        require(abs(left_blank - right_blank) <= 2, "non-overflowing Space Lane segments must be centered")
 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")
@@ -135,12 +203,12 @@
 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:
+    if item.get("isHovered") is True:
         expected_icon_size = root_view.get("appShelfHoverIconSize")
     else:
         expected_icon_size = icon_size
+    if item.get("isSelected") is True and item.get("isHovered") is not True:
+        require("selectedVisualSuppressed" in item.get("visualStates", []), "non-hovered selected App Shelf item must suppress selected visual state")
     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}
@@ -221,6 +289,14 @@
         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("titleBarFrame"), dict), "Waterfall cards must expose title bar frames")
+        require(card.get("titleBarBorderWidth") == 0, "Waterfall title area must not draw an enclosing rounded rectangle")
+        require(isinstance(card.get("titleFrame"), dict), "Waterfall cards must expose title frames")
+        card_frame = card.get("frame", {})
+        thumbnail_frame = card.get("thumbnailFrame", {})
+        title_bar_frame = card.get("titleBarFrame", {})
+        require(thumbnail_frame.get("width", 0) >= card_frame.get("width", 0) * 0.82, "Waterfall thumbnail must use most of the card width")
+        require(title_bar_frame.get("y", 0) > thumbnail_frame.get("y", 0) + thumbnail_frame.get("height", 0), "Waterfall title bar must sit above the thumbnail")
         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")
@@ -239,6 +315,11 @@
 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")
+if root_view.get("activeSpaceFocusID") is None:
+    require(
+        all("spaceFocused" not in card.get("visualStates", []) for column in waterfall_columns for card in column.get("cards", [])),
+        "Waterfall cards must not expose spaceFocused when no Space Lane focus is active"
+    )
 selected_cards = [
     card
     for column in waterfall_columns
@@ -246,9 +327,9 @@
     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("selectedVisualSuppressed" in selected_cards[0].get("visualStates", []), "non-hovered selected Waterfall card must suppress selected visual state")
+require(selected_cards[0].get("shineVisible") is False, "non-hovered selected Waterfall card must not expose shine layer")
+require(selected_cards[0].get("zPosition", 0) == 0, "non-hovered selected Waterfall card must not float 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")

--
Gitblit v1.9.3