Ariver
2026-06-19 5f7ad88e0d1f9cd2bffd28d86ee2ecef1e88e7f8
src/Apptag/ContentView.swift
@@ -82,6 +82,10 @@
    }
    func updateNSView(_ container: TextFieldContainer, context: Context) {
        if let editor = container.textField.currentEditor() as? NSTextView,
           editor.hasMarkedText() {
            return
        }
        if container.textField.stringValue != text {
            container.textField.stringValue = text
        }
@@ -106,6 +110,9 @@
        }
        func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
            if textView.hasMarkedText() {
                return false
            }
            if commandSelector == #selector(NSResponder.insertNewline(_:)) {
                onSubmit?()
                return true
@@ -319,6 +326,8 @@
    var bubbleDraftNote = ""
    var tagNavigationHoveredGroupName: String? = nil
    var tagNavigationAppDropTargetName: String? = nil
    var tagNavigationLastHoverScrollID: String? = nil
    var tagNavigationLastHoverScrollAt: Date? = nil
}
private struct EditActionFeedback: Identifiable {
@@ -353,6 +362,8 @@
    @State private var groupLayoutVersion = 0
    @State private var appGridScrollTargetID: String? = nil
    @State private var appGridScrollRequestToken = 0
    @State private var selectedUsageTipIndex = 0
    @State private var usageTipsHovered = false
    // Edit mode
    @State private var editPhase: EditPhase = .none
@@ -414,6 +425,7 @@
    @AppStorage("displayMode") private var displayMode = AppDefaults.displayMode
    @AppStorage("hideAppNames") private var hideAppNames = AppDefaults.hideAppNames
    @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = AppDefaults.showUncommonAppBubbles
    @AppStorage("hideUsageTips") private var hideUsageTips = AppDefaults.hideUsageTips
    @AppStorage("useAppKitTagNavigation") private var useAppKitTagNavigation = AppDefaults.useAppKitTagNavigation
    @AppStorage("skipTagRemovalDropConfirm") private var skipTagRemovalDropConfirm = false
    @AppStorage("skipUncategorizedDropConfirm") private var skipUncategorizedDropConfirm = false
@@ -423,7 +435,8 @@
    private let floatingControlsTrailingInset: CGFloat = 20
    private let floatingControlsReservedWidth: CGFloat = 120
    private var appBubbleDisabled: Bool {
        appGridInteraction.appDragModeActive
        usageTipsHovered
            || appGridInteraction.appDragModeActive
            || appGridInteraction.pendingUncategorizedDrop != nil
            || appGridInteraction.pendingTagRemovalDrop != nil
    }
@@ -432,6 +445,8 @@
            ?? (isColorlessContainerMode ? filledColorlessContainer : nil)
    }
    private let rightSidebarFloatingClearance: CGFloat = 44
    private let tagNavigationHoverScrollDelay: TimeInterval = 0.14
    private let tagNavigationHoverScrollInterval: TimeInterval = 0.22
    private var floatingButtonSurfaceColor: Color {
        colorScheme == .dark
            ? Color.white.opacity(0.10)
@@ -448,6 +463,23 @@
    private var isColorlessContainerMode: Bool {
        displayMode == "container" || displayMode == "gridContainer"
    }
    private var shouldShowUsageTips: Bool {
        !hideUsageTips && !allApps.isEmpty && !quickSearchOnlySession
    }
    private var appGridUsageTips: [AppGridUsageTip] {
        [
            AppGridUsageTip(id: 1, titleKey: "usageTips.tip1.title", detailKey: "usageTips.tip1.detail"),
            AppGridUsageTip(id: 2, titleKey: "usageTips.tip2.title", detailKey: "usageTips.tip2.detail"),
            AppGridUsageTip(id: 3, titleKey: "usageTips.tip3.title", detailKey: "usageTips.tip3.detail"),
            AppGridUsageTip(id: 4, titleKey: "usageTips.tip4.title", detailKey: "usageTips.tip4.detail"),
            AppGridUsageTip(id: 5, titleKey: "usageTips.tip5.title", detailKey: "usageTips.tip5.detail"),
            AppGridUsageTip(id: 6, titleKey: "usageTips.tip6.title", detailKey: "usageTips.tip6.detail"),
            AppGridUsageTip(id: 7, titleKey: "usageTips.tip7.title", detailKey: "usageTips.tip7.detail"),
            AppGridUsageTip(id: 8, titleKey: "usageTips.tip8.title", detailKey: "usageTips.tip8.detail"),
        ]
    }
    private var shouldRenderAppGridBehindQuickSearch: Bool {
@@ -1121,6 +1153,10 @@
                    bubbleDisabled: appBubbleDisabled,
                    showUncommonAppBubbles: showUncommonAppBubbles,
                    highlightedGroupName: appGridHighlightedGroupName,
                    bottomContentPadding: shouldShowUsageTips ? AppGridUsageTipsMetrics.reservedHeight : 0,
                    usageTipsVisible: shouldShowUsageTips,
                    usageTips: appGridUsageTips,
                    selectedUsageTipIndex: $selectedUsageTipIndex,
                    contentRevision: groupLayoutVersion,
                    scrollTargetID: appGridScrollTargetID,
                    scrollRequestToken: appGridScrollRequestToken,
@@ -1142,7 +1178,8 @@
                        }
                    },
                    onScrollActivity: handleAppGridScrollActivity,
                    onDragModeChange: { setAppDragMode($0) }
                    onDragModeChange: { setAppDragMode($0) },
                    onUsageTipsHoverChange: handleUsageTipsHoverChange
                )
            }
        }
@@ -1866,6 +1903,13 @@
        }
    }
    private func handleUsageTipsHoverChange(_ hovering: Bool) {
        usageTipsHovered = hovering
        if hovering, appGridInteraction.hoveredBubble != nil {
            appGridInteraction.hoveredBubble = nil
        }
    }
    private func handleBubbleHover(app: AppInfo, frame: CGRect, event: AppBubbleHoverEvent) {
        guard !appBubbleDisabled else {
            clearAppBubbleState()
@@ -2007,7 +2051,7 @@
    }
    private func handleTagNavigationHover(_ id: String, active: Bool) {
        if appGridInteraction.appDragModeActive {
        if appGridInteraction.appDragModeActive || tagNavDragModeActive {
            if !active, appGridInteraction.tagNavigationHoveredGroupName == id {
                appGridInteraction.tagNavigationHoveredGroupName = nil
            }
@@ -2023,6 +2067,7 @@
        appGridInteraction.tagNavigationHoveredGroupName = id
        fillColorlessContainer(id)
        scheduleTagNavigationHoverScroll(id)
    }
    private func handleTagNavigationAppDropHover(_ id: String, active: Bool) {
@@ -2043,6 +2088,26 @@
        appGridInteraction.tagNavigationAppDropTargetName = id
    }
    private func scheduleTagNavigationHoverScroll(_ id: String) {
        DispatchQueue.main.asyncAfter(deadline: .now() + tagNavigationHoverScrollDelay) {
            guard appGridInteraction.tagNavigationHoveredGroupName == id else { return }
            guard !appGridInteraction.appDragModeActive && !tagNavDragModeActive else { return }
            scrollToTagFromHover(id)
        }
    }
    private func scrollToTagFromHover(_ id: String) {
        let now = Date()
        if appGridInteraction.tagNavigationLastHoverScrollID == id,
           let lastScrollAt = appGridInteraction.tagNavigationLastHoverScrollAt,
           now.timeIntervalSince(lastScrollAt) < tagNavigationHoverScrollInterval {
            return
        }
        appGridInteraction.tagNavigationLastHoverScrollID = id
        appGridInteraction.tagNavigationLastHoverScrollAt = now
        scrollTo(id)
    }
    private func fillColorlessContainer(_ id: String) {
        guard isColorlessContainerMode else { return }
        guard filledColorlessContainer != id else { return }