Ariver
2026-06-18 1fcf6730652d509e78de4b80888772083f5b124a
C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -24,9 +24,8 @@
    private lazy var closeConfirmationLayers = makeCloseConfirmationLayers()
    private let closeFeedbackLayer = CALayer()
    private let closeFeedbackTextLayer = CATextLayer()
    private let settingsButtonLayer = CALayer()
    private let settingsButtonIconLayer = CALayer()
    private let settingsButtonFallbackTextLayer = CATextLayer()
    private let viewModeToggleButtonLayer = CALayer()
    private let viewModeToggleIconLayer = CALayer()
    private let appIconProvider: any AppIconProviderProtocol = WorkspaceAppIconProvider()
    private let skeletonThumbnailProvider = NativeSkeletonThumbnailProvider(size: NSSize(width: 160, height: 96))
    private var screenshotSourcesByWindowID: [UInt32: ScreenshotResolutionSource] = [:]
@@ -52,7 +51,8 @@
    private var closeConfirmationRequired = true
    private var closeFeedback: CloseFeedback?
    private var closeFeedbackHideTask: Task<Void, Never>?
    private var isSettingsButtonHovered = false
    private var isViewModeToggleButtonHovered = false
    private var suppressViewModeToggleHoverPreview = false
    private var hoverTarget: HoverTarget = .none
    private var hoverTargetSource: HoverTargetSource = .event
    private var hoveredSpaceLaneID: UInt64?
@@ -95,7 +95,7 @@
    var onCommitSelection: ((QuickSwitchSelection, QuickSwitchCommitSource) -> Void)?
    var onSpaceLaneClick: ((UInt64, Int) -> Void)?
    var onBackgroundClick: (() -> Bool)?
    var onOpenSettings: (() -> Void)?
    var onToggleWaterfallViewMode: (() -> Void)?
    var onRequestClose: ((QuickSwitchCloseRequest) -> Void)?
    var onCloseConfirmationDisabled: (() -> Void)?
@@ -241,6 +241,7 @@
        let item: QuickSwitchAppShelfItemViewModel
        let containerLayer: CALayer
        let backgroundLayer: CALayer
        let indexLayer: CATextLayer
        let iconLayer: CALayer
        let labelLayer: CATextLayer
        let badgeLayer: CATextLayer
@@ -299,6 +300,10 @@
        static let normalGap: CGFloat = 16
        static let minGap: CGFloat = 8
        static let maxRows = 2
        static let indexFontSize: CGFloat = 16
        static let compressedIndexFontSize: CGFloat = 14
        static let indexWidth: CGFloat = 18
        static let indexHeight: CGFloat = 24
    }
    private enum RootLayoutMetrics {
@@ -307,6 +312,24 @@
        static let shelfGap: CGFloat = 16
        static let waterfallTopGap: CGFloat = 14
        static let bottomMargin: CGFloat = 34
    }
    private enum ViewModeToggleMetrics {
        static let hitSize: CGFloat = 34
        static let visualSize: CGFloat = 30
        static let cornerRadius: CGFloat = 9
        static let iconInset: CGFloat = 7
        static let glyphLineWidth: CGFloat = 1.65
        static let glyphCornerRadius: CGFloat = 2.1
        static let selectedShadowRadius: CGFloat = 10
        static let idleShadowRadius: CGFloat = 5
    }
    private enum ViewModeToggleVisualState: String {
        case horizontalAvailable = "H1"
        case horizontalSelected = "H2"
        case verticalAvailable = "Z1"
        case verticalSelected = "Z2"
    }
    private enum WaterfallMetrics {
@@ -485,12 +508,16 @@
    override func mouseMoved(with event: NSEvent) {
        guard debugHoveredAppGroupIndex == nil else { return }
        let settingsHovered = settingsButtonContains(windowLocation: event.locationInWindow)
        if isSettingsButtonHovered != settingsHovered {
            isSettingsButtonHovered = settingsHovered
        let toggleHovered = viewModeToggleButtonContains(windowLocation: event.locationInWindow)
        if !toggleHovered {
            suppressViewModeToggleHoverPreview = false
        }
        let shouldShowToggleHoverPreview = toggleHovered && !suppressViewModeToggleHoverPreview
        if isViewModeToggleButtonHovered != shouldShowToggleHoverPreview {
            isViewModeToggleButtonHovered = shouldShowToggleHoverPreview
            needsLayout = true
        }
        if settingsHovered {
        if toggleHovered {
            hoveredCloseTarget = nil
            setHoverTarget(.none)
            return
@@ -506,7 +533,8 @@
    override func mouseExited(with event: NSEvent) {
        guard debugHoveredAppGroupIndex == nil else { return }
        isSettingsButtonHovered = false
        isViewModeToggleButtonHovered = false
        suppressViewModeToggleHoverPreview = false
        hoveredCloseTarget = nil
        setHoverTarget(.none)
    }
@@ -525,8 +553,8 @@
            return
        }
        if settingsButtonContains(windowLocation: event.locationInWindow) {
            clickSettingsButton(source: "mouse")
        if viewModeToggleButtonContains(windowLocation: event.locationInWindow) {
            clickViewModeToggleButton(source: "mouse")
            return
        }
@@ -728,11 +756,38 @@
        apply(viewModel: viewModel, preservingInteractionState: true, animatedProjection: true)
    }
    func hoverNextAppGroupIndex(cycleForward: Bool = true) -> Bool {
        guard !appShelfItems.isEmpty else {
            return false
        }
        let orderedIndexes = appShelfItems.map(\.item.appGroupIndex)
        let sortedIndexes = orderedIndexes
        var currentIndexPosition: Int?
        if let currentHovered = effectiveHoveredAppGroupIndex {
            currentIndexPosition = sortedIndexes.firstIndex(of: currentHovered)
        }
        let nextPosition = currentIndexPosition.map { current in
            if cycleForward {
                (current + 1) % sortedIndexes.count
            } else {
                (current - 1 + sortedIndexes.count) % sortedIndexes.count
            }
        } ?? 0
        let nextAppGroupIndex = sortedIndexes[nextPosition]
        hoverAppGroup(nextAppGroupIndex)
        return true
    }
    private func apply(
        viewModel: QuickSwitchViewModel?,
        preservingInteractionState: Bool,
        animatedProjection: Bool
    ) {
        let preservedWindowThumbnails = preservingInteractionState ? cachedThumbnailsByWindowID() : [:]
        let transitionSnapshot = animatedProjection ? captureProjectionTransitionSnapshot() : nil
        if !animatedProjection {
            resetProjectionTransitionReport()
@@ -793,12 +848,35 @@
        rebuildSpaceLaneSegments()
        rebuildAppShelfItems()
        rebuildWaterfallColumns()
        if preservingInteractionState {
            restoreThumbnailsFromCache(preservedWindowThumbnails)
        }
        needsLayout = true
        ensureCurrentSelectionVisible(horizontalIntent: .alignWithAppShelf(animated: false))
        needsLayout = true
        if animatedProjection, let transitionSnapshot {
            layoutSubtreeIfNeeded()
            applyProjectionTransitionAnimations(from: transitionSnapshot)
        }
    }
    private func cachedThumbnailsByWindowID() -> [UInt32: Any] {
        var snapshots: [UInt32: Any] = [:]
        for card in waterfallCardsByWindowID().values {
            snapshots[card.item.window.id] = card.thumbnailLayer.contents
        }
        return snapshots
    }
    private func restoreThumbnailsFromCache(_ cachedThumbnails: [UInt32: Any]) {
        for card in waterfallCardsByWindowID().values {
            if let thumbnail = cachedThumbnails[card.item.window.id] {
                card.thumbnailLayer.contents = thumbnail
            }
        }
    }
@@ -1081,9 +1159,20 @@
            "activeSpaceFocusID": activeSpaceFocusID ?? NSNull(),
            "spaceFilterLockedSpaceID": currentViewModel?.lockedSpaceID ?? NSNull(),
            "spaceFilterActive": currentViewModel?.lockedSpaceID != nil,
            "settingsButtonVisible": !settingsButtonLayer.isHidden,
            "settingsButtonHovered": isSettingsButtonHovered,
            "settingsButtonFrame": dictionary(from: settingsButtonLayer.frame),
            "viewModeToggleButtonVisible": !viewModeToggleButtonLayer.isHidden,
            "viewModeToggleButtonHovered": isViewModeToggleButtonHovered,
            "viewModeToggleHoverPreviewSuppressed": suppressViewModeToggleHoverPreview,
            "viewModeToggleButtonFrame": dictionary(from: viewModeToggleButtonLayer.frame),
            "viewModeToggleHitSize": Double(ViewModeToggleMetrics.hitSize),
            "viewModeToggleVisualSize": Double(ViewModeToggleMetrics.visualSize),
            "viewModeToggleCurrentMode": waterfallViewMode.rawValue,
            "viewModeToggleTargetMode": nextWaterfallViewMode.rawValue,
            "viewModeToggleIconKind": viewModeToggleIconKind,
            "viewModeToggleVisualState": viewModeToggleVisualState.rawValue,
            "viewModeToggleStandardBlueColor": colorDictionary(from: viewModeToggleBlueColor.cgColor),
            "viewModeToggleGlyphLineWidth": Double(ViewModeToggleMetrics.glyphLineWidth),
            "viewModeToggleAccessibilityLabel": viewModeToggleAccessibilityLabel,
            "settingsButtonVisible": false,
            "spaceFocusAppGroupIndexes": spaceFocusedAppGroupIndexes(),
            "appHoverAssociatedSpaceIDs": appHoverAssociatedSpaceIDs.sorted(),
            "appHoverAssociatedSpaceLabels": appHoverAssociatedSpaceLabels,
@@ -1326,9 +1415,15 @@
        case "click-background":
            recordMouseCommand("click-background")
            _ = onBackgroundClick?()
        case "click-settings":
            recordMouseCommand("click-settings")
            clickSettingsButton(source: "debug")
        case "hover-view-toggle":
            recordMouseCommand("hover-view-toggle")
            suppressViewModeToggleHoverPreview = false
            isViewModeToggleButtonHovered = true
            hoveredCloseTarget = nil
            setHoverTarget(.none, source: .debug)
        case "click-view-toggle", "click-settings":
            recordMouseCommand("click-view-toggle")
            clickViewModeToggleButton(source: "debug")
        case "hover-card" where parts.count == 3:
            guard let appGroupIndex = Int(parts[1]), let windowIndex = Int(parts[2]) else {
                recordMouseCommand("invalid:\(command)")
@@ -2281,25 +2376,15 @@
        closeFeedbackLayer.addSublayer(closeFeedbackTextLayer)
        glassLayer.addSublayer(closeFeedbackLayer)
        settingsButtonLayer.cornerRadius = 10
        settingsButtonLayer.masksToBounds = false
        settingsButtonLayer.borderWidth = 1
        settingsButtonLayer.shadowColor = NSColor.black.cgColor
        settingsButtonLayer.shadowOffset = CGSize(width: 0, height: -2)
        settingsButtonLayer.zPosition = 170
        settingsButtonIconLayer.contentsGravity = .resizeAspect
        settingsButtonIconLayer.contentsScale = backingScaleFactor
        settingsButtonIconLayer.contents = settingsIconImage()
        settingsButtonFallbackTextLayer.contentsScale = backingScaleFactor
        settingsButtonFallbackTextLayer.string = "⚙"
        settingsButtonFallbackTextLayer.font = NSFont.systemFont(ofSize: 18, weight: .regular)
        settingsButtonFallbackTextLayer.fontSize = 18
        settingsButtonFallbackTextLayer.alignmentMode = .center
        settingsButtonFallbackTextLayer.foregroundColor = NSColor.secondaryLabelColor.cgColor
        settingsButtonFallbackTextLayer.isHidden = settingsButtonIconLayer.contents != nil
        settingsButtonLayer.addSublayer(settingsButtonIconLayer)
        settingsButtonLayer.addSublayer(settingsButtonFallbackTextLayer)
        glassLayer.addSublayer(settingsButtonLayer)
        viewModeToggleButtonLayer.cornerRadius = 10
        viewModeToggleButtonLayer.masksToBounds = false
        viewModeToggleButtonLayer.borderWidth = 1
        viewModeToggleButtonLayer.shadowColor = NSColor.black.cgColor
        viewModeToggleButtonLayer.shadowOffset = CGSize(width: 0, height: -2)
        viewModeToggleButtonLayer.zPosition = 170
        viewModeToggleIconLayer.contentsScale = backingScaleFactor
        viewModeToggleButtonLayer.addSublayer(viewModeToggleIconLayer)
        glassLayer.addSublayer(viewModeToggleButtonLayer)
    }
    private func rebuildAppShelfItems() {
@@ -2307,7 +2392,7 @@
            item.containerLayer.removeFromSuperlayer()
        }
        appShelfItems = (currentViewModel?.appShelf ?? []).map { item in
        appShelfItems = (currentViewModel?.appShelf ?? []).enumerated().map { ordinal, item in
            let container = CALayer()
            container.name = item.app.name
@@ -2323,6 +2408,18 @@
            icon.contents = appIconProvider.icon(for: item.app)
            icon.cornerRadius = 12
            icon.masksToBounds = false
            let indexLabel = makeTextLayer(
                string: appShelfIndexText(forOrdinal: ordinal),
                fontSize: AppShelfMetrics.indexFontSize,
                weight: .regular,
                color: appShelfIndexColor,
                alignment: .center
            )
            indexLabel.font = appShelfIndexFont(size: AppShelfMetrics.indexFontSize)
            indexLabel.fontSize = AppShelfMetrics.indexFontSize
            indexLabel.truncationMode = .end
            indexLabel.zPosition = 14
            let label = makeTextLayer(
                string: item.app.name,
@@ -2348,6 +2445,7 @@
            let closeButton = makeCloseButtonLayers()
            container.addSublayer(background)
            container.addSublayer(indexLabel)
            container.addSublayer(icon)
            container.addSublayer(label)
            container.addSublayer(badge)
@@ -2358,6 +2456,7 @@
                item: item,
                containerLayer: container,
                backgroundLayer: background,
                indexLayer: indexLabel,
                iconLayer: icon,
                labelLayer: label,
                badgeLayer: badge,
@@ -2650,13 +2749,13 @@
        layoutWaterfallColumns()
        layoutCloseConfirmation()
        layoutCloseFeedback()
        layoutSettingsButton()
        layoutViewModeToggleButton()
        CATransaction.commit()
    }
    private func layoutSettingsButton() {
        let size: CGFloat = 34
    private func layoutViewModeToggleButton() {
        let size = ViewModeToggleMetrics.visualSize
        let topMargin = topSafeAreaInset + RootLayoutMetrics.topContentMargin
        let x = max(
            RootLayoutMetrics.horizontalMargin,
@@ -2666,24 +2765,144 @@
            RootLayoutMetrics.bottomMargin,
            glassLayer.bounds.height - topMargin - size
        )
        settingsButtonLayer.frame = CGRect(x: x, y: y, width: size, height: size)
        settingsButtonLayer.backgroundColor = NSColor.windowBackgroundColor
            .withAlphaComponent(isSettingsButtonHovered ? 0.78 : 0.54)
            .cgColor
        settingsButtonLayer.borderColor = NSColor.separatorColor
            .withAlphaComponent(isSettingsButtonHovered ? 0.42 : 0.26)
            .cgColor
        settingsButtonLayer.shadowOpacity = isSettingsButtonHovered ? 0.14 : 0.08
        settingsButtonLayer.shadowRadius = isSettingsButtonHovered ? 9 : 6
        settingsButtonIconLayer.contentsScale = backingScaleFactor
        settingsButtonFallbackTextLayer.contentsScale = backingScaleFactor
        let iconInset: CGFloat = 8
        settingsButtonIconLayer.frame = settingsButtonLayer.bounds.insetBy(dx: iconInset, dy: iconInset)
        settingsButtonFallbackTextLayer.frame = settingsButtonLayer.bounds.insetBy(dx: 0, dy: 6)
        settingsButtonFallbackTextLayer.foregroundColor = (isSettingsButtonHovered
            ? NSColor.labelColor
            : NSColor.secondaryLabelColor
        viewModeToggleButtonLayer.frame = CGRect(x: x, y: y, width: size, height: size)
        viewModeToggleButtonLayer.cornerRadius = ViewModeToggleMetrics.cornerRadius
        let visualState = viewModeToggleVisualState
        let isSelectedState = viewModeToggleVisualStateIsSelected(visualState)
        viewModeToggleButtonLayer.backgroundColor = (isSelectedState
            ? viewModeToggleBlueColor
            : NSColor.windowBackgroundColor.withAlphaComponent(isViewModeToggleButtonHovered ? 0.76 : 0.58)
        ).cgColor
        viewModeToggleButtonLayer.borderColor = (isSelectedState
            ? viewModeToggleBlueColor.withAlphaComponent(0.96)
            : viewModeToggleBlueColor.withAlphaComponent(isViewModeToggleButtonHovered ? 0.72 : 0.34)
        ).cgColor
        viewModeToggleButtonLayer.shadowOpacity = isSelectedState ? 0.16 : (isViewModeToggleButtonHovered ? 0.11 : 0.06)
        viewModeToggleButtonLayer.shadowRadius = isSelectedState
            ? ViewModeToggleMetrics.selectedShadowRadius
            : ViewModeToggleMetrics.idleShadowRadius
        viewModeToggleIconLayer.contentsScale = backingScaleFactor
        let iconInset = ViewModeToggleMetrics.iconInset
        viewModeToggleIconLayer.frame = viewModeToggleButtonLayer.bounds.insetBy(dx: iconInset, dy: iconInset)
        layoutViewModeToggleIcon()
    }
    private func layoutViewModeToggleIcon() {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        viewModeToggleIconLayer.sublayers?.forEach { $0.removeFromSuperlayer() }
        let bounds = viewModeToggleIconLayer.bounds
        guard bounds.width > 0, bounds.height > 0 else {
            CATransaction.commit()
            return
        }
        let visualState = viewModeToggleVisualState
        let isSelectedState = viewModeToggleVisualStateIsSelected(visualState)
        let strokeColor = isSelectedState
            ? NSColor.white.withAlphaComponent(0.98)
            : viewModeToggleBlueColor.withAlphaComponent(0.96)
        let fillColor = isSelectedState
            ? NSColor.white.withAlphaComponent(0.22)
            : viewModeToggleBlueColor.withAlphaComponent(0.06)
        let glyphRect = centeredSquare(in: bounds)
        switch visualState {
        case .verticalAvailable, .verticalSelected:
            addVerticalColumnsToggleGlyph(
                in: glyphRect,
                strokeColor: strokeColor,
                fillColor: fillColor
            )
        case .horizontalAvailable, .horizontalSelected:
            addHorizontalMasonryToggleGlyph(
                in: glyphRect,
                strokeColor: strokeColor,
                fillColor: fillColor
            )
        }
        CATransaction.commit()
    }
    private func addVerticalColumnsToggleGlyph(
        in rect: CGRect,
        strokeColor: NSColor,
        fillColor: NSColor
    ) {
        let columnWidth = rect.width * 0.22
        let gap = (rect.width - columnWidth * 3) / 2
        for index in 0..<3 {
            let x = rect.minX + CGFloat(index) * (columnWidth + gap)
            let columnRect = CGRect(x: x, y: rect.minY, width: columnWidth, height: rect.height)
            addViewModeToggleRoundedRect(
                columnRect,
                cornerRadius: ViewModeToggleMetrics.glyphCornerRadius,
                strokeColor: strokeColor,
                fillColor: fillColor,
                lineWidth: ViewModeToggleMetrics.glyphLineWidth
            )
        }
    }
    private func addHorizontalMasonryToggleGlyph(
        in rect: CGRect,
        strokeColor: NSColor,
        fillColor: NSColor
    ) {
        let cellSize = rect.width * 0.38
        let gap = rect.width - cellSize * 2
        for row in 0..<2 {
            for column in 0..<2 {
                let cellRect = CGRect(
                    x: rect.minX + CGFloat(column) * (cellSize + gap),
                    y: rect.minY + CGFloat(row) * (cellSize + gap),
                    width: cellSize,
                    height: cellSize
                )
                addViewModeToggleRoundedRect(
                    cellRect,
                    cornerRadius: ViewModeToggleMetrics.glyphCornerRadius,
                    strokeColor: strokeColor,
                    fillColor: fillColor,
                    lineWidth: ViewModeToggleMetrics.glyphLineWidth
                )
            }
        }
    }
    private func addViewModeToggleRoundedRect(
        _ rect: CGRect,
        cornerRadius: CGFloat,
        strokeColor: NSColor,
        fillColor: NSColor,
        lineWidth: CGFloat
    ) {
        let layer = CAShapeLayer()
        layer.contentsScale = backingScaleFactor
        layer.frame = viewModeToggleIconLayer.bounds
        layer.path = CGPath(
            roundedRect: rect,
            cornerWidth: cornerRadius,
            cornerHeight: cornerRadius,
            transform: nil
        )
        layer.fillColor = fillColor.cgColor
        layer.strokeColor = strokeColor.cgColor
        layer.lineWidth = lineWidth
        layer.lineJoin = .round
        viewModeToggleIconLayer.addSublayer(layer)
    }
    private func centeredSquare(in rect: CGRect) -> CGRect {
        let side = min(rect.width, rect.height)
        return CGRect(
            x: rect.midX - side / 2,
            y: rect.midY - side / 2,
            width: side,
            height: side
        )
    }
    private var topSafeAreaInset: CGFloat {
@@ -3035,24 +3254,31 @@
        spaceLaneSegments.first { $0.space.id == spaceID }
    }
    private func settingsButtonContains(windowLocation: CGPoint) -> Bool {
    private func viewModeToggleButtonContains(windowLocation: CGPoint) -> Bool {
        let locationInView = convert(windowLocation, from: nil)
        let locationInGlass = CGPoint(
            x: locationInView.x - glassLayer.frame.minX,
            y: locationInView.y - glassLayer.frame.minY
        )
        return settingsButtonLayer.frame.contains(locationInGlass)
        let hitOutset = (ViewModeToggleMetrics.hitSize - ViewModeToggleMetrics.visualSize) / 2
        return viewModeToggleButtonLayer.frame
            .insetBy(dx: -hitOutset, dy: -hitOutset)
            .contains(locationInGlass)
    }
    private func clickSettingsButton(source: String) {
        DevelopmentDiagnostics.log("quickSwitch.view.clickSettings", [
            "source": source
    private func clickViewModeToggleButton(source: String) {
        DevelopmentDiagnostics.log("quickSwitch.view.clickWaterfallViewModeToggle", [
            "source": source,
            "currentMode": waterfallViewMode.rawValue,
            "targetMode": nextWaterfallViewMode.rawValue
        ])
        lastMouseCommand = "click-settings"
        if !mouseCommandsApplied.contains("click-settings") {
            mouseCommandsApplied.append("click-settings")
        lastMouseCommand = "click-view-toggle"
        if !mouseCommandsApplied.contains("click-view-toggle") {
            mouseCommandsApplied.append("click-view-toggle")
        }
        onOpenSettings?()
        isViewModeToggleButtonHovered = false
        suppressViewModeToggleHoverPreview = true
        onToggleWaterfallViewMode?()
    }
    private func appShelfItem(at windowLocation: CGPoint) -> AppShelfItemLayers? {
@@ -3264,6 +3490,13 @@
            )
            : nil
        item.iconLayer.frame = CGRect(x: iconX, y: iconY, width: visualIconSize, height: visualIconSize)
        layoutAppShelfIndexLayer(
            item.indexLayer,
            iconFrame: item.iconLayer.frame,
            iconSize: visualIconSize,
            itemBounds: bounds,
            highlighted: showsSelectedVisual || isHovered
        )
        item.labelLayer.contentsScale = backingScaleFactor
        item.labelLayer.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 14)
        item.badgeLayer.string = badgeText
@@ -3289,6 +3522,67 @@
        )
        item.selectedIndicatorLayer.frame = .zero
        item.selectedIndicatorLayer.backgroundColor = NSColor.clear.cgColor
    }
    private var appShelfIndexColor: NSColor {
        NSColor.labelColor.withAlphaComponent(0.48)
    }
    private var appShelfIndexHighlightedColor: NSColor {
        NSColor.labelColor.withAlphaComponent(0.58)
    }
    private func appShelfIndexFont(size: CGFloat) -> NSFont {
        for fontName in ["Bradley Hand ITC", "Noteworthy-Light", "Marker Felt", "Snell Roundhand"] {
            if let font = NSFont(name: fontName, size: size) {
                return font
            }
        }
        return NSFont.monospacedSystemFont(ofSize: size, weight: .regular)
    }
    private func appShelfIndexText(forOrdinal ordinal: Int) -> String {
        let symbols = Array("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ")
        guard ordinal >= 0 else { return "" }
        if ordinal < symbols.count {
            return String(symbols[ordinal])
        }
        let overflowIndex = ordinal - symbols.count
        let prefix = symbols[10 + (overflowIndex / 26) % 26]
        let suffix = symbols[10 + overflowIndex % 26]
        return "\(prefix)\(suffix)"
    }
    private func layoutAppShelfIndexLayer(
        _ layer: CATextLayer,
        iconFrame: CGRect,
        iconSize: CGFloat,
        itemBounds: CGRect,
        highlighted: Bool
    ) {
        let fontSize = iconSize <= AppShelfMetrics.minIconSize + 1
            ? AppShelfMetrics.compressedIndexFontSize
            : AppShelfMetrics.indexFontSize
        layer.contentsScale = backingScaleFactor
        layer.font = appShelfIndexFont(size: fontSize)
        layer.fontSize = fontSize
        layer.foregroundColor = (highlighted ? appShelfIndexHighlightedColor : appShelfIndexColor).cgColor
        layer.opacity = 1
        let indexWidth = AppShelfMetrics.indexWidth
        let indexHeight = AppShelfMetrics.indexHeight
        let preferredX = iconFrame.minX - indexWidth - 2
        let clampedX = max(-7, min(itemBounds.width - indexWidth, preferredX))
        let preferredY = iconFrame.maxY - indexHeight + 6
        let clampedY = max(0, min(itemBounds.height - indexHeight, preferredY))
        layer.frame = CGRect(
            x: clampedX,
            y: clampedY,
            width: indexWidth,
            height: indexHeight
        )
    }
    private func appShelfRows() -> [[AppShelfItemLayers]] {
@@ -3338,6 +3632,14 @@
                .cards
                .compactMap(\.item.primarySpaceID) ?? []
        )
    }
    private var appIconHoverLinkedAppGroupIndex: Int? {
        if let debugHoveredAppGroupIndex {
            return debugHoveredAppGroupIndex
        }
        guard case .app(let appGroupIndex) = hoverTarget else { return nil }
        return appGroupIndex
    }
    private var appHoverAssociatedSpaceLabels: [String] {
@@ -3483,6 +3785,10 @@
        return item.item.windowCount > 1 ? "\(item.item.windowCount)" : ""
    }
    private var waterfallAppHoverHeaderFillColor: NSColor {
        NSColor.systemGray.withAlphaComponent(0.22)
    }
    private var appShelfMaxScrollOffset: CGFloat {
        max(0, appShelfContentWidth - shelfLayer.bounds.width)
    }
@@ -3568,6 +3874,7 @@
            column.countLayer.isHidden = true
            for card in column.cards {
                card.containerLayer.transform = CATransform3DIdentity
                if let frame = layout.cardFramesByWindowID[card.item.window.id] {
                    card.containerLayer.frame = frame
                } else {
@@ -3714,9 +4021,11 @@
            width: bounds.width,
            height: WaterfallMetrics.headerHeight
        )
        column.headerLayer.backgroundColor = (isSpaceFocused
            ? NSColor.systemFill.withAlphaComponent(0.10)
            : NSColor.controlBackgroundColor.withAlphaComponent(0.52)
        column.headerLayer.backgroundColor = (isHovered
            ? waterfallAppHoverHeaderFillColor
            : (isSpaceFocused
                ? NSColor.systemFill.withAlphaComponent(0.10)
                : NSColor.controlBackgroundColor.withAlphaComponent(0.52))
        ).cgColor
        column.appNameLayer.frame = CGRect(
            x: WaterfallMetrics.columnHeaderTextInset,
@@ -3724,16 +4033,14 @@
            width: max(1, bounds.width - WaterfallMetrics.columnHeaderTextInset * 2),
            height: 16
        )
        column.appNameLayer.foregroundColor = (isHovered
            ? spaceLaneOccupiedColor
            : NSColor.labelColor
        ).cgColor
        column.appNameLayer.foregroundColor = NSColor.labelColor.cgColor
        column.countLayer.frame = CGRect(
            x: bounds.width - 48,
            y: bounds.height - 24,
            width: 36,
            height: 16
        )
        column.countLayer.foregroundColor = NSColor.secondaryLabelColor.cgColor
        let appGroupIndex = column.column.appGroupIndex
        let verticalOffset = clampedWaterfallColumnOffset(
@@ -3753,6 +4060,7 @@
            let y = firstCardTop
                - WaterfallMetrics.cardHeight
                - CGFloat(cardIndex) * (WaterfallMetrics.cardHeight + WaterfallMetrics.cardGap)
            card.containerLayer.transform = CATransform3DIdentity
            card.containerLayer.frame = CGRect(
                x: WaterfallMetrics.columnHorizontalPadding,
                y: y,
@@ -3767,10 +4075,12 @@
        let bounds = card.containerLayer.bounds
        let isSelected = card.item.window.id == effectiveSelection?.windowID
        let isHovered = card.item.window.id == hoveredWindowID
        let isAppLinked = isWaterfallCardAppLinked(card)
        applyWaterfallCardVisualState(
            card,
            selected: shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered),
            hovered: isHovered
            hovered: isHovered,
            appLinked: isAppLinked
        )
        let outerInset: CGFloat = 0
@@ -3833,12 +4143,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 +4164,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 +4196,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
@@ -4405,6 +4728,26 @@
        return card.item.primarySpaceID == activeSpaceFocusID
    }
    private func isWaterfallCardAppLinked(_ card: WaterfallCardLayers) -> Bool {
        guard card.item.appGroupIndex == appIconHoverLinkedAppGroupIndex else { return false }
        if isHorizontalMasonryMode {
            return true
        }
        return waterfallColumns
            .first(where: { $0.column.appGroupIndex == card.item.appGroupIndex })?
            .cards
            .sorted { lhs, rhs in
                if lhs.item.windowIndex != rhs.item.windowIndex {
                    return lhs.item.windowIndex < rhs.item.windowIndex
                }
                return lhs.item.window.id < rhs.item.window.id
            }
            .first?
            .item.window.id == card.item.window.id
    }
    private func waterfallColumnWidth() -> CGFloat {
        guard waterfallColumns.count > 0 else { return WaterfallMetrics.columnWidth }
@@ -4422,6 +4765,43 @@
    private var isHorizontalMasonryMode: Bool {
        waterfallViewMode == .horizontalMasonry
    }
    private var nextWaterfallViewMode: QuickSwitchWaterfallViewMode {
        isHorizontalMasonryMode ? .verticalColumns : .horizontalMasonry
    }
    private var viewModeToggleBlueColor: NSColor {
        NSColor(deviceRed: 135.0 / 255.0, green: 200.0 / 255.0, blue: 1, alpha: 1)
    }
    private var viewModeToggleVisualState: ViewModeToggleVisualState {
        if isHorizontalMasonryMode {
            return isViewModeToggleButtonHovered ? .verticalAvailable : .horizontalSelected
        }
        return isViewModeToggleButtonHovered ? .horizontalAvailable : .verticalSelected
    }
    private func viewModeToggleVisualStateIsSelected(_ state: ViewModeToggleVisualState) -> Bool {
        switch state {
        case .horizontalSelected, .verticalSelected:
            return true
        case .horizontalAvailable, .verticalAvailable:
            return false
        }
    }
    private var viewModeToggleIconKind: String {
        viewModeToggleVisualState.rawValue
    }
    private var viewModeToggleAccessibilityLabel: String {
        switch nextWaterfallViewMode {
        case .verticalColumns:
            return "切换到纵栏瀑布"
        case .horizontalMasonry:
            return "切换到横栏瀑布"
        }
    }
    private var waterfallMaxScrollOffset: CGFloat {
@@ -4541,21 +4921,6 @@
        shapeLayer.lineCap = .round
        shapeLayer.lineJoin = .round
        return shapeLayer
    }
    private func settingsIconImage() -> CGImage? {
        let names = [
            NSImage.Name("NSPreferencesGeneral"),
            NSImage.Name("NSAdvanced")
        ]
        for name in names {
            guard let image = NSImage(named: name) else { continue }
            var rect = CGRect(origin: .zero, size: image.size)
            if let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil) {
                return cgImage
            }
        }
        return nil
    }
    private func makeTextLayer(
@@ -4837,6 +5202,12 @@
                "frame": dictionary(from: item.containerLayer.frame),
                "visibleFrame": dictionary(from: visibleFrame),
                "iconFrame": dictionary(from: item.iconLayer.frame),
                "indexText": item.indexLayer.string ?? "",
                "indexFrame": dictionary(from: item.indexLayer.frame),
                "indexVisible": !item.indexLayer.isHidden && item.indexLayer.opacity > 0,
                "indexFontSize": Double(item.indexLayer.fontSize),
                "indexOpacity": Double(item.indexLayer.opacity),
                "indexColor": colorDictionary(from: item.indexLayer.foregroundColor),
                "labelFrame": dictionary(from: item.labelLayer.frame),
                "labelTruncationMode": "middle",
                "badgeVisible": !item.badgeLayer.isHidden,
@@ -4910,12 +5281,14 @@
                "frame": dictionary(from: column.containerLayer.frame),
                "visibleFrame": dictionary(from: visibleFrame),
                "headerFrame": dictionary(from: column.headerLayer.frame),
                "headerBackgroundColor": colorDictionary(from: column.headerLayer.backgroundColor),
                "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),
                "countFrame": dictionary(from: column.countLayer.frame),
                "countColor": colorDictionary(from: column.countLayer.foregroundColor),
                "verticalScrollOffset": Double(waterfallColumnScrollOffsets[appGroupIndex] ?? 0),
                "maxVerticalScrollOffset": Double(waterfallMaxVerticalScrollOffset(for: appGroupIndex)),
                "cards": waterfallCardReports(for: column)
@@ -5002,6 +5375,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 +5483,7 @@
        var states: [String] = []
        let isSelected = card.item.window.id == effectiveSelection?.windowID
        let isHovered = card.item.window.id == hoveredWindowID
        let isAppLinked = isWaterfallCardAppLinked(card)
        if shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered) {
            states.append("selected")
        } else if isSelected {
@@ -5117,6 +5492,9 @@
        if isHovered {
            states.append("hover")
        }
        if isAppLinked {
            states.append("appLinked")
        }
        if isWaterfallCardSpaceFocused(card) {
            states.append("spaceFocused")
        }