From 6f3ed4f3b8eb45734ca77916b4c96fab937cb2e2 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 31 May 2026 02:58:27 +0800
Subject: [PATCH] Prepare 7.8.7 dock policy release

---
 Apptag/OverlayWindowController.swift                                                      |    8 ++
 Release/AppStore-7.8.7-20260531.0252/README.md                                            |   32 ++++++++
 Apptag/ApptagApp.swift                                                                    |   56 ++++++++++++-
 Apptag/ProcessSingleton.swift                                                             |    3 
 Release/AppStore-7.8.7-20260531.0252/APP_STORE_ASSET_INVENTORY.md                         |   17 ++++
 Scripts/window_logic_qa.sh                                                                |   73 ++++++++++++++++++
 Release/AppStore-7.8.7-20260531.0252/Archive/TagLauncher-7.8.7-20260531.0252-local-QA.dmg |    0 
 Apptag/Info.plist                                                                         |    4 
 Release/AppStore-7.8.7-20260531.0252/QA_RELEASE_EVIDENCE.md                               |   39 +++++++++
 CHANGELOG.md                                                                              |    8 ++
 10 files changed, 231 insertions(+), 9 deletions(-)

diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index 449efca..7008544 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -38,6 +38,7 @@
     private static let downloadHelpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherDownloadHelpMenuItem")
     private static let externalActivationNotification = Notification.Name("TagLauncherExternalActivationRequested")
     private static let externalActivationObject = AppIdentity.bundleIdentifier
+    private static let duplicateLaunchSuppressReopenKey = "duplicateLaunchSuppressReopenAt"
     private static let launcherOverlayLevel = NSWindow.Level(rawValue: NSWindow.Level.mainMenu.rawValue - 1)
     private static let overlayDefaultLevel = launcherOverlayLevel
     private static let overlayTextInputLevel = launcherOverlayLevel
@@ -208,11 +209,39 @@
         configureApplicationMenuWhenAvailable(retries: 12)
     }
 
-    /// Reopening the already-running app must not implicitly show App Grid.
-    /// App Grid is only opened by explicit launcher commands: main hotkey or status item/menu.
+    /// Dock icon reopen is an explicit App Grid entry only when the user chooses to show the Dock icon.
+    /// Duplicate-instance handoff suppresses this path so repeated launches do not show App Grid.
     func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
         guard Date() >= suppressReopenUntil else { return false }
+        let lastDuplicateLaunch = UserDefaults.standard.double(forKey: Self.duplicateLaunchSuppressReopenKey)
+        if lastDuplicateLaunch > 0,
+           Date().timeIntervalSince1970 - lastDuplicateLaunch < 2.0 {
+            suppressReopenUntil = Date().addingTimeInterval(1.0)
+            return false
+        }
+        guard UserDefaults.standard.bool(forKey: Self.showDockIconKey) else { return false }
+        guard isPointerNearDockArea() else { return false }
+        showOrFocusOverlay()
         return false  // Suppress default "unhide all windows" behavior
+    }
+
+    private func isPointerNearDockArea() -> Bool {
+        let mouse = NSEvent.mouseLocation
+        return NSScreen.screens.contains { screen in
+            let frame = screen.frame
+            let visible = screen.visibleFrame
+            guard NSMouseInRect(mouse, frame, false) else { return false }
+            let bottomDock = visible.minY > frame.minY
+                && mouse.y >= frame.minY
+                && mouse.y <= visible.minY + 24
+            let leftDock = visible.minX > frame.minX
+                && mouse.x >= frame.minX
+                && mouse.x <= visible.minX + 24
+            let rightDock = visible.maxX < frame.maxX
+                && mouse.x <= frame.maxX
+                && mouse.x >= visible.maxX - 24
+            return bottomDock || leftDock || rightDock
+        }
     }
 
     func applicationWillTerminate(_ notification: Notification) {
@@ -272,11 +301,17 @@
     }
 
     private func beginLauncherForegroundOwnership(activate: Bool = true, keyWindow: NSWindow? = nil) {
-        if NSApp.activationPolicy() != .regular {
-            NSApp.setActivationPolicy(.regular)
+        let showDock = UserDefaults.standard.bool(forKey: Self.showDockIconKey)
+        let desiredPolicy: NSApplication.ActivationPolicy = showDock ? .regular : .accessory
+        if NSApp.activationPolicy() != desiredPolicy {
+            NSApp.setActivationPolicy(desiredPolicy)
         }
-        if activate {
+        if activate, showDock {
             claimLauncherForeground(keyWindow: keyWindow)
+        } else if activate {
+            NSApp.activate(ignoringOtherApps: true)
+            keyWindow?.makeKeyAndOrderFront(nil)
+            keyWindow?.orderFrontRegardless()
         }
     }
 
@@ -335,15 +370,18 @@
 
         let shouldStayAccessoryForCurrentFullscreenSpace = isOverlayVisible
             && (avoidSpaceSwitch || overlayAvoidsSpaceSwitch)
+        let shouldStayAccessoryForHiddenDockChrome = requiresForegroundOwnership
+            && !showDock
         let shouldStayAccessoryForQuickOnlySearch = isOverlayVisible
             && quickSearchOnlyOverlaySession
             && !showDock
         let desiredPolicy: NSApplication.ActivationPolicy = shouldStayAccessoryForCurrentFullscreenSpace
             ? .accessory
+            : (shouldStayAccessoryForHiddenDockChrome ? .accessory
             : (shouldStayAccessoryForQuickOnlySearch ? .accessory
             : (requiresForegroundOwnership
             ? .regular
-            : (showDock ? .regular : .accessory)))
+            : (showDock ? .regular : .accessory))))
         if NSApp.activationPolicy() != desiredPolicy {
             NSApp.setActivationPolicy(desiredPolicy)
         }
@@ -355,6 +393,7 @@
 
         if activate && requiresForegroundOwnership
             && !shouldStayAccessoryForCurrentFullscreenSpace
+            && !shouldStayAccessoryForHiddenDockChrome
             && !shouldStayAccessoryForQuickOnlySearch {
             let keyWindow = isSettingsVisible ? settingsWindow : (isOverlayVisible ? overlayWindow : nil)
             claimLauncherForeground(
@@ -1478,7 +1517,10 @@
 
     @objc private func handleExternalActivationRequest(_ notification: Notification) {
         let shouldShowOverlay = notification.userInfo?["showOverlay"] as? Bool ?? false
-        guard shouldShowOverlay else { return }
+        guard shouldShowOverlay else {
+            suppressReopenUntil = Date().addingTimeInterval(1.0)
+            return
+        }
         showOrFocusOverlay()
     }
 
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 153ed6e..1d5c6b7 100644
--- a/Apptag/Info.plist
+++ b/Apptag/Info.plist
@@ -19,9 +19,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>7.8.6</string>
+	<string>7.8.7</string>
 	<key>CFBundleVersion</key>
-	<string>20260531.0146</string>
+	<string>20260531.0252</string>
 	<key>LSApplicationCategoryType</key>
 	<string>public.app-category.utilities</string>
 	<key>LSMinimumSystemVersion</key>
diff --git a/Apptag/OverlayWindowController.swift b/Apptag/OverlayWindowController.swift
index 99ac779..1472d2e 100644
--- a/Apptag/OverlayWindowController.swift
+++ b/Apptag/OverlayWindowController.swift
@@ -154,6 +154,14 @@
                 newWindow.setFrame(placementFrame, display: true)
                 self.orderFront(newWindow)
             }
+            for delay in [0.2, 0.5] {
+                DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self, weak newWindow] in
+                    guard let self, let newWindow, self.window === newWindow else { return }
+                    guard newWindow.frame != placementFrame else { return }
+                    newWindow.setFrame(placementFrame, display: true)
+                    self.orderFront(newWindow)
+                }
+            }
             if let settingsWindow = self.dependencies.settingsWindow(), settingsWindow.isVisible {
                 self.dependencies.prepareSettingsWindow(settingsWindow)
             }
diff --git a/Apptag/ProcessSingleton.swift b/Apptag/ProcessSingleton.swift
index af0e54b..c2111a2 100644
--- a/Apptag/ProcessSingleton.swift
+++ b/Apptag/ProcessSingleton.swift
@@ -6,6 +6,7 @@
     private static let lockURL = AppIdentity.applicationSupportDirectory
         .appendingPathComponent("TagLauncher.lock")
     private static let activationNotification = Notification.Name("TagLauncherExternalActivationRequested")
+    private static let duplicateLaunchSuppressReopenKey = "duplicateLaunchSuppressReopenAt"
 
     static func acquireOrHandOffAndExit() -> FileHandle {
         do {
@@ -34,6 +35,8 @@
         let arguments = CommandLine.arguments.dropFirst()
         let shouldShowOverlay = arguments.contains("--show-overlay")
         if !shouldShowOverlay {
+            UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: duplicateLaunchSuppressReopenKey)
+            UserDefaults.standard.synchronize()
             NSApplication.shared.setActivationPolicy(.accessory)
         }
         let ownerInstance = NSRunningApplication
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 455be64..5b3936c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # TagLauncher Changelog
 
+## [7.8.7] — 2026-05-31
+
+- 修复关闭“Show in Dock”后用主快捷键打开 App Grid 时 TagLauncher Dock 图标仍会闪现的问题;隐藏 Dock 图标设置现在严格生效
+- 恢复“Show in Dock”开启时点击 Dock 图标打开 App Grid 的交互,同时继续阻止程序化重复启动误唤出 App Grid
+- 加固 overlay 显示后的窗口 frame 复核,避免隐藏 Dock / 多屏 / 全屏场景下系统坐标变化导致 overlay 偏移
+- 窗口 QA 增加 Dock 点击、隐藏 Dock 主快捷键、重复启动不弹 App Grid 的组合回归
+- 版本号更新为 `7.8.7`,Build 更新为 `20260531.0252`
+
 ## [7.8.6] — 2026-05-31
 
 - 修复未按主快捷键时 App Grid 可能被重复启动或外部激活路径误唤出的问题;App Grid 现在只由主快捷键或状态栏显式命令打开
diff --git a/Release/AppStore-7.8.7-20260531.0252/APP_STORE_ASSET_INVENTORY.md b/Release/AppStore-7.8.7-20260531.0252/APP_STORE_ASSET_INVENTORY.md
new file mode 100644
index 0000000..f154e56
--- /dev/null
+++ b/Release/AppStore-7.8.7-20260531.0252/APP_STORE_ASSET_INVENTORY.md
@@ -0,0 +1,17 @@
+# App Store Asset Inventory
+
+This release pack freezes TagLauncher `7.8.7` build `20260531.0252`.
+
+## Included
+
+- `Archive/TagLauncher-7.8.7-20260531.0252-local-QA.dmg`
+- `README.md`
+- `QA_RELEASE_EVIDENCE.md`
+- `APP_STORE_ASSET_INVENTORY.md`
+
+## External Items Still Required For App Store Upload
+
+- Apple Developer Bundle ID selection/confirmation in App Store Connect.
+- Mac App Store signing certificate and provisioning profile.
+- Final App Store screenshots and metadata review before submission.
+
diff --git a/Release/AppStore-7.8.7-20260531.0252/Archive/TagLauncher-7.8.7-20260531.0252-local-QA.dmg b/Release/AppStore-7.8.7-20260531.0252/Archive/TagLauncher-7.8.7-20260531.0252-local-QA.dmg
new file mode 100644
index 0000000..20d99d2
--- /dev/null
+++ b/Release/AppStore-7.8.7-20260531.0252/Archive/TagLauncher-7.8.7-20260531.0252-local-QA.dmg
Binary files differ
diff --git a/Release/AppStore-7.8.7-20260531.0252/QA_RELEASE_EVIDENCE.md b/Release/AppStore-7.8.7-20260531.0252/QA_RELEASE_EVIDENCE.md
new file mode 100644
index 0000000..0517b38
--- /dev/null
+++ b/Release/AppStore-7.8.7-20260531.0252/QA_RELEASE_EVIDENCE.md
@@ -0,0 +1,39 @@
+# QA Release Evidence
+
+## Build Under Test
+
+- Version: `7.8.7`
+- Build: `20260531.0252`
+- App: `/Users/ar/Projects/Taglauncher/build/TagLauncher.app`
+- DMG: `/Users/ar/Projects/Taglauncher/build/TagLauncher.dmg`
+- Archived DMG: `Archive/TagLauncher-7.8.7-20260531.0252-local-QA.dmg`
+- DMG SHA-256: `51cb2861eaf9194dc717cf00b96d3adca490ba89b16438c530b24939a25a94ae`
+
+## Checks
+
+- Version metadata:
+  - `CFBundleShortVersionString = 7.8.7`
+  - `CFBundleVersion = 20260531.0252`
+  - `LSUIElement = true`
+- Code signing:
+  - `codesign --verify --deep --strict --verbose=2 build/TagLauncher.app`
+  - Result: passed
+- Disk image:
+  - `hdiutil verify build/TagLauncher.dmg`
+  - Result: passed
+- Window logic:
+  - `APP_BUILD=20260531.0252 Scripts/window_logic_qa.sh`
+  - Result: `ALL WINDOW LOGIC QA PASSED`
+
+## Targeted Fix Coverage
+
+- `Show in Dock = false`, main hotkey:
+  - Expected: App Grid appears and no TagLauncher Dock tile appears.
+  - Result: passed
+- `Show in Dock = true`, Dock icon click:
+  - Expected: App Grid appears.
+  - Result: passed
+- Duplicate launches:
+  - Expected: one Dock tile and no unexpected App Grid.
+  - Result: passed
+
diff --git a/Release/AppStore-7.8.7-20260531.0252/README.md b/Release/AppStore-7.8.7-20260531.0252/README.md
new file mode 100644
index 0000000..9d42303
--- /dev/null
+++ b/Release/AppStore-7.8.7-20260531.0252/README.md
@@ -0,0 +1,32 @@
+# TagLauncher 7.8.7 Release Pack
+
+This folder freezes TagLauncher `7.8.7` build `20260531.0252` for local QA distribution and Mac App Store submission preparation.
+
+## Release Identity
+
+- Product: `TagLauncher`
+- Version: `7.8.7`
+- Build: `20260531.0252`
+- Branch: `codex/fix-quick-search-focus-routing`
+- Local QA DMG: `Archive/TagLauncher-7.8.7-20260531.0252-local-QA.dmg`
+- DMG SHA-256: `51cb2861eaf9194dc717cf00b96d3adca490ba89b16438c530b24939a25a94ae`
+
+## Changes Since 7.8.6
+
+- Hidden-Dock mode no longer flashes the TagLauncher Dock icon when opening App Grid with the main hotkey.
+- Dock icon click opens App Grid when `Show in Dock` is enabled.
+- Programmatic duplicate launches remain blocked from opening App Grid.
+- Overlay frame is rechecked after show-time system coordinate changes to reduce fullscreen/multi-display drift.
+
+## Verification
+
+- App version metadata verified: `7.8.7 (20260531.0252)`
+- `LSUIElement = true`
+- `codesign --verify --deep --strict --verbose=2 build/TagLauncher.app`: passed
+- `hdiutil verify build/TagLauncher.dmg`: passed
+- `Scripts/window_logic_qa.sh`: passed
+- Targeted checks:
+  - `showDockIcon=false` + main hotkey: App Grid appears, no TagLauncher Dock tile.
+  - `showDockIcon=true` + Dock click: App Grid appears.
+  - repeated launches: one Dock tile, no unexpected App Grid.
+
diff --git a/Scripts/window_logic_qa.sh b/Scripts/window_logic_qa.sh
index 89beba4..4a333b0 100755
--- a/Scripts/window_logic_qa.sh
+++ b/Scripts/window_logic_qa.sh
@@ -278,6 +278,61 @@
   log "PASS Dock tile count: $names"
 }
 
+assert_no_dock_tile() {
+  local output count names
+  output="$(osascript <<'OSA'
+tell application "System Events"
+  tell process "Dock"
+    set tagCount to 0
+    set tagNames to {}
+    repeat with itemRef in UI elements of list 1
+      try
+        set itemName to name of itemRef as text
+        if itemName is "TagLauncher" then
+          set tagCount to tagCount + 1
+          set end of tagNames to itemName
+        end if
+      end try
+    end repeat
+    return (tagCount as text) & "|" & (tagNames as text)
+  end tell
+end tell
+OSA
+)"
+  count="${output%%|*}"
+  names="${output#*|}"
+  if [[ "$count" != "0" ]]; then
+    echo "FAIL: expected no TagLauncher Dock tile, got $count ($names)" >&2
+    return 1
+  fi
+  log "PASS no TagLauncher Dock tile"
+}
+
+click_taglauncher_dock_tile() {
+  local coords x y
+  coords="$(osascript <<'OSA'
+tell application "System Events"
+  tell process "Dock"
+    repeat with itemRef in UI elements of list 1
+      try
+        if (name of itemRef as text) is "TagLauncher" then
+          set itemPosition to position of itemRef
+          set itemSize to size of itemRef
+          set centerX to (item 1 of itemPosition) + ((item 1 of itemSize) / 2)
+          set centerY to (item 2 of itemPosition) + ((item 2 of itemSize) / 2)
+          return (centerX as integer as text) & " " & (centerY as integer as text)
+        end if
+      end try
+    end repeat
+    error "TagLauncher Dock tile not found"
+  end tell
+end tell
+OSA
+)"
+  read -r x y <<<"$coords"
+  click_xy "$x" "$y"
+}
+
 assert_frontmost_taglauncher() {
   local frontmost
   frontmost="$(osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true')"
@@ -1071,6 +1126,24 @@
 assert_single_qa_app_instance
 assert_single_dock_tile
 swift_assert no-overlay
+log "==> QA Dock reopen: showDockIcon=true opens App Grid from explicit app reopen"
+click_taglauncher_dock_tile
+sleep 1.0
+wait_swift_assert overlay
+send_keycode 53
+sleep 0.4
+wait_swift_assert no-overlay
+log "==> QA hidden Dock: main hotkey opens App Grid without showing Dock tile"
+defaults write "$DEFAULTS_DOMAIN" showDockIcon -bool false
+prepare_isolated_app_instance
+assert_no_dock_tile
+send_main_hotkey
+sleep 0.8
+wait_swift_assert overlay
+assert_no_dock_tile
+send_keycode 53
+sleep 0.4
+wait_swift_assert no-overlay
 kill_all_taglauncher_instances
 sleep 0.4
 swift_assert no-overlay

--
Gitblit v1.9.3