Ariver
2026-06-14 d5cf4d94edbe72d53fb417bf32105c2258bce924
Fix horizontal App hover linkage
5 files modified
87 ■■■■ changed files
C1.source/Resources/Aligner-Info.plist 4 ●●●● patch | view | raw | blame | history
C1.source/Sources/Aligner/QuickSwitchRootView.swift 46 ●●●● patch | view | raw | blame | history
C3.tools/round1-app-shelf-fixture-qa.sh 1 ●●●● patch | view | raw | blame | history
C3.tools/round1-horizontal-waterfall-fixture-qa.sh 35 ●●●●● patch | view | raw | blame | history
C3.tools/round1-three-zone-linkage-fixture-qa.sh 1 ●●●● patch | view | raw | blame | history
C1.source/Resources/Aligner-Info.plist
@@ -17,9 +17,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>0.0.66</string>
    <string>0.0.67</string>
    <key>CFBundleVersion</key>
    <string>20260613.2043</string>
    <string>20260614.1559</string>
    <key>LSMinimumSystemVersion</key>
    <string>26.0</string>
    <key>NSHighResolutionCapable</key>
C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -3340,6 +3340,14 @@
        )
    }
    private var appIconHoverLinkedAppGroupIndex: Int? {
        if let debugHoveredAppGroupIndex {
            return debugHoveredAppGroupIndex
        }
        guard case .app(let appGroupIndex) = hoverTarget else { return nil }
        return appGroupIndex
    }
    private var appHoverAssociatedSpaceLabels: [String] {
        guard !appHoverAssociatedSpaceIDs.isEmpty else { return [] }
        return currentViewModel?.displays
@@ -3767,10 +3775,13 @@
        let bounds = card.containerLayer.bounds
        let isSelected = card.item.window.id == effectiveSelection?.windowID
        let isHovered = card.item.window.id == hoveredWindowID
        let isAppLinked = isHorizontalMasonryMode
            && card.item.appGroupIndex == appIconHoverLinkedAppGroupIndex
        applyWaterfallCardVisualState(
            card,
            selected: shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered),
            hovered: isHovered
            hovered: isHovered,
            appLinked: isAppLinked
        )
        let outerInset: CGFloat = 0
@@ -3833,12 +3844,19 @@
        )
    }
    private func applyWaterfallCardVisualState(_ card: WaterfallCardLayers, selected: Bool, hovered: Bool) {
    private func applyWaterfallCardVisualState(
        _ card: WaterfallCardLayers,
        selected: Bool,
        hovered: Bool,
        appLinked: Bool
    ) {
        let isSpaceFocused = isWaterfallCardSpaceFocused(card)
        card.containerLayer.backgroundColor = (selected
            ? NSColor.windowBackgroundColor.withAlphaComponent(0.94)
            : hovered
                ? accentTintedWindowBackground(fraction: 0.09, alpha: 0.95)
                : appLinked
                    ? accentTintedWindowBackground(fraction: 0.08, alpha: 0.93)
                : isSpaceFocused
                    ? accentTintedWindowBackground(fraction: 0.10, alpha: 0.94)
                : NSColor.windowBackgroundColor.withAlphaComponent(0.82)
@@ -3847,27 +3865,31 @@
            ? NSColor.controlAccentColor.withAlphaComponent(0.52)
            : hovered
                ? NSColor.controlAccentColor.withAlphaComponent(0.44)
                : appLinked
                    ? NSColor.controlAccentColor.withAlphaComponent(0.38)
                : isSpaceFocused
                    ? NSColor.controlAccentColor.withAlphaComponent(0.42)
                : NSColor.separatorColor.withAlphaComponent(0.28)
        ).cgColor
        card.containerLayer.borderWidth = (selected || hovered || isSpaceFocused) ? 1.5 : 1
        card.containerLayer.borderWidth = (selected || hovered || appLinked || isSpaceFocused) ? 1.5 : 1
        card.containerLayer.shadowColor = NSColor.black.cgColor
        card.containerLayer.shadowOpacity = selected ? 0.24 : hovered ? 0.14 : isSpaceFocused ? 0.20 : 0
        card.containerLayer.shadowRadius = selected ? 22 : hovered ? 17 : isSpaceFocused ? 24 : 0
        card.containerLayer.shadowOpacity = selected ? 0.24 : hovered ? 0.14 : appLinked ? 0.16 : isSpaceFocused ? 0.20 : 0
        card.containerLayer.shadowRadius = selected ? 22 : hovered ? 17 : appLinked ? 18 : isSpaceFocused ? 24 : 0
        card.containerLayer.shadowOffset = selected
            ? CGSize(width: 0, height: -6)
            : hovered
                ? CGSize(width: 0, height: -2.5)
                : appLinked
                    ? CGSize(width: 0, height: -3)
                : isSpaceFocused
                    ? CGSize(width: 0, height: -5)
                    : .zero
        card.containerLayer.zPosition = selected ? 20 : hovered ? 10 : isSpaceFocused ? 8 : 0
        card.containerLayer.zPosition = selected ? 20 : hovered ? 12 : appLinked ? 10 : isSpaceFocused ? 8 : 0
        let focusedScale: CGFloat = selected ? 1.015 : isSpaceFocused ? 1.012 : 1.008
        card.containerLayer.transform = (selected || hovered || isSpaceFocused)
            ? CATransform3DMakeScale(focusedScale, focusedScale, 1)
            : CATransform3DIdentity
        card.containerLayer.shadowPath = (selected || hovered || isSpaceFocused)
        card.containerLayer.shadowPath = (selected || hovered || appLinked || isSpaceFocused)
            ? CGPath(
                roundedRect: card.containerLayer.bounds,
                cornerWidth: card.containerLayer.cornerRadius,
@@ -3875,7 +3897,9 @@
                transform: nil
            )
            : nil
        card.titleBarLayer.backgroundColor = NSColor.clear.cgColor
        card.titleBarLayer.backgroundColor = appLinked
            ? spaceLaneOccupiedColor.withAlphaComponent(0.18).cgColor
            : NSColor.clear.cgColor
        card.titleBarLayer.borderColor = NSColor.clear.cgColor
        card.titleBarLayer.borderWidth = 0
        card.shineLayer.opacity = selected ? 1 : 0
@@ -5002,6 +5026,7 @@
                "shadowRadius": Double(card.containerLayer.shadowRadius),
                "thumbnailFrame": dictionary(from: card.thumbnailLayer.frame),
                "titleBarFrame": dictionary(from: card.titleBarLayer.frame),
                "titleBarBackgroundColor": colorDictionary(from: card.titleBarLayer.backgroundColor),
                "titleBarBorderWidth": Double(card.titleBarLayer.borderWidth),
                "titleFrame": dictionary(from: card.titleLayer.frame),
                "metaFrame": dictionary(from: card.metaLayer.frame),
@@ -5109,6 +5134,8 @@
        var states: [String] = []
        let isSelected = card.item.window.id == effectiveSelection?.windowID
        let isHovered = card.item.window.id == hoveredWindowID
        let isAppLinked = isHorizontalMasonryMode
            && card.item.appGroupIndex == appIconHoverLinkedAppGroupIndex
        if shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered) {
            states.append("selected")
        } else if isSelected {
@@ -5117,6 +5144,9 @@
        if isHovered {
            states.append("hover")
        }
        if isAppLinked {
            states.append("appLinked")
        }
        if isWaterfallCardSpaceFocused(card) {
            states.append("spaceFocused")
        }
C3.tools/round1-app-shelf-fixture-qa.sh
@@ -214,6 +214,7 @@
    --round01-open-quick-switch
    --round01-fixture-app-count="$app_count"
    --round01-disable-screenshot-refresh
    --round01-waterfall-view-mode=vertical
    --round01-debug-hover-app-index="$hover_index"
    --round01-quick-switch-report="$report"
  )
C3.tools/round1-horizontal-waterfall-fixture-qa.sh
@@ -122,6 +122,7 @@
require(root.get("horizontalMasonryMaxScrollOffset", 0) > 0, "horizontal masonry must expose positive vertical max scroll offset")
require(root.get("horizontalMasonryScrollOffset", 0) > 0, "hovering a later App must anchor masonry vertically")
require(root.get("appShelfHoveredIndex") == hover_app_index, "debug hover must mark the requested App Shelf item")
require(root.get("hoveredAppGroupIndex") == hover_app_index, "horizontal masonry must keep the hovered App group available to Waterfall visuals")
require(cards, "horizontal masonry fixture must expose cards")
widths = [round(card.get("frame", {}).get("width", 0), 1) for card in cards]
@@ -161,6 +162,37 @@
hover_visible = hover_first_card.get("visibleFrame", {})
require(hover_visible.get("y", -9999) < waterfall_height + 1, "hovered App first card should be brought into vertical viewport")
require(hover_visible.get("y", 0) + hover_visible.get("height", 0) > -1, "hovered App first card should not be fully below viewport")
hover_cards = hover_column.get("cards", [])
non_hover_cards = [
    card
    for column in columns
    if column.get("appGroupIndex") != hover_app_index
    for card in column.get("cards", [])
]
blue_reference = next(
    (
        color
        for segment in root.get("spaceLaneSegments", [])
        for color in segment.get("windowBlockColors", [])
        if color.get("alpha", 0) > 0
    ),
    None
)
require(blue_reference is not None, "fixture must expose a Space Lane blue reference color")
require(all("appLinked" in card.get("visualStates", []) for card in hover_cards), "all cards for the hovered App must expose appLinked visual state")
require(all("appLinked" not in card.get("visualStates", []) for card in non_hover_cards), "non-hovered App cards must not expose appLinked visual state")
require(all(card.get("shadowOpacity", 0) >= 0.15 for card in hover_cards), "hovered App cards must float with visible shadow opacity")
require(all(card.get("shadowRadius", 0) >= 18 for card in hover_cards), "hovered App cards must float with visible shadow radius")
require(all(card.get("shadowOpacity", 1) <= 0.01 for card in non_hover_cards if "spaceFocused" not in card.get("visualStates", []) and "hover" not in card.get("visualStates", []) and "selected" not in card.get("visualStates", [])), "normal non-hovered cards must not inherit App hover shadow")
for card in hover_cards:
    title_fill = card.get("titleBarBackgroundColor", {})
    require(title_fill.get("alpha", 0) >= 0.16, "hovered App card title bar must get a visible blue fill")
    for channel in ("red", "green", "blue"):
        require(
            abs(title_fill.get(channel, -1) - blue_reference.get(channel, -2)) <= 0.002,
            f"hovered App card title bar must use the Space Lane blue reference for {channel}"
        )
require(all(card.get("titleBarBackgroundColor", {}).get("alpha", 1) <= 0.01 for card in non_hover_cards), "non-hovered App card title bars must stay clear")
print(json.dumps({
    "mode": root.get("waterfallViewMode"),
@@ -170,6 +202,9 @@
    "cardHeight": heights[0],
    "thumbnailHeight": thumbnail_heights[0],
    "cardStep": expected_card_step,
    "hoveredAppGroupIndex": root.get("hoveredAppGroupIndex"),
    "hoveredAppLinkedCardCount": len(hover_cards),
    "hoveredAppTitleBarFill": hover_cards[0].get("titleBarBackgroundColor"),
    "horizontalMasonryScrollOffset": root.get("horizontalMasonryScrollOffset"),
    "horizontalMasonryMaxScrollOffset": root.get("horizontalMasonryMaxScrollOffset")
}, indent=2, ensure_ascii=False))
C3.tools/round1-three-zone-linkage-fixture-qa.sh
@@ -131,6 +131,7 @@
    --round01-debug-overlay-width="$OVERLAY_WIDTH"
    --round01-disable-screenshot-refresh
    --round01-debug-window-activation
    --round01-waterfall-view-mode=vertical
    --round01-debug-mouse-sequence="$sequence"
    --round01-quick-switch-report="$report"
  )