Ariver
2026-06-09 6a49b26ff3ef989844673cb77d2700404242c424
C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -22,11 +22,14 @@
    private let waterfallLayer = CALayer()
    private let waterfallContentLayer = CALayer()
    private lazy var closeConfirmationLayers = makeCloseConfirmationLayers()
    private let closeFeedbackLayer = CALayer()
    private let closeFeedbackTextLayer = CATextLayer()
    private let appIconProvider: any AppIconProviderProtocol = WorkspaceAppIconProvider()
    private let skeletonThumbnailProvider = NativeSkeletonThumbnailProvider(size: NSSize(width: 160, height: 96))
    private var screenshotSourcesByWindowID: [UInt32: ScreenshotResolutionSource] = [:]
    private var screenshotSkippedReasonsByWindowID: [UInt32: String] = [:]
    private var currentViewModel: QuickSwitchViewModel?
    private var spaceLaneDisplayGroups: [SpaceLaneDisplayGroupLayers] = []
    private var spaceLaneSegments: [SpaceLaneSegmentLayers] = []
    private var spaceLaneContentWidth: CGFloat = 0
    private var spaceLaneScrollOffset: CGFloat = 0
@@ -40,9 +43,14 @@
    private var hoveredSpaceID: UInt64?
    private var hoveredCloseTarget: CloseTarget?
    private var pendingCloseTarget: CloseTarget?
    private var pendingCloseRequest: QuickSwitchCloseRequest?
    private var pendingCloseOptOut = false
    private var closeConfirmationArrowEdge: CloseConfirmationArrowEdge?
    private var closeConfirmationRequired = true
    private var closeFeedback: CloseFeedback?
    private var closeFeedbackHideTask: Task<Void, Never>?
    private var hoverTarget: HoverTarget = .none
    private var hoverTargetSource: HoverTargetSource = .event
    private var hoveredSpaceLaneID: UInt64?
    private var focusedSpaceID: UInt64?
    private var debugHoveredAppGroupIndex: Int?
@@ -108,9 +116,92 @@
        let cancelButtonTextLayer: CATextLayer
    }
    private struct CloseFeedback: Equatable, Sendable {
        enum Kind: String, Sendable {
            case progress
            case failure
        }
        let kind: Kind
        let message: String
        let targetKind: String
        let appGroupIndex: Int
        let windowID: UInt32?
    }
    private enum CloseConfirmationArrowEdge: String {
        case left
        case right
    }
    private enum HoverTarget: Equatable {
        case none
        case space(UInt64)
        case app(Int)
        case window(appGroupIndex: Int, windowID: UInt32, spaceID: UInt64?)
        var kindDescription: String {
            switch self {
            case .none:
                return "none"
            case .space:
                return "space"
            case .app:
                return "app"
            case .window:
                return "window"
            }
        }
        var appGroupIndex: Int? {
            switch self {
            case .app(let appGroupIndex):
                return appGroupIndex
            case .window(let appGroupIndex, _, _):
                return appGroupIndex
            case .none, .space:
                return nil
            }
        }
        var windowID: UInt32? {
            switch self {
            case .window(_, let windowID, _):
                return windowID
            case .none, .space, .app:
                return nil
            }
        }
        var spaceID: UInt64? {
            switch self {
            case .space(let spaceID):
                return spaceID
            case .window(_, _, let spaceID):
                return spaceID
            case .none, .app:
                return nil
            }
        }
        var spaceLaneID: UInt64? {
            guard case .space(let spaceID) = self else { return nil }
            return spaceID
        }
        var shouldAlignWaterfallWithAppShelf: Bool {
            switch self {
            case .app:
                return true
            case .none, .space, .window:
                return false
            }
        }
    }
    private enum HoverTargetSource: String {
        case event
        case debug
    }
    private struct SpaceLaneSegmentLayers {
@@ -121,6 +212,12 @@
        let appLayer: CATextLayer
        let windowBlocks: [CALayer]
        let fullscreenMarkerLayer: CALayer?
    }
    private struct SpaceLaneDisplayGroupLayers {
        let display: QuickSwitchDisplayViewModel
        let containerLayer: CALayer
        let segments: [SpaceLaneSegmentLayers]
    }
    private struct AppShelfItemLayers {
@@ -184,11 +281,15 @@
        static let columnWidth: CGFloat = 252
        static let columnMinWidth: CGFloat = 220
        static let columnMaxWidth: CGFloat = 292
        static let columnPadding: CGFloat = 12
        static let contentHorizontalPadding: CGFloat = 0
        static let columnHorizontalPadding: CGFloat = 0
        static let columnVerticalPadding: CGFloat = 12
        static let columnHeaderTextInset: CGFloat = 12
        static let headerHeight: CGFloat = 34
        static let cardHeight: CGFloat = 138
        static let cardGap: CGFloat = 12
        static let revealPadding: CGFloat = 24
        static let horizontalRevealPadding: CGFloat = 0
    }
    private enum WaterfallHorizontalIntent {
@@ -200,6 +301,16 @@
        static let tolerance: CGFloat = 1
        static let animationDuration: CFTimeInterval = 0.18
        static let animationTimingName = CAMediaTimingFunctionName.easeOut
    }
    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 segmentMinWidth: CGFloat = 96
        static let segmentMaxWidth: CGFloat = 150
    }
    override init(frame frameRect: NSRect) {
@@ -231,7 +342,7 @@
        let trackingArea = NSTrackingArea(
            rect: bounds,
            options: [.activeInKeyWindow, .mouseMoved, .mouseEnteredAndExited, .inVisibleRect],
            options: [.activeAlways, .mouseMoved, .mouseEnteredAndExited, .inVisibleRect],
            owner: self,
            userInfo: nil
        )
@@ -268,6 +379,8 @@
    }
    override func scrollWheel(with event: NSEvent) {
        refreshHoverTarget(at: event.locationInWindow)
        let dominantDelta = abs(event.scrollingDeltaX) >= abs(event.scrollingDeltaY)
            ? event.scrollingDeltaX
            : event.scrollingDeltaY
@@ -318,43 +431,13 @@
            hoveredCloseTarget = closeTarget
            needsLayout = true
        }
        if let segment = spaceLaneSegment(at: event.locationInWindow) {
            hoverSpaceLaneSegment(segment)
            return
        }
        if let card = waterfallCard(at: event.locationInWindow) {
            hoveredSpaceLaneID = nil
            updateHoverState(
                appGroupIndex: card.item.appGroupIndex,
                windowID: card.item.window.id,
                spaceID: card.item.primarySpaceID
            )
            return
        }
        if let item = appShelfItem(at: event.locationInWindow) {
            hoveredSpaceLaneID = nil
            updateHoverState(
                appGroupIndex: item.item.appGroupIndex,
                windowID: nil,
                spaceID: nil,
                alignsWaterfallWithAppShelf: true
            )
            return
        }
        hoveredSpaceLaneID = nil
        hoveredCloseTarget = nil
        updateHoverState(appGroupIndex: nil, windowID: nil, spaceID: nil)
        refreshHoverTarget(at: event.locationInWindow)
    }
    override func mouseExited(with event: NSEvent) {
        guard debugHoveredAppGroupIndex == nil else { return }
        hoveredSpaceLaneID = nil
        hoveredCloseTarget = nil
        updateHoverState(appGroupIndex: nil, windowID: nil, spaceID: nil)
        setHoverTarget(.none)
    }
    override func mouseDown(with event: NSEvent) {
@@ -392,10 +475,8 @@
    func setDebugHoveredAppGroupIndex(_ index: Int?) {
        debugHoveredAppGroupIndex = index
        setHoverTarget(.none, source: .debug)
        hoveredAppGroupIndex = index
        hoveredWindowID = nil
        hoveredSpaceID = nil
        hoveredSpaceLaneID = nil
        needsLayout = true
    }
@@ -457,10 +538,47 @@
        closeConfirmationRequired = required
        if !required {
            pendingCloseTarget = nil
            pendingCloseRequest = nil
            pendingCloseOptOut = false
            closeConfirmationLayers.containerLayer.isHidden = true
            closeConfirmationArrowEdge = nil
        }
        needsLayout = true
    }
    func showCloseProgress(for request: QuickSwitchCloseRequest) {
        setCloseFeedback(
            CloseFeedback(
                kind: .progress,
                message: "正在关闭...",
                targetKind: request.kindDescription,
                appGroupIndex: request.appGroupIndex,
                windowID: request.windowID
            ),
            autoHideAfter: nil
        )
    }
    func showCloseFailure(message: String, for request: QuickSwitchCloseRequest) {
        setCloseFeedback(
            CloseFeedback(
                kind: .failure,
                message: message,
                targetKind: request.kindDescription,
                appGroupIndex: request.appGroupIndex,
                windowID: request.windowID
            ),
            autoHideAfter: 2.75
        )
    }
    func clearCloseFeedback() {
        closeFeedbackHideTask?.cancel()
        closeFeedbackHideTask = nil
        closeFeedback = nil
        closeFeedbackLayer.isHidden = true
        closeFeedbackLayer.opacity = 0
        closeFeedbackTextLayer.string = ""
        needsLayout = true
    }
@@ -471,7 +589,9 @@
        guard let card = waterfallCardsByWindowID()[windowID] else { return }
        card.thumbnailLayer.contents = resolution.image
        if case .realScreenshot = resolution.source {
            card.shineLayer.opacity = card.item.window.id == effectiveSelection?.windowID ? 1 : 0
            let isSelected = card.item.window.id == effectiveSelection?.windowID
            let isHovered = card.item.window.id == hoveredWindowID
            card.shineLayer.opacity = shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered) ? 1 : 0
        }
        needsLayout = true
    }
@@ -507,20 +627,16 @@
        lastSpaceFocusedAppGroupIndex = nil
        lastSpaceLaneClickSpaceID = nil
        lastAppShelfScrollSyncedAppGroupIndex = nil
        hoveredWindowID = nil
        hoveredSpaceID = nil
        focusedSpaceID = nil
        hoveredCloseTarget = nil
        pendingCloseTarget = nil
        pendingCloseRequest = nil
        pendingCloseOptOut = false
        closeConfirmationLayers.containerLayer.isHidden = true
        closeConfirmationArrowEdge = nil
        hoveredSpaceLaneID = nil
        focusedSpaceID = nil
        if debugHoveredAppGroupIndex == nil {
            hoveredAppGroupIndex = nil
        } else {
        clearCloseFeedback()
        setHoverTarget(.none)
            hoveredAppGroupIndex = debugHoveredAppGroupIndex
        }
        if let currentSelection {
            columnSelectionHistory[currentSelection.appGroupIndex] = currentSelection.windowIndex
        }
@@ -557,13 +673,20 @@
            "spaceLaneVisibleWidth": Double(laneLayer.bounds.width),
            "spaceLaneMaxScrollOffset": Double(spaceLaneMaxScrollOffset),
            "spaceLaneScrollable": spaceLaneMaxScrollOffset > 0,
            "spaceLaneDisplayGroupCount": spaceLaneDisplayGroups.count,
            "spaceLaneDisplayGroups": spaceLaneDisplayGroupReports(),
            "spaceLaneSegmentCount": spaceLaneSegments.count,
            "spaceLaneLabels": currentViewModel?.displays.flatMap { $0.spaces.map(\.label) } ?? [],
            "spaceLaneSegments": spaceLaneSegmentReports(),
            "hoverTargetKind": hoverTarget.kindDescription,
            "hoverTargetSource": hoverTargetSource.rawValue,
            "spaceLaneHoveredSpaceID": hoveredSpaceLaneID ?? NSNull(),
            "spaceLaneFocusedSpaceID": focusedSpaceID ?? NSNull(),
            "activeSpaceFocusID": activeSpaceFocusID ?? NSNull(),
            "spaceFocusAppGroupIndexes": spaceFocusedAppGroupIndexes(),
            "appHoverAssociatedSpaceIDs": appHoverAssociatedSpaceIDs.sorted(),
            "appHoverAssociatedSpaceLabels": appHoverAssociatedSpaceLabels,
            "windowHoverAssociatedSpaceID": windowHoverAssociatedSpaceID ?? NSNull(),
            "lastSpaceFocusedAppGroupIndex": lastSpaceFocusedAppGroupIndex ?? NSNull(),
            "lastSpaceLaneClickSpaceID": lastSpaceLaneClickSpaceID ?? NSNull(),
            "lastAppShelfScrollSyncedAppGroupIndex": lastAppShelfScrollSyncedAppGroupIndex ?? NSNull(),
@@ -627,6 +750,13 @@
            "closeConfirmationVisible": !closeConfirmationLayers.containerLayer.isHidden,
            "closeConfirmationFrame": dictionary(from: closeConfirmationLayers.containerLayer.frame),
            "closeConfirmationArrowEdge": closeConfirmationArrowEdge?.rawValue ?? NSNull(),
            "closeFeedbackVisible": closeFeedback != nil && !closeFeedbackLayer.isHidden,
            "closeFeedbackKind": closeFeedback?.kind.rawValue ?? NSNull(),
            "closeFeedbackMessage": closeFeedback?.message ?? NSNull(),
            "closeFeedbackTargetKind": closeFeedback?.targetKind ?? NSNull(),
            "closeFeedbackAppGroupIndex": closeFeedback?.appGroupIndex ?? NSNull(),
            "closeFeedbackWindowID": closeFeedback?.windowID ?? NSNull(),
            "closeFeedbackFrame": dictionary(from: closeFeedbackLayer.frame),
            "mouseCommandsApplied": mouseCommandsApplied,
            "lastMouseCommand": lastMouseCommand ?? NSNull(),
            "lastClickedAppGroupIndex": lastClickedAppGroupIndex ?? NSNull(),
@@ -756,7 +886,7 @@
            }
            recordMouseCommand("hover-space:\(spaceID)")
            if let segment = spaceLaneSegment(spaceID: spaceID) {
                hoverSpaceLaneSegment(segment)
                hoverSpaceLaneSegment(segment, source: .debug)
            }
        case "click-space" where parts.count == 2:
            guard let spaceID = UInt64(parts[1]) else {
@@ -774,7 +904,7 @@
            }
            recordMouseCommand("hover-card:\(appGroupIndex):\(windowIndex)")
            if let card = waterfallCard(appGroupIndex: appGroupIndex, windowIndex: windowIndex) {
                hoverWindowCard(card)
                hoverWindowCard(card, source: .debug)
            }
        case "click-card" where parts.count == 3:
            guard let appGroupIndex = Int(parts[1]), let windowIndex = Int(parts[2]) else {
@@ -826,13 +956,12 @@
    }
    private func hoverAppGroup(_ appGroupIndex: Int) {
        hoveredSpaceLaneID = nil
        hoveredCloseTarget = nil
        updateHoverState(
            appGroupIndex: appGroupIndex,
            windowID: nil,
            spaceID: nil,
            alignsWaterfallWithAppShelf: true
            alignsWaterfallWithAppShelf: true,
            source: .debug
        )
    }
@@ -847,19 +976,18 @@
        beginCloseConfirmation(for: .app(appGroupIndex: appGroupIndex))
    }
    private func hoverWindowCard(_ card: WaterfallCardLayers) {
        hoveredSpaceLaneID = nil
        hoveredCloseTarget = nil
    private func hoverWindowCard(_ card: WaterfallCardLayers, source: HoverTargetSource = .event) {
        updateHoverState(
            appGroupIndex: card.item.appGroupIndex,
            windowID: card.item.window.id,
            spaceID: card.item.primarySpaceID
            spaceID: card.item.primarySpaceID,
            source: source
        )
    }
    private func hoverCardCloseButton(appGroupIndex: Int, windowIndex: Int) {
        guard let card = waterfallCard(appGroupIndex: appGroupIndex, windowIndex: windowIndex) else { return }
        hoverWindowCard(card)
        hoverWindowCard(card, source: .debug)
        hoveredCloseTarget = .window(
            appGroupIndex: appGroupIndex,
            windowIndex: windowIndex,
@@ -880,8 +1008,8 @@
        )
    }
    private func hoverSpaceLaneSegment(_ segment: SpaceLaneSegmentLayers) {
        focusSpace(segment.space.id, persistent: false)
    private func hoverSpaceLaneSegment(_ segment: SpaceLaneSegmentLayers, source: HoverTargetSource = .event) {
        focusSpace(segment.space.id, persistent: false, source: source)
    }
    private func clickSpaceLaneSegment(_ segment: SpaceLaneSegmentLayers) {
@@ -902,11 +1030,8 @@
        commitWindowCard(firstCard, trigger: "spaceLane")
    }
    private func focusSpace(_ spaceID: UInt64, persistent: Bool) {
        hoveredAppGroupIndex = nil
        hoveredWindowID = nil
        hoveredSpaceID = spaceID
        hoveredSpaceLaneID = spaceID
    private func focusSpace(_ spaceID: UInt64, persistent: Bool, source: HoverTargetSource = .event) {
        setHoverTarget(.space(spaceID), source: source)
        if persistent {
            focusedSpaceID = spaceID
            lastSpaceLaneClickSpaceID = spaceID
@@ -1035,24 +1160,67 @@
        appGroupIndex: Int?,
        windowID: UInt32?,
        spaceID: UInt64?,
        alignsWaterfallWithAppShelf: Bool = false
        alignsWaterfallWithAppShelf: Bool = false,
        source: HoverTargetSource = .event
    ) {
        guard hoveredAppGroupIndex != appGroupIndex
            || hoveredWindowID != windowID
            || hoveredSpaceID != spaceID
        else {
            if alignsWaterfallWithAppShelf, let appGroupIndex {
                alignWaterfallColumnWithAppShelfIcon(appGroupIndex: appGroupIndex, animated: true)
            }
            return
        let target: HoverTarget
        if let appGroupIndex, let windowID {
            target = .window(appGroupIndex: appGroupIndex, windowID: windowID, spaceID: spaceID)
        } else if let appGroupIndex {
            target = .app(appGroupIndex)
        } else {
            target = .none
        }
        hoveredAppGroupIndex = appGroupIndex
        hoveredWindowID = windowID
        hoveredSpaceID = spaceID
        if alignsWaterfallWithAppShelf, let appGroupIndex {
        setHoverTarget(target, source: source, alignsWaterfallWithAppShelf: alignsWaterfallWithAppShelf)
    }
    private func refreshHoverTarget(at windowLocation: CGPoint) {
        guard debugHoveredAppGroupIndex == nil else { return }
        let target = resolveHoverTarget(at: windowLocation)
        setHoverTarget(target, alignsWaterfallWithAppShelf: target.shouldAlignWaterfallWithAppShelf)
    }
    private func resolveHoverTarget(at windowLocation: CGPoint) -> HoverTarget {
        if let segment = spaceLaneSegment(at: windowLocation) {
            return .space(segment.space.id)
        }
        if let card = waterfallCard(at: windowLocation) {
            return .window(
                appGroupIndex: card.item.appGroupIndex,
                windowID: card.item.window.id,
                spaceID: card.item.primarySpaceID
            )
        }
        if let item = appShelfItem(at: windowLocation) {
            return .app(item.item.appGroupIndex)
        }
        return .none
    }
    private func setHoverTarget(
        _ target: HoverTarget,
        source: HoverTargetSource = .event,
        alignsWaterfallWithAppShelf: Bool = false
    ) {
        let previousTarget = hoverTarget
        let previousSource = hoverTargetSource
        hoverTarget = target
        hoverTargetSource = source
        hoveredAppGroupIndex = target.appGroupIndex
        hoveredWindowID = target.windowID
        hoveredSpaceID = target.spaceID
        hoveredSpaceLaneID = target.spaceLaneID
        if alignsWaterfallWithAppShelf, let appGroupIndex = target.appGroupIndex {
            alignWaterfallColumnWithAppShelfIcon(appGroupIndex: appGroupIndex, animated: true)
        }
        guard previousTarget != target || previousSource != source else { return }
        needsLayout = true
    }
@@ -1165,7 +1333,7 @@
            1,
            waterfallLayer.bounds.height
                - WaterfallMetrics.headerHeight
                - WaterfallMetrics.columnPadding * 2
                - WaterfallMetrics.columnVerticalPadding * 2
        )
        let cardStride = WaterfallMetrics.cardHeight + WaterfallMetrics.cardGap
        let cardStart = CGFloat(selection.windowIndex) * cardStride
@@ -1228,7 +1396,7 @@
                currentOffset: waterfallScrollOffset,
                visibleWidth: waterfallLayer.bounds.width,
                maxOffset: waterfallMaxScrollOffset,
                inset: WaterfallMetrics.revealPadding
                inset: WaterfallMetrics.horizontalRevealPadding
            )
        )
    }
@@ -1427,6 +1595,24 @@
        closeConfirmationLayers.containerLayer.isHidden = true
        glassLayer.addSublayer(closeConfirmationLayers.containerLayer)
        closeFeedbackLayer.isHidden = true
        closeFeedbackLayer.opacity = 0
        closeFeedbackLayer.cornerRadius = 11
        closeFeedbackLayer.borderWidth = 1
        closeFeedbackLayer.masksToBounds = false
        closeFeedbackLayer.shadowColor = NSColor.black.cgColor
        closeFeedbackLayer.shadowOpacity = 0.14
        closeFeedbackLayer.shadowRadius = 16
        closeFeedbackLayer.shadowOffset = CGSize(width: 0, height: -5)
        closeFeedbackLayer.zPosition = 190
        closeFeedbackTextLayer.contentsScale = backingScaleFactor
        closeFeedbackTextLayer.font = NSFont.systemFont(ofSize: 13, weight: .medium)
        closeFeedbackTextLayer.fontSize = 13
        closeFeedbackTextLayer.alignmentMode = .center
        closeFeedbackTextLayer.truncationMode = .end
        closeFeedbackLayer.addSublayer(closeFeedbackTextLayer)
        glassLayer.addSublayer(closeFeedbackLayer)
    }
    private func rebuildAppShelfItems() {
@@ -1434,22 +1620,15 @@
            item.containerLayer.removeFromSuperlayer()
        }
        let selectedAppGroupIndex = effectiveSelection?.appGroupIndex
        appShelfItems = (currentViewModel?.appShelf ?? []).map { item in
            let container = CALayer()
            container.name = item.app.name
            let background = CALayer()
            background.cornerRadius = 10
            background.backgroundColor = (item.appGroupIndex == selectedAppGroupIndex
                ? NSColor.systemFill.withAlphaComponent(0.16)
                : NSColor.clear
            ).cgColor
            background.borderColor = (item.appGroupIndex == selectedAppGroupIndex
                ? NSColor.separatorColor.withAlphaComponent(0.36)
                : NSColor.clear
            ).cgColor
            background.borderWidth = item.appGroupIndex == selectedAppGroupIndex ? 1 : 0
            background.backgroundColor = NSColor.clear.cgColor
            background.borderColor = NSColor.clear.cgColor
            background.borderWidth = 0
            let icon = CALayer()
            icon.contentsGravity = .resizeAspect
@@ -1523,7 +1702,8 @@
                string: column.app.name,
                fontSize: 13,
                weight: .semibold,
                color: .labelColor
                color: .labelColor,
                alignment: .center
            )
            let count = makeTextLayer(
                string: "\(column.windows.count)",
@@ -1632,12 +1812,27 @@
    }
    private func rebuildSpaceLaneSegments() {
        for segment in spaceLaneSegments {
            segment.containerLayer.removeFromSuperlayer()
        for group in spaceLaneDisplayGroups {
            group.containerLayer.removeFromSuperlayer()
        }
        let spaces = currentViewModel?.displays.flatMap(\.spaces) ?? []
        spaceLaneSegments = spaces.map { space in
        var groups: [SpaceLaneDisplayGroupLayers] = []
        var allSegments: [SpaceLaneSegmentLayers] = []
        for display in currentViewModel?.displays ?? [] {
            let groupContainer = CALayer()
            groupContainer.name = display.displayLabel
            groupContainer.cornerRadius = 16
            groupContainer.masksToBounds = false
            groupContainer.borderWidth = 1
            groupContainer.borderColor = NSColor.separatorColor.withAlphaComponent(0.26).cgColor
            groupContainer.backgroundColor = NSColor.windowBackgroundColor.withAlphaComponent(0.28).cgColor
            groupContainer.shadowColor = NSColor.black.cgColor
            groupContainer.shadowOpacity = 0
            groupContainer.shadowRadius = 0
            groupContainer.shadowOffset = .zero
            let segments = display.spaces.map { space in
            let container = CALayer()
            container.name = space.label
            container.cornerRadius = 10
@@ -1680,7 +1875,7 @@
            for block in windowBlocks {
                container.addSublayer(block)
            }
            spaceLaneContentLayer.addSublayer(container)
                groupContainer.addSublayer(container)
            return SpaceLaneSegmentLayers(
                space: space,
@@ -1692,6 +1887,18 @@
                fullscreenMarkerLayer: fullscreenMarkerLayer
            )
        }
            spaceLaneContentLayer.addSublayer(groupContainer)
            groups.append(SpaceLaneDisplayGroupLayers(
                display: display,
                containerLayer: groupContainer,
                segments: segments
            ))
            allSegments.append(contentsOf: segments)
        }
        spaceLaneDisplayGroups = groups
        spaceLaneSegments = allSegments
    }
    private func layoutLayers() {
@@ -1740,6 +1947,7 @@
        layoutAppShelfItems()
        layoutWaterfallColumns()
        layoutCloseConfirmation()
        layoutCloseFeedback()
        CATransaction.commit()
    }
@@ -1823,15 +2031,33 @@
    }
    private func layoutSpaceLaneSegments() {
        guard !spaceLaneSegments.isEmpty else { return }
        guard !spaceLaneDisplayGroups.isEmpty, !spaceLaneSegments.isEmpty else { return }
        let gap: CGFloat = 10
        let segmentHeight = max(1, laneLayer.bounds.height - 20)
        let availableWidth = laneLayer.bounds.width - 24 - CGFloat(spaceLaneSegments.count - 1) * gap
        let segmentWidth = max(96, min(150, availableWidth / CGFloat(spaceLaneSegments.count)))
        let groupHeight = max(1, laneLayer.bounds.height - 16)
        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
            total + CGFloat(max(0, group.segments.count - 1)) * SpaceLaneMetrics.segmentGap
        }
        let groupPaddingWidth = CGFloat(spaceLaneDisplayGroups.count) * SpaceLaneMetrics.groupHorizontalPadding * 2
        let availableWidth = laneLayer.bounds.width
            - SpaceLaneMetrics.horizontalPadding * 2
            - groupGapWidth
            - segmentGapWidth
            - groupPaddingWidth
        let segmentWidth = max(
            SpaceLaneMetrics.segmentMinWidth,
            min(SpaceLaneMetrics.segmentMaxWidth, availableWidth / CGFloat(spaceLaneSegments.count))
        )
        let groupWidths = spaceLaneDisplayGroups.map { group in
            SpaceLaneMetrics.groupHorizontalPadding * 2
                + CGFloat(group.segments.count) * segmentWidth
                + CGFloat(max(0, group.segments.count - 1)) * SpaceLaneMetrics.segmentGap
        }
        let segmentGroupWidth = groupWidths.reduce(0, +) + groupGapWidth
        spaceLaneContentWidth = max(
            laneLayer.bounds.width,
            24 + CGFloat(spaceLaneSegments.count) * segmentWidth + CGFloat(spaceLaneSegments.count - 1) * gap
            SpaceLaneMetrics.horizontalPadding * 2 + segmentGroupWidth
        )
        spaceLaneScrollOffset = clampedSpaceLaneOffset(spaceLaneScrollOffset)
        spaceLaneContentLayer.frame = CGRect(
@@ -1841,13 +2067,35 @@
            height: laneLayer.bounds.height
        )
        var x: CGFloat = 12
        var x = max(
            SpaceLaneMetrics.horizontalPadding,
            (spaceLaneContentWidth - segmentGroupWidth) / 2
        )
        for segment in spaceLaneSegments {
            segment.containerLayer.frame = CGRect(x: x, y: 10, width: segmentWidth, height: segmentHeight)
        for (index, group) in spaceLaneDisplayGroups.enumerated() {
            let groupWidth = groupWidths[index]
            group.containerLayer.frame = CGRect(
                x: x,
                y: 8,
                width: groupWidth,
                height: groupHeight
            )
            applySpaceLaneDisplayGroupVisualState(group)
            var segmentX = SpaceLaneMetrics.groupHorizontalPadding
            for segment in group.segments {
                segment.containerLayer.frame = CGRect(
                    x: segmentX,
                    y: SpaceLaneMetrics.groupVerticalPadding,
                    width: segmentWidth,
                    height: segmentHeight
                )
            applySpaceLaneVisualState(segment)
            layoutSpaceLaneSegmentContents(segment)
            x += segmentWidth + gap
                segmentX += segmentWidth + SpaceLaneMetrics.segmentGap
            }
            x += groupWidth + SpaceLaneMetrics.groupGap
        }
    }
@@ -1859,29 +2107,74 @@
        min(max(0, offset), spaceLaneMaxScrollOffset)
    }
    private func applySpaceLaneDisplayGroupVisualState(_ group: SpaceLaneDisplayGroupLayers) {
        let hasActiveSegment = group.segments.contains { segment in
            segment.space.id == activeSpaceFocusID
                || appHoverAssociatedSpaceIDs.contains(segment.space.id)
                || 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.zPosition = hasActiveSegment ? 8 : hasCurrentSegment ? 4 : 0
        group.containerLayer.shadowPath = CGPath(
            roundedRect: group.containerLayer.bounds,
            cornerWidth: group.containerLayer.cornerRadius,
            cornerHeight: group.containerLayer.cornerRadius,
            transform: nil
        )
    }
    private func applySpaceLaneVisualState(_ segment: SpaceLaneSegmentLayers) {
        let isHovered = segment.space.id == hoveredSpaceID
        let isHovered = segment.space.id == activeSpaceFocusID
        let isFocused = segment.space.id == activeSpaceFocusID
        let isCurrent = segment.space.isCurrent
        let hasWindows = segment.space.windowCount > 0
        let isAppAssociated = appHoverAssociatedSpaceIDs.contains(segment.space.id)
        let isWindowAssociated = segment.space.id == windowHoverAssociatedSpaceID
        let isActive = isHovered || isFocused
        let isAssociated = (isAppAssociated || isWindowAssociated) && !isActive
        segment.containerLayer.borderWidth = (isCurrent || isActive) ? 1.5 : 1
        segment.containerLayer.borderWidth = (hasWindows || isCurrent || isActive || isAssociated) ? 1.5 : 1
        segment.containerLayer.borderColor = (isActive
            ? NSColor.controlAccentColor.withAlphaComponent(0.54)
            ? spaceLaneOccupiedColor.withAlphaComponent(0.62)
            : isAssociated
                ? spaceLaneOccupiedColor.withAlphaComponent(0.50)
                : hasWindows
                    ? spaceLaneOccupiedColor.withAlphaComponent(isCurrent ? 0.54 : 0.44)
            : isCurrent
                ? NSColor.labelColor.withAlphaComponent(0.34)
                : NSColor.separatorColor.withAlphaComponent(0.34)
        ).cgColor
        segment.containerLayer.backgroundColor = (isActive
            ? accentTintedWindowBackground(fraction: 0.10, alpha: 0.94)
            : isAssociated
                ? accentTintedWindowBackground(fraction: 0.07, alpha: 0.90)
            : spaceColor(for: segment.space)
        ).cgColor
        segment.containerLayer.shadowOpacity = isActive ? 0.26 : isCurrent ? 0.14 : 0
        segment.containerLayer.shadowRadius = isActive ? 24 : isCurrent ? 12 : 0
        segment.containerLayer.shadowOffset = CGSize(width: 0, height: isActive ? -6 : isCurrent ? -2.5 : 0)
        segment.containerLayer.zPosition = isActive ? 30 : isCurrent ? 10 : 0
        segment.containerLayer.transform = isActive
            ? CATransform3DMakeScale(1.03, 1.03, 1)
        segment.containerLayer.shadowOpacity = isActive ? 0.26 : isAssociated ? 0.18 : isCurrent ? 0.14 : 0
        segment.containerLayer.shadowRadius = isActive ? 24 : isAssociated ? 18 : isCurrent ? 12 : 0
        segment.containerLayer.shadowOffset = CGSize(
            width: 0,
            height: isActive ? -6 : isAssociated ? -4 : isCurrent ? -2.5 : 0
        )
        segment.containerLayer.zPosition = isActive ? 30 : isAssociated ? 18 : isCurrent ? 10 : 0
        segment.containerLayer.transform = (isActive || isAssociated)
            ? CATransform3DMakeScale(isActive ? 1.03 : 1.015, isActive ? 1.03 : 1.015, 1)
            : CATransform3DIdentity
        segment.containerLayer.shadowPath = CGPath(
            roundedRect: segment.containerLayer.bounds,
@@ -1944,9 +2237,17 @@
            y: locationInView.y - glassLayer.frame.minY - laneLayer.frame.minY - spaceLaneContentLayer.frame.minY
        )
        return spaceLaneSegments.first { segment in
            segment.containerLayer.frame.contains(locationInSpaceLaneContent)
        for group in spaceLaneDisplayGroups where group.containerLayer.frame.contains(locationInSpaceLaneContent) {
            let locationInGroup = CGPoint(
                x: locationInSpaceLaneContent.x - group.containerLayer.frame.minX,
                y: locationInSpaceLaneContent.y - group.containerLayer.frame.minY
            )
            if let segment = group.segments.first(where: { $0.containerLayer.frame.contains(locationInGroup) }) {
                return segment
        }
        }
        return nil
    }
    private func spaceLaneSegment(spaceID: UInt64) -> SpaceLaneSegmentLayers? {
@@ -2069,8 +2370,8 @@
    private func layoutAppShelfItemContents(_ item: AppShelfItemLayers, iconSize: CGFloat) {
        let bounds = item.containerLayer.bounds
        let isSelected = item.item.appGroupIndex == effectiveSelection?.appGroupIndex
        let showsSelectedVisual = shouldShowSelectedVisual(isSelected)
        let isHovered = item.item.appGroupIndex == effectiveHoveredAppGroupIndex
        let showsSelectedVisual = shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered)
        let isSpaceFocused = spaceFocusWindowCount(for: item.item.appGroupIndex) > 0
        let visualIconSize = appShelfVisualIconSize(
            forBaseIconSize: iconSize,
@@ -2174,15 +2475,52 @@
    }
    private var activeSpaceFocusID: UInt64? {
        hoveredSpaceLaneID
        guard case .space(let spaceID) = hoverTarget else { return nil }
        if hoverTargetSource == .debug {
            return spaceID
        }
        guard let window else { return nil }
        let currentMouseLocation = window.mouseLocationOutsideOfEventStream
        guard let segment = spaceLaneSegment(at: currentMouseLocation),
              segment.space.id == spaceID
        else {
            return nil
        }
        return spaceID
    }
    private var appHoverAssociatedSpaceIDs: Set<UInt64> {
        guard case .app(let appGroupIndex) = hoverTarget else { return [] }
        return Set(
            waterfallColumns
                .first { $0.column.appGroupIndex == appGroupIndex }?
                .cards
                .compactMap(\.item.primarySpaceID) ?? []
        )
    }
    private var appHoverAssociatedSpaceLabels: [String] {
        guard !appHoverAssociatedSpaceIDs.isEmpty else { return [] }
        return currentViewModel?.displays
            .flatMap(\.spaces)
            .filter { appHoverAssociatedSpaceIDs.contains($0.id) }
            .map(\.label)
            .sorted() ?? []
    }
    private var windowHoverAssociatedSpaceID: UInt64? {
        guard case .window(_, _, let spaceID) = hoverTarget else { return nil }
        return spaceID
    }
    private var isSpaceAssociationActive: Bool {
        activeSpaceFocusID != nil
    }
    private func shouldShowSelectedVisual(_ isSelected: Bool) -> Bool {
        isSelected && !isSpaceAssociationActive
    private func shouldShowSelectedVisual(isSelected: Bool, isHovered: Bool) -> Bool {
        isSelected && isHovered && !isSpaceAssociationActive
    }
    private func spaceFocusedAppGroupIndexes() -> [Int] {
@@ -2319,7 +2657,7 @@
        }
        let columnWidth = waterfallColumnWidth()
        let totalWidth = WaterfallMetrics.columnPadding * 2
        let totalWidth = WaterfallMetrics.contentHorizontalPadding * 2
            + CGFloat(waterfallColumns.count) * columnWidth
            + CGFloat(max(0, waterfallColumns.count - 1)) * WaterfallMetrics.gap
        waterfallContentWidth = max(waterfallLayer.bounds.width, totalWidth)
@@ -2331,7 +2669,7 @@
            height: waterfallLayer.bounds.height
        )
        var x = WaterfallMetrics.columnPadding
        var x = WaterfallMetrics.contentHorizontalPadding
        for column in waterfallColumns {
            column.containerLayer.frame = CGRect(
                x: x,
@@ -2347,6 +2685,7 @@
    private func layoutWaterfallColumn(_ column: WaterfallColumnLayers) {
        let bounds = column.containerLayer.bounds
        let isSpaceFocused = spaceFocusWindowCount(for: column.column.appGroupIndex) > 0
        let isHovered = column.column.appGroupIndex == effectiveHoveredAppGroupIndex
        column.containerLayer.borderColor = (isSpaceFocused
            ? NSColor.separatorColor.withAlphaComponent(0.38)
            : NSColor.separatorColor.withAlphaComponent(0.24)
@@ -2363,11 +2702,15 @@
            : NSColor.controlBackgroundColor.withAlphaComponent(0.52)
        ).cgColor
        column.appNameLayer.frame = CGRect(
            x: WaterfallMetrics.columnPadding,
            x: WaterfallMetrics.columnHeaderTextInset,
            y: bounds.height - 24,
            width: max(1, bounds.width - 64),
            width: max(1, bounds.width - WaterfallMetrics.columnHeaderTextInset * 2),
            height: 16
        )
        column.appNameLayer.foregroundColor = (isHovered
            ? spaceLaneOccupiedColor
            : NSColor.labelColor
        ).cgColor
        column.countLayer.frame = CGRect(
            x: bounds.width - 48,
            y: bounds.height - 24,
@@ -2382,10 +2725,10 @@
        )
        waterfallColumnScrollOffsets[appGroupIndex] = verticalOffset
        let cardWidth = max(1, bounds.width - WaterfallMetrics.columnPadding * 2)
        let cardWidth = max(1, bounds.width - WaterfallMetrics.columnHorizontalPadding * 2)
        let firstCardTop = bounds.height
            - WaterfallMetrics.headerHeight
            - WaterfallMetrics.columnPadding
            - WaterfallMetrics.columnVerticalPadding
            + verticalOffset
        for card in column.cards {
@@ -2394,7 +2737,7 @@
                - WaterfallMetrics.cardHeight
                - CGFloat(cardIndex) * (WaterfallMetrics.cardHeight + WaterfallMetrics.cardGap)
            card.containerLayer.frame = CGRect(
                x: WaterfallMetrics.columnPadding,
                x: WaterfallMetrics.columnHorizontalPadding,
                y: y,
                width: cardWidth,
                height: WaterfallMetrics.cardHeight
@@ -2407,9 +2750,13 @@
        let bounds = card.containerLayer.bounds
        let isSelected = card.item.window.id == effectiveSelection?.windowID
        let isHovered = card.item.window.id == hoveredWindowID
        applyWaterfallCardVisualState(card, selected: shouldShowSelectedVisual(isSelected), hovered: isHovered)
        applyWaterfallCardVisualState(
            card,
            selected: shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered),
            hovered: isHovered
        )
        let outerInset: CGFloat = 8
        let outerInset: CGFloat = 0
        let titleBarHeight: CGFloat = 26
        let titleGap: CGFloat = 6
        let titleBarFrame = CGRect(
@@ -2470,7 +2817,7 @@
    }
    private func applyWaterfallCardVisualState(_ card: WaterfallCardLayers, selected: Bool, hovered: Bool) {
        let isSpaceFocused = card.item.primarySpaceID == activeSpaceFocusID
        let isSpaceFocused = isWaterfallCardSpaceFocused(card)
        card.containerLayer.backgroundColor = (selected
            ? NSColor.windowBackgroundColor.withAlphaComponent(0.94)
            : hovered
@@ -2527,13 +2874,14 @@
        let size: CGFloat = isButtonHovered ? 24 : 18
        let buttonFill = isButtonHovered
            ? NSColor.systemRed.withAlphaComponent(0.94)
            : NSColor.labelColor.withAlphaComponent(0.24)
            : NSColor.white.withAlphaComponent(0.34)
        let buttonStroke = isButtonHovered
            ? NSColor.white.withAlphaComponent(0.76)
            : NSColor.white.withAlphaComponent(0.52)
            : NSColor.white.withAlphaComponent(0.46)
        let glyphStroke = isButtonHovered
            ? NSColor.white
            : NSColor.secondaryLabelColor.withAlphaComponent(0.92)
            : NSColor.white.withAlphaComponent(0.88)
        button.containerLayer.frame = CGRect(
            x: anchorTopRight.x - size / 2,
            y: anchorTopRight.y - size / 2,
@@ -2545,10 +2893,10 @@
        button.containerLayer.borderColor = buttonStroke.cgColor
        button.containerLayer.opacity = visible ? 1 : 0
        button.containerLayer.zPosition = isButtonHovered ? 80 : 60
        button.containerLayer.shadowOpacity = visible ? (isButtonHovered ? 0.22 : 0.07) : 0
        button.containerLayer.shadowOpacity = visible ? (isButtonHovered ? 0.22 : 0.04) : 0
        button.containerLayer.shadowRadius = isButtonHovered ? 9 : 5
        button.glyphLayer.frame = CGRect(x: 0, y: 0, width: size, height: size)
        button.glyphLayer.lineWidth = isButtonHovered ? 2.1 : 1.7
        button.glyphLayer.lineWidth = isButtonHovered ? 2.1 : 1.45
        button.glyphLayer.strokeColor = glyphStroke.cgColor
        button.glyphLayer.path = closeGlyphPath(size: size, hovered: isButtonHovered)
    }
@@ -2641,6 +2989,80 @@
        layers.confirmButtonTextLayer.frame = layers.confirmButtonLayer.bounds.insetBy(dx: 0, dy: 7)
    }
    private func setCloseFeedback(_ feedback: CloseFeedback, autoHideAfter delay: TimeInterval?) {
        closeFeedbackHideTask?.cancel()
        closeFeedback = feedback
        closeFeedbackLayer.isHidden = false
        closeFeedbackLayer.opacity = 1
        closeFeedbackTextLayer.string = feedback.message
        switch feedback.kind {
        case .progress:
            closeFeedbackLayer.backgroundColor = NSColor.windowBackgroundColor.withAlphaComponent(0.94).cgColor
            closeFeedbackLayer.borderColor = NSColor.separatorColor.withAlphaComponent(0.28).cgColor
            closeFeedbackTextLayer.foregroundColor = NSColor.secondaryLabelColor.withAlphaComponent(0.96).cgColor
        case .failure:
            closeFeedbackLayer.backgroundColor = NSColor.systemRed.withAlphaComponent(0.10).cgColor
            closeFeedbackLayer.borderColor = NSColor.systemRed.withAlphaComponent(0.32).cgColor
            closeFeedbackTextLayer.foregroundColor = NSColor.systemRed.withAlphaComponent(0.95).cgColor
        }
        needsLayout = true
        guard let delay else {
            closeFeedbackHideTask = nil
            return
        }
        closeFeedbackHideTask = Task { @MainActor [weak self] in
            try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
            guard !Task.isCancelled,
                  let self,
                  self.closeFeedback == feedback
            else {
                return
            }
            self.clearCloseFeedback()
        }
    }
    private func layoutCloseFeedback() {
        guard let feedback = closeFeedback else {
            closeFeedbackLayer.isHidden = true
            closeFeedbackLayer.opacity = 0
            return
        }
        closeFeedbackLayer.isHidden = false
        closeFeedbackLayer.opacity = 1
        let font = NSFont.systemFont(
            ofSize: 13,
            weight: feedback.kind == .failure ? .medium : .regular
        )
        closeFeedbackTextLayer.font = font
        closeFeedbackTextLayer.fontSize = 13
        let textWidth = (feedback.message as NSString).size(
            withAttributes: [.font: font]
        ).width
        let maxWidth = max(180, glassLayer.bounds.width - 48)
        let width = min(max(180, textWidth + 34), min(460, maxWidth))
        let height: CGFloat = 34
        let x = max(24, (glassLayer.bounds.width - width) / 2)
        let preferredY = waterfallLayer.frame.maxY - height - 14
        let y = min(
            max(16, preferredY),
            max(16, glassLayer.bounds.height - height - 16)
        )
        closeFeedbackLayer.frame = CGRect(x: x, y: y, width: width, height: height)
        closeFeedbackLayer.cornerRadius = height / 2
        closeFeedbackLayer.shadowPath = CGPath(
            roundedRect: closeFeedbackLayer.bounds,
            cornerWidth: closeFeedbackLayer.cornerRadius,
            cornerHeight: closeFeedbackLayer.cornerRadius,
            transform: nil
        )
        closeFeedbackTextLayer.frame = closeFeedbackLayer.bounds.insetBy(dx: 17, dy: 8)
    }
    private func closeConfirmationBubblePath(
        bounds: CGRect,
        contentFrame: CGRect,
@@ -2726,18 +3148,23 @@
        }
        pendingCloseTarget = nil
        pendingCloseRequest = nil
        pendingCloseOptOut = false
        closeConfirmationLayers.containerLayer.isHidden = true
        closeConfirmationArrowEdge = nil
        if pendingCloseOptOut {
            onCloseConfirmationDisabled?()
        }
        onRequestClose?(request)
    }
    private func beginCloseConfirmation(for target: CloseTarget) {
        if closeConfirmationRequired {
            guard let request = closeRequest(for: target) else {
                DevelopmentDiagnostics.log("quickSwitch.view.closeConfirmation.missingRequest", [
                    "targetKind": target.kindDescription
                ])
                return
            }
            pendingCloseTarget = target
            pendingCloseRequest = request
            pendingCloseOptOut = false
            DevelopmentDiagnostics.log("quickSwitch.view.closeConfirmation.show", [
                "targetKind": target.kindDescription
@@ -2750,23 +3177,30 @@
    }
    private func confirmPendingClose() {
        guard let target = pendingCloseTarget else { return }
        guard let target = pendingCloseTarget,
              let request = pendingCloseRequest
        else { return }
        let shouldDisableConfirmation = pendingCloseOptOut
        pendingCloseTarget = nil
        pendingCloseRequest = nil
        pendingCloseOptOut = false
        closeConfirmationLayers.containerLayer.isHidden = true
        closeConfirmationArrowEdge = nil
        if shouldDisableConfirmation {
        if shouldDisableConfirmation && closeConfirmationRequired {
            closeConfirmationRequired = false
            onCloseConfirmationDisabled?()
        }
        if let request = closeRequest(for: target) {
        DevelopmentDiagnostics.log("quickSwitch.view.closeConfirmation.confirm", [
            "targetKind": target.kindDescription,
            "disableFutureConfirmation": shouldDisableConfirmation
        ])
            onRequestClose?(request)
        }
    }
    private func cancelPendingClose() {
        DevelopmentDiagnostics.log("quickSwitch.view.closeConfirmation.cancel")
        pendingCloseTarget = nil
        pendingCloseRequest = nil
        pendingCloseOptOut = false
        closeConfirmationLayers.containerLayer.isHidden = true
        closeConfirmationArrowEdge = nil
@@ -2774,7 +3208,15 @@
    }
    private func toggleCloseConfirmationOptOut() {
        if pendingCloseOptOut && !closeConfirmationRequired {
            return
        }
        pendingCloseOptOut.toggle()
        if pendingCloseOptOut {
            closeConfirmationRequired = false
            onCloseConfirmationDisabled?()
            DevelopmentDiagnostics.log("quickSwitch.view.closeConfirmation.optOutPersisted")
        }
        needsLayout = true
    }
@@ -2941,13 +3383,18 @@
        return color.withAlphaComponent(alpha)
    }
    private func isWaterfallCardSpaceFocused(_ card: WaterfallCardLayers) -> Bool {
        guard let activeSpaceFocusID else { return false }
        return card.item.primarySpaceID == activeSpaceFocusID
    }
    private func waterfallColumnWidth() -> CGFloat {
        guard waterfallColumns.count > 0 else { return WaterfallMetrics.columnWidth }
        let visibleColumns = min(4, max(1, waterfallColumns.count))
        let fittingWidth = (
            waterfallLayer.bounds.width
                - WaterfallMetrics.columnPadding * 2
                - WaterfallMetrics.contentHorizontalPadding * 2
                - CGFloat(max(0, visibleColumns - 1)) * WaterfallMetrics.gap
        ) / CGFloat(visibleColumns)
        return max(
@@ -2975,7 +3422,7 @@
            1,
            waterfallLayer.bounds.height
                - WaterfallMetrics.headerHeight
                - WaterfallMetrics.columnPadding * 2
                - WaterfallMetrics.columnVerticalPadding * 2
        )
        let contentHeight = CGFloat(column.cards.count) * WaterfallMetrics.cardHeight
            + CGFloat(max(0, column.cards.count - 1)) * WaterfallMetrics.cardGap
@@ -3006,14 +3453,19 @@
    }
    private func spaceColor(for space: QuickSwitchSpaceViewModel) -> NSColor {
        if space.isCurrent {
            return NSColor.windowBackgroundColor.withAlphaComponent(0.76)
        }
        if space.windowCount > 0 {
            return NSColor.controlBackgroundColor.withAlphaComponent(0.56)
            return NSColor.controlBackgroundColor.withAlphaComponent(0.36)
        }
        return NSColor.controlBackgroundColor.withAlphaComponent(0.26)
        return NSColor.labelColor.withAlphaComponent(space.isCurrent ? 0.07 : 0.05)
    }
    private var spaceLaneOccupiedColor: NSColor {
        if effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua {
            return NSColor(calibratedRed: 10 / 255, green: 132 / 255, blue: 1, alpha: 1)
        }
        return NSColor(calibratedRed: 0, green: 122 / 255, blue: 1, alpha: 1)
    }
    private func makeFullscreenMarkerLayer(for space: QuickSwitchSpaceViewModel) -> CALayer? {
@@ -3179,10 +3631,9 @@
        return (0..<count).map { _ in
            let layer = CALayer()
            layer.cornerRadius = 3
            layer.backgroundColor = (space.type == .fullscreen
                ? NSColor.labelColor.withAlphaComponent(0.20)
                : NSColor.labelColor.withAlphaComponent(0.18)
            ).cgColor
            layer.backgroundColor = spaceLaneOccupiedColor
                .withAlphaComponent(space.isCurrent ? 0.72 : 0.58)
                .cgColor
            return layer
        }
    }
@@ -3238,9 +3689,9 @@
    private func fullscreenMarkerFrame(in segmentBounds: CGRect) -> CGRect {
        CGRect(
            x: 10,
            y: 30,
            y: 12,
            width: max(1, segmentBounds.width - 20),
            height: max(1, segmentBounds.height - 58)
            height: max(1, segmentBounds.height - 64)
        )
    }
@@ -3282,7 +3733,9 @@
                "closeButtonVisible": item.closeButton.containerLayer.opacity > 0,
                "closeButtonFrame": dictionary(from: item.closeButton.containerLayer.frame),
                "closeButtonSize": Double(item.closeButton.containerLayer.bounds.width),
                "closeButtonFillColor": colorDictionary(from: item.closeButton.containerLayer.backgroundColor)
                "closeButtonFillColor": colorDictionary(from: item.closeButton.containerLayer.backgroundColor),
                "closeButtonGlyphColor": colorDictionary(from: item.closeButton.glyphLayer.strokeColor),
                "closeButtonGlyphLineWidth": Double(item.closeButton.glyphLayer.lineWidth)
            ] as [String: Any]
        }
    }
@@ -3290,12 +3743,13 @@
    private func appShelfVisualStates(for item: AppShelfItemLayers) -> [String] {
        var states: [String] = []
        let isSelected = item.item.appGroupIndex == effectiveSelection?.appGroupIndex
        if shouldShowSelectedVisual(isSelected) {
        let isHovered = item.item.appGroupIndex == effectiveHoveredAppGroupIndex
        if shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered) {
            states.append("selected")
        } else if isSelected {
            states.append("selectedVisualSuppressed")
        }
        if item.item.appGroupIndex == effectiveHoveredAppGroupIndex {
        if isHovered {
            states.append("hover")
        }
        if spaceFocusWindowCount(for: item.item.appGroupIndex) > 0 {
@@ -3308,21 +3762,39 @@
        return states
    }
    private func waterfallColumnVisualStates(for column: WaterfallColumnLayers) -> [String] {
        var states: [String] = []
        if column.column.appGroupIndex == effectiveHoveredAppGroupIndex {
            states.append("hover")
        }
        if spaceFocusWindowCount(for: column.column.appGroupIndex) > 0 {
            states.append("spaceFocused")
        }
        return states
    }
    private func waterfallColumnReports() -> [[String: Any]] {
        waterfallColumns.map { column in
            let visibleFrame = column.containerLayer.frame.offsetBy(dx: waterfallContentLayer.frame.minX, dy: 0)
            let appGroupIndex = column.column.appGroupIndex
            let isHovered = appGroupIndex == effectiveHoveredAppGroupIndex
            return [
                "appGroupIndex": appGroupIndex,
                "appName": column.column.app.name,
                "bundleIdentifier": column.column.app.bundleIdentifier,
                "windowCount": column.column.windows.count,
                "isHovered": isHovered,
                "visualStates": waterfallColumnVisualStates(for: column),
                "spaceFocusWindowCount": spaceFocusWindowCount(for: appGroupIndex),
                "isSpaceFocused": spaceFocusWindowCount(for: appGroupIndex) > 0,
                "spaceFocusDirection": spaceFocusDirection(for: appGroupIndex),
                "frame": dictionary(from: column.containerLayer.frame),
                "visibleFrame": dictionary(from: visibleFrame),
                "appNameFrame": dictionary(from: column.appNameLayer.frame),
                "appNameAlignment": column.appNameLayer.alignmentMode.rawValue,
                "appNameColor": colorDictionary(from: column.appNameLayer.foregroundColor),
                "countFrame": dictionary(from: column.countLayer.frame),
                "verticalScrollOffset": Double(waterfallColumnScrollOffsets[appGroupIndex] ?? 0),
                "maxVerticalScrollOffset": Double(waterfallMaxVerticalScrollOffset(for: appGroupIndex)),
                "cards": waterfallCardReports(for: column)
@@ -3418,6 +3890,8 @@
                "closeButtonFrame": dictionary(from: card.closeButton.containerLayer.frame),
                "closeButtonSize": Double(card.closeButton.containerLayer.bounds.width),
                "closeButtonFillColor": colorDictionary(from: card.closeButton.containerLayer.backgroundColor),
                "closeButtonGlyphColor": colorDictionary(from: card.closeButton.glyphLayer.strokeColor),
                "closeButtonGlyphLineWidth": Double(card.closeButton.glyphLayer.lineWidth),
                "shineVisible": card.shineLayer.opacity > 0,
                "zPosition": Double(card.containerLayer.zPosition),
                "frame": dictionary(from: card.containerLayer.frame),
@@ -3513,15 +3987,16 @@
    private func waterfallCardVisualStates(for card: WaterfallCardLayers) -> [String] {
        var states: [String] = []
        let isSelected = card.item.window.id == effectiveSelection?.windowID
        if shouldShowSelectedVisual(isSelected) {
        let isHovered = card.item.window.id == hoveredWindowID
        if shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered) {
            states.append("selected")
        } else if isSelected {
            states.append("selectedVisualSuppressed")
        }
        if card.item.window.id == hoveredWindowID {
        if isHovered {
            states.append("hover")
        }
        if card.item.primarySpaceID == activeSpaceFocusID {
        if isWaterfallCardSpaceFocused(card) {
            states.append("spaceFocused")
        }
        if card.item.window.isMinimized {
@@ -3537,16 +4012,47 @@
        window?.backingScaleFactor ?? NSScreen.main?.backingScaleFactor ?? 2
    }
    private func spaceLaneDisplayGroupReports() -> [[String: Any]] {
        spaceLaneDisplayGroups.map { group in
            let visibleFrame = group.containerLayer.frame.offsetBy(dx: spaceLaneContentLayer.frame.minX, dy: 0)
            return [
                "displayUUID": group.display.displayUUID,
                "displayIndex": group.display.displayIndex,
                "displayLabel": group.display.displayLabel,
                "physical": group.display.physical,
                "spaceCount": group.segments.count,
                "spaceIDs": group.segments.map(\.space.id),
                "spaceLabels": group.segments.map(\.space.label),
                "frame": dictionary(from: group.containerLayer.frame),
                "visibleFrame": dictionary(from: visibleFrame),
                "backgroundColor": colorDictionary(from: group.containerLayer.backgroundColor),
                "borderColor": colorDictionary(from: group.containerLayer.borderColor),
                "borderWidth": Double(group.containerLayer.borderWidth),
                "shadowOpacity": Double(group.containerLayer.shadowOpacity),
                "shadowRadius": Double(group.containerLayer.shadowRadius),
                "groupGap": Double(SpaceLaneMetrics.groupGap),
                "segmentGap": Double(SpaceLaneMetrics.segmentGap)
            ] as [String: Any]
        }
    }
    private func spaceLaneSegmentReports() -> [[String: Any]] {
        currentViewModel?.displays.flatMap { display in
            display.spaces.compactMap { space in
                guard let segment = spaceLaneSegments.first(where: { $0.space.id == space.id }) else {
                    return nil
                }
                let visibleFrame = segment.containerLayer.frame.offsetBy(dx: spaceLaneContentLayer.frame.minX, dy: 0)
                let isAppAssociated = appHoverAssociatedSpaceIDs.contains(space.id)
                let isWindowAssociated = space.id == windowHoverAssociatedSpaceID
                let group = spaceLaneDisplayGroup(containingSpaceID: space.id)
                let visibleFrame = segment.containerLayer.frame.offsetBy(
                    dx: (group?.containerLayer.frame.minX ?? 0) + spaceLaneContentLayer.frame.minX,
                    dy: group?.containerLayer.frame.minY ?? 0
                )
                return [
                    "displayUUID": display.displayUUID,
                    "displayLabel": display.displayLabel,
                    "spaceID": space.id,
                    "label": space.label,
                    "type": spaceTypeName(space.type),
                    "visualStates": visualStates(for: space),
@@ -3554,15 +4060,21 @@
                    "fullscreenMarkerKind": segment.fullscreenMarkerLayer == nil ? "none" : "cornerBracket",
                    "fullscreenMarkerFrame": segment.fullscreenMarkerLayer.map { dictionary(from: $0.frame) } ?? NSNull(),
                    "windowBlockCount": segment.windowBlocks.count,
                    "windowBlockColors": segment.windowBlocks.map { colorDictionary(from: $0.backgroundColor) },
                    "windowBlockFrames": segment.windowBlocks.map { dictionary(from: $0.frame) },
                    "backgroundColor": colorDictionary(from: segment.containerLayer.backgroundColor),
                    "borderColor": colorDictionary(from: segment.containerLayer.borderColor),
                    "borderWidth": Double(segment.containerLayer.borderWidth),
                    "shadowOpacity": Double(segment.containerLayer.shadowOpacity),
                    "shadowRadius": Double(segment.containerLayer.shadowRadius),
                    "appFrame": dictionary(from: segment.appLayer.frame),
                    "frame": dictionary(from: segment.containerLayer.frame),
                    "visibleFrame": dictionary(from: visibleFrame),
                    "isCurrent": space.isCurrent,
                    "isHovered": space.id == hoveredSpaceID,
                    "isHovered": space.id == activeSpaceFocusID,
                    "isFocused": space.id == activeSpaceFocusID,
                    "isAppAssociated": isAppAssociated,
                    "isWindowAssociated": isWindowAssociated,
                    "windowCount": space.windowCount,
                    "appCount": space.appCount,
                    "appNames": space.appNames
@@ -3571,12 +4083,24 @@
        } ?? []
    }
    private func spaceLaneDisplayGroup(containingSpaceID spaceID: UInt64) -> SpaceLaneDisplayGroupLayers? {
        spaceLaneDisplayGroups.first { group in
            group.segments.contains { $0.space.id == spaceID }
        }
    }
    private func visualStates(for space: QuickSwitchSpaceViewModel) -> [String] {
        var states: [String] = []
        if space.isCurrent {
            states.append("current")
        }
        if space.id == hoveredSpaceID {
        if appHoverAssociatedSpaceIDs.contains(space.id) {
            states.append("appAssociated")
        }
        if space.id == windowHoverAssociatedSpaceID {
            states.append("windowAssociated")
        }
        if space.id == activeSpaceFocusID {
            states.append("hover")
        }
        if space.id == activeSpaceFocusID {