From 146fbcf6430d2513df7fb536c7e9ace81f1e57b9 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 09 Jun 2026 21:45:32 +0800
Subject: [PATCH] Fix Quick Switch grouped display visuals

---
 C3.tools/round1-waterfall-fixture-qa.sh             |    6 ++
 C1.source/Sources/Aligner/QuickSwitchRootView.swift |   78 +++++++++++++-------------
 C1.source/Resources/Aligner-Info.plist              |    4 
 C3.tools/round1-three-zone-linkage-fixture-qa.sh    |   38 ++++++++++++
 4 files changed, 84 insertions(+), 42 deletions(-)

diff --git a/C1.source/Resources/Aligner-Info.plist b/C1.source/Resources/Aligner-Info.plist
index 6930b37..1a78fa3 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.48</string>
+    <string>0.0.49</string>
 	<key>CFBundleVersion</key>
-    <string>20260609.2055</string>
+    <string>20260609.2143</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 d8451c2..0b41426 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -283,7 +283,7 @@
         static let columnMaxWidth: CGFloat = 292
         static let contentHorizontalPadding: CGFloat = 0
         static let columnHorizontalPadding: CGFloat = 0
-        static let columnVerticalPadding: CGFloat = 12
+        static let columnVerticalPadding: CGFloat = 0
         static let columnHeaderTextInset: CGFloat = 12
         static let headerHeight: CGFloat = 34
         static let cardHeight: CGFloat = 138
@@ -304,11 +304,11 @@
     }
 
     private enum SpaceLaneMetrics {
-        static let horizontalPadding: CGFloat = 12
-        static let groupGap: CGFloat = 30
-        static let groupHorizontalPadding: CGFloat = 12
-        static let groupVerticalPadding: CGFloat = 10
-        static let segmentGap: CGFloat = 10
+        static let horizontalPadding: CGFloat = 0
+        static let groupGap: CGFloat = 36
+        static let groupHorizontalPadding: CGFloat = 24
+        static let groupVerticalPadding: CGFloat = 6
+        static let segmentGap: CGFloat = 12
         static let segmentMinWidth: CGFloat = 96
         static let segmentMaxWidth: CGFloat = 150
     }
@@ -669,6 +669,8 @@
             "glassBorderWidth": Double(glassLayer.borderWidth),
             "glassShadowOpacity": Double(glassLayer.shadowOpacity),
             "spaceLaneFrame": dictionary(from: laneLayer.frame),
+            "spaceLaneBackgroundColor": colorDictionary(from: laneLayer.backgroundColor),
+            "spaceLaneCornerRadius": Double(laneLayer.cornerRadius),
             "spaceLaneContentWidth": Double(spaceLaneContentWidth),
             "spaceLaneVisibleWidth": Double(laneLayer.bounds.width),
             "spaceLaneMaxScrollOffset": Double(spaceLaneMaxScrollOffset),
@@ -1576,8 +1578,8 @@
         glassLayer.shadowOpacity = 0
         rootLayer.addSublayer(glassLayer)
 
-        laneLayer.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.68).cgColor
-        laneLayer.cornerRadius = 16
+        laneLayer.backgroundColor = NSColor.clear.cgColor
+        laneLayer.cornerRadius = 0
         laneLayer.masksToBounds = true
         glassLayer.addSublayer(laneLayer)
         laneLayer.addSublayer(spaceLaneContentLayer)
@@ -1822,11 +1824,11 @@
         for display in currentViewModel?.displays ?? [] {
             let groupContainer = CALayer()
             groupContainer.name = display.displayLabel
-            groupContainer.cornerRadius = 16
+            groupContainer.cornerRadius = 14
             groupContainer.masksToBounds = false
-            groupContainer.borderWidth = 1
-            groupContainer.borderColor = NSColor.separatorColor.withAlphaComponent(0.26).cgColor
-            groupContainer.backgroundColor = NSColor.windowBackgroundColor.withAlphaComponent(0.28).cgColor
+            groupContainer.borderWidth = 0
+            groupContainer.borderColor = NSColor.clear.cgColor
+            groupContainer.backgroundColor = NSColor.windowBackgroundColor.cgColor
             groupContainer.shadowColor = NSColor.black.cgColor
             groupContainer.shadowOpacity = 0
             groupContainer.shadowRadius = 0
@@ -2033,7 +2035,8 @@
     private func layoutSpaceLaneSegments() {
         guard !spaceLaneDisplayGroups.isEmpty, !spaceLaneSegments.isEmpty else { return }
 
-        let groupHeight = max(1, laneLayer.bounds.height - 16)
+        let groupOuterVerticalInset: CGFloat = 6
+        let groupHeight = max(1, laneLayer.bounds.height - groupOuterVerticalInset * 2)
         let segmentHeight = max(1, groupHeight - SpaceLaneMetrics.groupVerticalPadding * 2)
         let groupGapWidth = CGFloat(max(0, spaceLaneDisplayGroups.count - 1)) * SpaceLaneMetrics.groupGap
         let segmentGapWidth = spaceLaneDisplayGroups.reduce(CGFloat(0)) { total, group in
@@ -2076,7 +2079,7 @@
             let groupWidth = groupWidths[index]
             group.containerLayer.frame = CGRect(
                 x: x,
-                y: 8,
+                y: groupOuterVerticalInset,
                 width: groupWidth,
                 height: groupHeight
             )
@@ -2114,29 +2117,14 @@
                 || segment.space.id == windowHoverAssociatedSpaceID
         }
         let hasCurrentSegment = group.display.spaces.contains { $0.isCurrent }
-        let hasWindows = group.display.spaces.contains { $0.windowCount > 0 }
-        let background = hasActiveSegment
-            ? accentTintedWindowBackground(fraction: 0.035, alpha: 0.62)
-            : NSColor.windowBackgroundColor.withAlphaComponent(hasWindows ? 0.44 : 0.30)
-
-        group.containerLayer.backgroundColor = background.cgColor
-        group.containerLayer.borderColor = (hasActiveSegment
-            ? spaceLaneOccupiedColor.withAlphaComponent(0.36)
-            : hasCurrentSegment
-                ? NSColor.labelColor.withAlphaComponent(0.22)
-                : NSColor.separatorColor.withAlphaComponent(0.24)
-        ).cgColor
-        group.containerLayer.borderWidth = 1
-        group.containerLayer.shadowOpacity = hasActiveSegment ? 0.12 : 0
-        group.containerLayer.shadowRadius = hasActiveSegment ? 14 : 0
-        group.containerLayer.shadowOffset = CGSize(width: 0, height: -3)
+        group.containerLayer.backgroundColor = NSColor.windowBackgroundColor.cgColor
+        group.containerLayer.borderColor = NSColor.clear.cgColor
+        group.containerLayer.borderWidth = 0
+        group.containerLayer.shadowOpacity = 0
+        group.containerLayer.shadowRadius = 0
+        group.containerLayer.shadowOffset = .zero
         group.containerLayer.zPosition = hasActiveSegment ? 8 : hasCurrentSegment ? 4 : 0
-        group.containerLayer.shadowPath = CGPath(
-            roundedRect: group.containerLayer.bounds,
-            cornerWidth: group.containerLayer.cornerRadius,
-            cornerHeight: group.containerLayer.cornerRadius,
-            transform: nil
-        )
+        group.containerLayer.shadowPath = nil
     }
 
     private func applySpaceLaneVisualState(_ segment: SpaceLaneSegmentLayers) {
@@ -3457,7 +3445,7 @@
             return NSColor.controlBackgroundColor.withAlphaComponent(0.36)
         }
 
-        return NSColor.labelColor.withAlphaComponent(space.isCurrent ? 0.07 : 0.05)
+        return NSColor.clear
     }
 
     private var spaceLaneOccupiedColor: NSColor {
@@ -3689,9 +3677,9 @@
     private func fullscreenMarkerFrame(in segmentBounds: CGRect) -> CGRect {
         CGRect(
             x: 10,
-            y: 12,
+            y: 30,
             width: max(1, segmentBounds.width - 20),
-            height: max(1, segmentBounds.height - 64)
+            height: max(1, segmentBounds.height - 58)
         )
     }
 
@@ -3778,6 +3766,13 @@
             let visibleFrame = column.containerLayer.frame.offsetBy(dx: waterfallContentLayer.frame.minX, dy: 0)
             let appGroupIndex = column.column.appGroupIndex
             let isHovered = appGroupIndex == effectiveHoveredAppGroupIndex
+            let firstCardFrame = column.cards.first?.containerLayer.frame
+            let headerToFirstCardGap = firstCardFrame.map {
+                column.headerLayer.frame.minY - $0.maxY
+            }
+            let topToFirstCardGap = firstCardFrame.map {
+                column.containerLayer.bounds.height - $0.maxY
+            }
 
             return [
                 "appGroupIndex": appGroupIndex,
@@ -3791,6 +3786,9 @@
                 "spaceFocusDirection": spaceFocusDirection(for: appGroupIndex),
                 "frame": dictionary(from: column.containerLayer.frame),
                 "visibleFrame": dictionary(from: visibleFrame),
+                "headerFrame": dictionary(from: column.headerLayer.frame),
+                "headerToFirstCardGap": headerToFirstCardGap.map(Double.init) ?? NSNull(),
+                "topToFirstCardGap": topToFirstCardGap.map(Double.init) ?? NSNull(),
                 "appNameFrame": dictionary(from: column.appNameLayer.frame),
                 "appNameAlignment": column.appNameLayer.alignmentMode.rawValue,
                 "appNameColor": colorDictionary(from: column.appNameLayer.foregroundColor),
@@ -4031,6 +4029,8 @@
                 "shadowOpacity": Double(group.containerLayer.shadowOpacity),
                 "shadowRadius": Double(group.containerLayer.shadowRadius),
                 "groupGap": Double(SpaceLaneMetrics.groupGap),
+                "groupHorizontalPadding": Double(SpaceLaneMetrics.groupHorizontalPadding),
+                "groupVerticalPadding": Double(SpaceLaneMetrics.groupVerticalPadding),
                 "segmentGap": Double(SpaceLaneMetrics.segmentGap)
             ] as [String: Any]
         }
diff --git a/C3.tools/round1-three-zone-linkage-fixture-qa.sh b/C3.tools/round1-three-zone-linkage-fixture-qa.sh
index 93a8bc2..3510d7f 100755
--- a/C3.tools/round1-three-zone-linkage-fixture-qa.sh
+++ b/C3.tools/round1-three-zone-linkage-fixture-qa.sh
@@ -534,6 +534,7 @@
 root = report.get("rootView", {})
 groups = root.get("spaceLaneDisplayGroups", [])
 segments = root.get("spaceLaneSegments", [])
+lane_background = root.get("spaceLaneBackgroundColor", {})
 
 require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
 require(report.get("quickSwitchVisible") is True, "multi-display report must keep Quick Switch visible")
@@ -544,6 +545,8 @@
 require(len(segments) == 5, "Space segment reports must contain five segments")
 require(groups[0].get("spaceLabels") == ["A1", "A2", "A3"], "first Display group must contain A spaces")
 require(groups[1].get("spaceLabels") == ["B1", "B2"], "second Display group must contain B spaces")
+require(lane_background.get("alpha", 1) <= 0.01, "Space Lane outer background must be transparent")
+require(root.get("spaceLaneCornerRadius") == 0, "Space Lane outer layer must not draw a rounded white rectangle")
 
 first_group_frame = groups[0].get("visibleFrame", {})
 second_group_frame = groups[1].get("visibleFrame", {})
@@ -556,9 +559,23 @@
 for group in groups:
     group_frame = group.get("visibleFrame", {})
     labels = group.get("spaceLabels", [])
+    require(group.get("borderWidth") == 0, "Display group container must be borderless")
+    require(group.get("shadowOpacity") == 0, "Display group container must not add a floating shadow")
+    require(group.get("backgroundColor", {}).get("alpha", 0) >= 0.98, "Display group container must use an opaque system surface")
+    require(group.get("groupHorizontalPadding", 0) >= 24, "Display group horizontal padding must be widened")
     for label in labels:
         require(label in segments_by_label, f"segment report must include {label}")
         require(contains(group_frame, segments_by_label[label].get("visibleFrame", {})), f"{label} must be inside its Display group")
+    first_segment = segments_by_label[labels[0]].get("visibleFrame", {})
+    last_segment = segments_by_label[labels[-1]].get("visibleFrame", {})
+    require(
+        first_segment.get("x", 0) - group_frame.get("x", 0) >= group.get("groupHorizontalPadding", 0) - 1.0,
+        "Display group leading padding must match widened padding"
+    )
+    require(
+        right_edge(group_frame) - right_edge(last_segment) >= group.get("groupHorizontalPadding", 0) - 1.0,
+        "Display group trailing padding must match widened padding"
+    )
 
 for group in groups:
     labels = group.get("spaceLabels", [])
@@ -578,6 +595,25 @@
 require(len(focused) == 1, "exactly one Space segment must be focused")
 require(focused[0].get("label") == "B1", "hovered multi-display Space must be B1")
 require("focused" in focused[0].get("visualStates", []), "focused B1 segment must expose focused visual state")
+
+idle_empty_segments = [
+    segment for segment in segments
+    if segment.get("windowCount") == 0
+    and segment.get("isFocused") is not True
+    and segment.get("isCurrent") is not True
+    and segment.get("isAppAssociated") is not True
+    and segment.get("isWindowAssociated") is not True
+]
+require(idle_empty_segments, "multi-display visual fixture must include at least one idle empty Space")
+require(
+    all(segment.get("backgroundColor", {}).get("alpha", 1) <= 0.01 for segment in idle_empty_segments),
+    "idle empty Spaces must not keep a dirty gray fill"
+)
+fullscreen_segments = [segment for segment in segments if segment.get("type") == "fullscreen"]
+require(fullscreen_segments, "multi-display visual fixture must include a fullscreen Space")
+for segment in fullscreen_segments:
+    marker_frame = segment.get("fullscreenMarkerFrame", {})
+    require(marker_frame.get("y") == 30, "fullscreen Space marker must keep the previous vertical style")
 
 print(json.dumps({
     "displayGroups": [
@@ -626,7 +662,7 @@
 run_report "$SCROLL_REPORT" "scroll-app-shelf:99999" "scroll-app-shelf:99999" 60 1
 assert_app_shelf_scroll_report "$SCROLL_REPORT"
 
-run_report "$DISPLAY_GROUP_REPORT" "hover-space:4" "hover-space:4" 6 3 report-only multi-display
+run_report "$DISPLAY_GROUP_REPORT" "hover-space:4" "hover-space:4" 1 1 report-only multi-display
 assert_display_group_report "$DISPLAY_GROUP_REPORT"
 
 swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch >&2
diff --git a/C3.tools/round1-waterfall-fixture-qa.sh b/C3.tools/round1-waterfall-fixture-qa.sh
index 444920b..c90b454 100755
--- a/C3.tools/round1-waterfall-fixture-qa.sh
+++ b/C3.tools/round1-waterfall-fixture-qa.sh
@@ -121,8 +121,12 @@
     require(column.get("windowCount") == fixture_windows_per_app, "Waterfall column window count must match fixture")
     require(column.get("appNameAlignment") == "center", "Waterfall column app name must be center aligned")
     column_frame = column.get("frame", {})
+    header_frame = column.get("headerFrame", {})
     app_name_frame = column.get("appNameFrame", {})
     count_frame = column.get("countFrame", {})
+    header_to_first_card_gap = column.get("headerToFirstCardGap")
+    top_to_first_card_gap = column.get("topToFirstCardGap")
+    require(isinstance(header_frame, dict), "Waterfall column must expose headerFrame")
     require(isinstance(app_name_frame, dict), "Waterfall column must expose appNameFrame")
     require(isinstance(count_frame, dict), "Waterfall column must expose countFrame")
     column_width = column_frame.get("width", 0)
@@ -131,6 +135,8 @@
     require(app_name_frame.get("width", 0) >= column_width * 0.88, "Waterfall column app name frame must span most of the title bar")
     require(count_frame.get("x", 0) + count_frame.get("width", 0) <= column_width + 1.0, "Waterfall count frame must remain inside the title bar")
     require(count_frame.get("x", 0) >= column_width * 0.72, "Waterfall count frame must stay in the right side of the title bar")
+    require(abs(header_to_first_card_gap) <= 1.0, "Waterfall first card must sit directly under the title bar")
+    require(top_to_first_card_gap <= header_frame.get("height", 0) + 1.0, "Waterfall title-to-card height must remain compact")
     require(column.get("maxVerticalScrollOffset", 0) > 0, "Long Waterfall column must expose vertical scroll overflow")
     require(column.get("verticalScrollOffset") == 0, "Waterfall column should start at top")
 

--
Gitblit v1.9.3