Ariver
2026-08-28 c8f827e22866cc98f8bbb1c8146e28b2bb40a5cd
C1.source/Apptag/ContentView.swift
@@ -447,6 +447,7 @@
    @State private var quickSearchQuery = ""
    @State private var quickSearchDocuments: [QuickSearchDocument] = []
    @State private var quickSearchResults: [QuickSearchResult] = []
    @State private var quickSearchSelectedTagName: String? = nil
    @State private var quickSearchSelectedID: URL? = nil
    @State private var quickSearchManualSelection = false
    @State private var quickSearchFocusToken = 0
@@ -583,6 +584,10 @@
    }
    var body: some View {
        contentWithStateObservers
    }
    private var contentWithNotificationObservers: some View {
        contentStack
            .onAppear(perform: handleContentAppear)
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidShow), perform: handleOverlayDidShow)
@@ -593,6 +598,10 @@
            .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification), perform: handleApplicationDidBecomeActive)
            .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange), perform: handleDataDidChange)
            .onReceive(NotificationCenter.default.publisher(for: .appLanguageDidChange), perform: handleAppLanguageDidChange)
    }
    private var contentWithStateObservers: some View {
        contentWithNotificationObservers
            .onChange(of: editPhase) { _, newPhase in
                let active = newPhase != .none
                dismissAppBubble()
@@ -612,6 +621,9 @@
            }
            .onChange(of: defaultGroupName) { _, _ in
                rebuildDisplayGroups(apps: allApps, tagOrder: draggedTagNames)
                if quickSearchVisible {
                    refreshQuickSearchResults()
                }
            }
            .onChange(of: quickSearchQuery) { _, _ in
                quickSearchErrorMessage = nil
@@ -808,6 +820,8 @@
                QuickSearchPanelPresentationView(
                    query: $quickSearchQuery,
                    results: quickSearchResults,
                    tagFilters: quickSearchTagFilters,
                    selectedTagName: quickSearchSelectedTagName,
                    selectedID: quickSearchSelectedID,
                    focusToken: quickSearchFocusToken,
                    selectionScrollToken: quickSearchSelectionScrollToken,
@@ -818,7 +832,8 @@
                    errorMessage: quickSearchErrorMessage,
                    onCommand: handleQuickSearchCommand,
                    onHover: selectQuickSearchResult,
                    onLaunch: launchQuickSearchResult
                    onLaunch: launchQuickSearchResult,
                    onTagFilterChange: selectQuickSearchTagFilter
                )
                .frame(width: 0, height: 0)
                .allowsHitTesting(false)
@@ -835,6 +850,7 @@
            && !quickSearchResults.isEmpty
        let visibleRows = min(max(1, quickSearchResults.count), quickSearchMaxVisibleRows(in: size))
        return QuickSearchPanelMetrics.contentHeight(
            hasTagFilters: !quickSearchTagFilters.isEmpty,
            hasResultList: hasResultList,
            visibleRows: visibleRows
        )
@@ -847,6 +863,7 @@
    private func quickSearchMaxVisibleRows(in size: CGSize) -> Int {
        let bottomClearance: CGFloat = 84
        let chromeHeight = QuickSearchPanelMetrics.headerHeight
            + (quickSearchTagFilters.isEmpty ? 0 : QuickSearchPanelMetrics.tagFilterBarHeight)
            + QuickSearchPanelMetrics.dividerHeight
            + QuickSearchPanelMetrics.resultListVerticalInset * 2
        let rowHeightWithSpacing = QuickSearchPanelMetrics.rowHeight
@@ -866,6 +883,7 @@
        quickSearchVisible = true
        quickSearchOpenedAt = Date()
        quickSearchQuery = ""
        quickSearchSelectedTagName = nil
        quickSearchCompositionActive = false
        quickSearchManualSelection = false
        quickSearchErrorMessage = nil
@@ -920,6 +938,7 @@
        quickSearchCompositionActive = false
        quickSearchQuery = ""
        quickSearchResults = []
        quickSearchSelectedTagName = nil
        quickSearchSelectedID = nil
        quickSearchManualSelection = false
        quickSearchErrorMessage = nil
@@ -969,9 +988,12 @@
    private func refreshQuickSearchResults() {
        let previousSelection = quickSearchSelectedID
        let documents = quickSearchVisibleDocuments
        let selectedTagName = validateQuickSearchSelectedTagName(against: quickSearchTagFilters)
        quickSearchResults = QuickSearchEngine.search(
            quickSearchQuery,
            documents: quickSearchDocuments.filter { FileManager.default.fileExists(atPath: $0.app.path.path) }
            documents: documents,
            selectedTagName: selectedTagName
        )
        if quickSearchResults.isEmpty {
@@ -988,6 +1010,58 @@
            quickSearchSelectedID = quickSearchResults.first?.id
            quickSearchManualSelection = false
        }
    }
    private var quickSearchVisibleDocuments: [QuickSearchDocument] {
        quickSearchDocuments.filter { FileManager.default.fileExists(atPath: $0.app.path.path) }
    }
    private var quickSearchTagFilters: [QuickSearchTagFilterOption] {
        let availableTagNames = Set(
            quickSearchVisibleDocuments
                .flatMap(\.tagNames)
                .filter(isQuickSearchFilterableTag)
        )
        guard !availableTagNames.isEmpty else { return [] }
        let ordered = draggedTagNames
            .filter { availableTagNames.contains($0) && isQuickSearchFilterableTag($0) }
        let orderedSet = Set(ordered)
        let remaining = availableTagNames
            .filter { !orderedSet.contains($0) }
            .sorted { $0.localizedStandardCompare($1) == .orderedAscending }
        return (ordered + remaining).map { tagName in
            let definition = tagDefinitions[tagName]
            return QuickSearchTagFilterOption(
                name: tagName,
                colorIndex: definition?.color ?? tagColors[tagName] ?? 0,
                customColor: definition?.customColor ?? tagCustomColors[tagName]
            )
        }
    }
    private func isQuickSearchFilterableTag(_ tagName: String) -> Bool {
        let name = tagName.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !name.isEmpty, tagDefinitions[name] != nil else { return false }
        return !AppContainerID.isReservedGroupName(name, defaultGroupName: defaultGroupName)
    }
    private func validateQuickSearchSelectedTagName(against filters: [QuickSearchTagFilterOption]) -> String? {
        guard let quickSearchSelectedTagName else { return nil }
        guard filters.contains(where: { $0.name == quickSearchSelectedTagName }) else {
            self.quickSearchSelectedTagName = nil
            return nil
        }
        return quickSearchSelectedTagName
    }
    private func selectQuickSearchTagFilter(_ tagName: String?) {
        quickSearchSelectedTagName = tagName
        quickSearchErrorMessage = nil
        quickSearchManualSelection = false
        quickSearchFocusToken &+= 1
        refreshQuickSearchResults()
    }
    private func handleQuickSearchCommand(_ command: QuickSearchCommand) {
@@ -2421,6 +2495,7 @@
            quickSearchFocusToken &+= 1
        }
        quickSearchOpenedAt = Date()
        quickSearchSelectedTagName = nil
        refreshQuickSearchResults()
        refreshAppsForQuickSearch()
        NotificationCenter.default.post(