From d701709c08f98a8b6e2791fcbb1e9291624e3236 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 04 Jun 2026 01:24:54 +0800
Subject: [PATCH] Prepare 7.8.12 App Store submission materials

---
 Apptag/ContentView.swift |   67 ++++++++++++++++++++++++++++++---
 1 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index f29d986..86e511f 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -389,6 +389,7 @@
     @State private var quickSearchSelectionScrollToken = 0
     @State private var quickSearchCloseHidesOverlay = false
     @State private var quickSearchOnlySession = false
+    @State private var quickSearchOpenedAt: Date? = nil
     @State private var quickSearchErrorMessage: String? = nil
     @State private var initialQuickSearchConsumed = false
 
@@ -542,6 +543,7 @@
                     quickSearchVisible = true
                     quickSearchFocusToken &+= 1
                 }
+                quickSearchOpenedAt = Date()
                 refreshQuickSearchResults()
                 NotificationCenter.default.post(
                     name: .tagLauncherQuickSearchVisibilityChanged,
@@ -568,8 +570,9 @@
             let source = notification.userInfo?["source"] as? String ?? QuickSearchOpenSource.mainOverlay
             openQuickSearch(source: source)
         }
-        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchDismissRequested)) { _ in
-            closeQuickSearch()
+        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchDismissRequested)) { notification in
+            let source = notification.userInfo?["source"] as? String
+            closeQuickSearch(dismissalSource: source)
         }
         .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
             guard allApps.isEmpty, !refreshInProgress else { return }
@@ -663,7 +666,7 @@
                     .ignoresSafeArea()
                     .contentShape(Rectangle())
                     .onTapGesture {
-                        closeQuickSearch()
+                        closeQuickSearch(dismissalSource: QuickSearchDismissSource.backdrop)
                     }
                     .zIndex(899)
 
@@ -726,11 +729,13 @@
         quickSearchCloseHidesOverlay = quickSearchCloseHidesOverlay
             || source == QuickSearchOpenSource.globalHidden
         quickSearchVisible = true
+        quickSearchOpenedAt = Date()
         quickSearchQuery = ""
         quickSearchManualSelection = false
         quickSearchErrorMessage = nil
         quickSearchFocusToken &+= 1
         refreshQuickSearchResults()
+        refreshApps()
         NotificationCenter.default.post(
             name: .tagLauncherQuickSearchVisibilityChanged,
             object: nil,
@@ -749,10 +754,26 @@
             && !quickSearchVisible
     }
 
-    private func closeQuickSearch(notify: Bool = true, hideOverlayIfNeeded: Bool = true) {
+    private func closeQuickSearch(
+        notify: Bool = true,
+        hideOverlayIfNeeded: Bool = true,
+        dismissalSource: String? = nil
+    ) {
+        Diagnostics.log("content.quickSearch.closeRequest", [
+            "source": dismissalSource,
+            "notify": notify,
+            "hideOverlayIfNeeded": hideOverlayIfNeeded,
+            "quickSearchVisible": quickSearchVisible,
+            "quickSearchCloseHidesOverlay": quickSearchCloseHidesOverlay,
+            "quickSearchOnlySession": quickSearchOnlySession
+        ])
+        if shouldIgnoreEarlyQuickSearchDismiss(from: dismissalSource) {
+            return
+        }
         let shouldHideOverlay = quickSearchVisible && quickSearchCloseHidesOverlay && hideOverlayIfNeeded
         guard quickSearchVisible else { return }
         quickSearchVisible = false
+        quickSearchOpenedAt = nil
         quickSearchQuery = ""
         quickSearchResults = []
         quickSearchSelectedID = nil
@@ -775,9 +796,29 @@
         }
     }
 
+    private func shouldIgnoreEarlyQuickSearchDismiss(from source: String?) -> Bool {
+        guard quickSearchVisible,
+              let quickSearchOpenedAt,
+              source == QuickSearchDismissSource.mouse || source == QuickSearchDismissSource.backdrop
+        else { return false }
+
+        let age = Date().timeIntervalSince(quickSearchOpenedAt)
+        let gracePeriod: TimeInterval = quickSearchOnlySession ? 1.25 : 0.30
+        guard age < gracePeriod else { return false }
+        Diagnostics.log("content.quickSearch.ignoredEarlyDismiss", [
+            "source": source,
+            "age": String(format: "%.3f", age),
+            "gracePeriod": String(format: "%.2f", gracePeriod)
+        ])
+        return true
+    }
+
     private func refreshQuickSearchResults() {
         let previousSelection = quickSearchSelectedID
-        quickSearchResults = QuickSearchEngine.search(quickSearchQuery, documents: quickSearchDocuments)
+        quickSearchResults = QuickSearchEngine.search(
+            quickSearchQuery,
+            documents: quickSearchDocuments.filter { FileManager.default.fileExists(atPath: $0.app.path.path) }
+        )
 
         if quickSearchResults.isEmpty {
             quickSearchSelectedID = nil
@@ -796,6 +837,11 @@
     }
 
     private func handleQuickSearchCommand(_ command: QuickSearchCommand) {
+        Diagnostics.log("content.quickSearch.command", [
+            "command": String(describing: command),
+            "quickSearchCloseHidesOverlay": quickSearchCloseHidesOverlay,
+            "quickSearchOnlySession": quickSearchOnlySession
+        ])
         switch command {
         case .moveUp:
             moveQuickSearchSelection(by: -1)
@@ -2369,7 +2415,7 @@
         }
     }
 
-    func refreshApps(forceLayoutRefresh: Bool = false) {
+    func refreshApps(forceLayoutRefresh: Bool = false, useCache: Bool = true) {
         guard !refreshInProgress else {
             if forceLayoutRefresh {
                 refreshAgainAfterCurrent = true
@@ -2380,7 +2426,7 @@
 
         refreshInProgress = true
         DispatchQueue.global(qos: .userInitiated).async {
-            let result = AppLibraryController.refresh()
+            let result = AppLibraryController.refresh(useCache: useCache)
             DispatchQueue.main.async {
                 applyAppLibrarySnapshot(result.snapshot)
                 handleSmartStartRunResult(result.smartStartResult)
@@ -2417,6 +2463,13 @@
     ) {
         appGridInteraction.appDragModeActive = false
         endTagNavReorder()
+        guard FileManager.default.fileExists(atPath: app.path.path) else {
+            AppIndexer.invalidateScanCache()
+            refreshApps()
+            onFailure?()
+            return
+        }
+
         let closeQuickSearchAndOverlayBeforeOpening = closeQuickSearchOnSuccess
             && closeOverlayOnSuccess
             && (quickSearchCloseHidesOverlay || quickSearchOnlySession)

--
Gitblit v1.9.3