From 1d19c0593ebe6abe60468e12e87459ab68586603 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 04 Jun 2026 12:07:09 +0800
Subject: [PATCH] Refresh app index when application folders change
---
TODO.md | 6 ++
Scripts/window_logic_qa.sh | 87 +++++++++++++++++++++--------
Apptag/ContentView.swift | 37 +++++++++---
Apptag/DataLayer.swift | 10 +++
Apptag/Info.plist | 4
CHANGELOG.md | 8 ++
6 files changed, 116 insertions(+), 36 deletions(-)
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index 86e511f..59964a3 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -376,6 +376,7 @@
@State private var refreshInProgress = false
@State private var refreshAgainAfterCurrent = false
@State private var refreshAgainForceLayout = false
+ @State private var refreshAgainUseCache = true
@FocusState private var bubbleNoteFocused: Bool
// Quick Search
@@ -532,7 +533,7 @@
}
.onAppear {
refreshNotchHeight()
- refreshAppsIfNeeded()
+ refreshAppsForOverlay()
if let initialQuickSearchSource, !initialQuickSearchConsumed {
initialQuickSearchConsumed = true
if initialQuickSearchSource == QuickSearchOpenSource.globalHidden {
@@ -545,6 +546,7 @@
}
quickSearchOpenedAt = Date()
refreshQuickSearchResults()
+ refreshAppsForQuickSearch()
NotificationCenter.default.post(
name: .tagLauncherQuickSearchVisibilityChanged,
object: nil,
@@ -558,7 +560,7 @@
.onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidShow)) { _ in
resetTransientDragState()
refreshNotchHeight()
- refreshAppsIfNeeded()
+ refreshAppsForOverlay()
}
.onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidHide)) { _ in
resetTransientDragState()
@@ -676,7 +678,7 @@
selectedID: quickSearchSelectedID,
focusToken: quickSearchFocusToken,
selectionScrollToken: quickSearchSelectionScrollToken,
- isLoading: quickSearchDocuments.isEmpty && refreshInProgress,
+ isLoading: quickSearchShouldShowLoading,
maxVisibleRows: quickSearchMaxVisibleRows(in: proxy.size),
panelTopY: quickSearchPanelTopY(in: proxy.size),
panelHeight: quickSearchPanelContentHeight(in: proxy.size),
@@ -695,7 +697,7 @@
}
private func quickSearchPanelContentHeight(in size: CGSize) -> CGFloat {
- let hasResultList = !(quickSearchDocuments.isEmpty && refreshInProgress)
+ let hasResultList = !quickSearchShouldShowLoading
&& quickSearchErrorMessage == nil
&& !quickSearchResults.isEmpty
let visibleRows = min(max(1, quickSearchResults.count), quickSearchMaxVisibleRows(in: size))
@@ -735,7 +737,7 @@
quickSearchErrorMessage = nil
quickSearchFocusToken &+= 1
refreshQuickSearchResults()
- refreshApps()
+ refreshAppsForQuickSearch()
NotificationCenter.default.post(
name: .tagLauncherQuickSearchVisibilityChanged,
object: nil,
@@ -752,6 +754,10 @@
&& smartStartNotice == nil
&& !appGridInteraction.dropRefreshVisible
&& !quickSearchVisible
+ }
+
+ private var quickSearchShouldShowLoading: Bool {
+ quickSearchVisible && refreshInProgress && quickSearchResults.isEmpty
}
private func closeQuickSearch(
@@ -1807,6 +1813,16 @@
refreshApps()
}
+ private func refreshAppsForOverlay() {
+ guard AppIndexer.shouldRefreshForSearchPathChanges() else { return }
+ refreshApps()
+ }
+
+ private func refreshAppsForQuickSearch() {
+ guard AppIndexer.shouldRefreshForSearchPathChanges() else { return }
+ refreshApps()
+ }
+
private func refreshNotchHeight() {
let mousePoint = NSEvent.mouseLocation
let activeScreen = NSScreen.screens.first(where: {
@@ -2417,10 +2433,9 @@
func refreshApps(forceLayoutRefresh: Bool = false, useCache: Bool = true) {
guard !refreshInProgress else {
- if forceLayoutRefresh {
- refreshAgainAfterCurrent = true
- refreshAgainForceLayout = true
- }
+ refreshAgainAfterCurrent = true
+ refreshAgainForceLayout = refreshAgainForceLayout || forceLayoutRefresh
+ refreshAgainUseCache = refreshAgainUseCache && useCache
return
}
@@ -2436,9 +2451,11 @@
refreshInProgress = false
if refreshAgainAfterCurrent {
let shouldForceLayout = refreshAgainForceLayout
+ let shouldUseCache = refreshAgainUseCache
refreshAgainAfterCurrent = false
refreshAgainForceLayout = false
- refreshApps(forceLayoutRefresh: shouldForceLayout)
+ refreshAgainUseCache = true
+ refreshApps(forceLayoutRefresh: shouldForceLayout, useCache: shouldUseCache)
}
}
}
diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 060c58c..36fd8c8 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -145,6 +145,16 @@
scanCacheLock.unlock()
}
+ static func shouldRefreshForSearchPathChanges() -> Bool {
+ scanCacheLock.lock()
+ let hasCachedApps = cachedScanApps != nil
+ let cachedSignature = cachedSearchPathSignature
+ scanCacheLock.unlock()
+
+ guard hasCachedApps else { return true }
+ return cachedSignature != currentSearchPathSignature()
+ }
+
private static func cachedScanIfFresh(matching searchPathSignature: [SearchPathSignature]) -> [AppInfo]? {
scanCacheLock.lock()
defer { scanCacheLock.unlock() }
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 78ff12b..11f278e 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.12</string>
+ <string>7.8.13</string>
<key>CFBundleVersion</key>
- <string>20260604.0105</string>
+ <string>20260604.1121</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b4f277..e21d2ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# TagLauncher Changelog
+## [7.8.13] — 2026-06-04
+
+- 修复 App 运行期间新安装应用后,App Grid 和 Quick Search 仍复用旧应用索引、无法立即检索到新应用的问题
+- App Grid / Quick Search 打开时会轻量检查标准应用目录签名;签名未变化时不刷新也不重建界面,只有目录变化时才后台重扫
+- 刷新过程中再次打开 App Grid 或 Quick Search 会合并一个后续刷新请求,避免新安装发生在刷新窗口内被漏掉
+- 窗口 QA 对 Dock 点击打开、App Grid 内打开 Quick Search 和 Quick Search 结果坐标读取增加短重试,避免系统事件或窗口列表瞬时抖动导致误判
+- 版本号更新为 `7.8.13`,Build 更新为 `20260604.1121`
+
## [7.8.12] — 2026-06-04
- 修复 Quick Search-only 模式刚打开后可能被过期鼠标关闭事件立即关掉的问题;现在 `Fn + Space` 打开后候选面板会保持稳定,键盘取消和再次按 `Fn + Space` 仍可立即关闭
diff --git a/Scripts/window_logic_qa.sh b/Scripts/window_logic_qa.sh
index 9e0c433..897add1 100755
--- a/Scripts/window_logic_qa.sh
+++ b/Scripts/window_logic_qa.sh
@@ -349,6 +349,20 @@
click_xy "$x" "$y"
}
+open_overlay_from_dock_with_retry() {
+ local output=""
+ for _ in {1..3}; do
+ click_taglauncher_dock_tile
+ sleep 0.8
+ if output="$(wait_swift_assert overlay 2>&1)"; then
+ printf '%s\n' "$output"
+ return 0
+ fi
+ done
+ printf '%s\n' "$output" >&2
+ return 1
+}
+
assert_frontmost_taglauncher() {
local frontmost
frontmost="$(osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true')"
@@ -792,10 +806,10 @@
return name.isEmpty
&& width >= 500
&& width <= 900
- && height >= 120
+ && height >= 320
&& height <= 850
}), let bounds = quickSearch[kCGWindowBounds as String] as? NSDictionary else {
- fputs("FAIL: could not find quick search bounds\n", stderr)
+ fputs("FAIL: could not find quick search result-list bounds\n", stderr)
exit(1)
}
let x = dimension(bounds, "X")
@@ -1029,16 +1043,37 @@
return 1
}
+open_appgrid_quick_search_with_retry() {
+ local output=""
+ for _ in {1..3}; do
+ send_keycode 49
+ sleep 0.4
+ if output="$(wait_swift_assert quick-search 2>&1)"; then
+ printf '%s\n' "$output"
+ return 0
+ fi
+ done
+ printf '%s\n' "$output" >&2
+ return 1
+}
+
assert_fullscreen_overlay_stable() {
local output=""
+ local consecutive_successes=0
for _ in {1..20}; do
- if ! output="$(swift "$assert_swift" fullscreen-overlay 2>&1)"; then
- printf '%s\n' "$output" >&2
- return 1
+ if output="$(swift "$assert_swift" fullscreen-overlay 2>&1)"; then
+ consecutive_successes=$((consecutive_successes + 1))
+ if [[ "$consecutive_successes" -ge 6 ]]; then
+ printf '%s\n' "$output"
+ return 0
+ fi
+ else
+ consecutive_successes=0
fi
sleep 0.1
done
- printf '%s\n' "$output"
+ printf '%s\n' "$output" >&2
+ return 1
}
show_overlay() {
@@ -1077,9 +1112,9 @@
hover_quick_search_results() {
local coords x y
- coords="$(swift "$coords_swift" quick-search-result)"
+ coords="$(quick_search_result_coords_with_retry)"
read -r x y <<<"$coords"
- for offset in 0 42 84 126 84 42 0; do
+ for offset in 0 8 16 8 0; do
move_xy "$x" "$((y + offset))"
sleep 0.08
done
@@ -1087,9 +1122,22 @@
click_quick_search_result() {
local coords x y
- coords="$(swift "$coords_swift" quick-search-result)"
+ coords="$(quick_search_result_coords_with_retry)"
read -r x y <<<"$coords"
click_xy "$x" "$y"
+}
+
+quick_search_result_coords_with_retry() {
+ local output
+ for _ in {1..8}; do
+ if output="$(swift "$coords_swift" quick-search-result 2>&1)"; then
+ printf '%s\n' "$output"
+ return 0
+ fi
+ sleep 0.15
+ done
+ printf '%s\n' "$output" >&2
+ return 1
}
page_scroll_appgrid() {
@@ -1193,9 +1241,7 @@
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
+open_overlay_from_dock_with_retry
send_keycode 53
sleep 0.4
wait_swift_assert no-overlay
@@ -1254,9 +1300,7 @@
wait_swift_assert fullscreen-overlay
assert_fullscreen_overlay_stable
log "==> QA fullscreen Space: quick search from appgrid does not switch Space"
- send_keycode 49
- sleep 0.4
- wait_swift_assert quick-search
+ open_appgrid_quick_search_with_retry
assert_fullscreen_overlay_stable
send_keycode 53
sleep 0.3
@@ -1288,8 +1332,7 @@
swift_assert overlay
log "==> QA 1/7 and 4/7: settings floats above appgrid and quick search"
-send_keycode 49
-sleep 0.4
+open_appgrid_quick_search_with_retry
send_cmd_comma
sleep 0.7
swift_assert settings
@@ -1308,8 +1351,7 @@
assert_frontmost_taglauncher
log "==> QA 4/7 and 5/7: appgrid-space quick search and double-Esc behavior"
-send_keycode 49
-sleep 0.4
+open_appgrid_quick_search_with_retry
send_keycode 53
sleep 0.4
swift_assert overlay
@@ -1321,9 +1363,7 @@
show_overlay
for _ in {1..5}; do
page_scroll_appgrid
- send_keycode 49
- sleep 0.35
- swift_assert quick-search
+ open_appgrid_quick_search_with_retry
send_keycode 53
sleep 0.25
swift_assert overlay
@@ -1335,8 +1375,7 @@
log "==> QA 5/7: clicking outside quick search closes search, not appgrid"
show_overlay
-send_keycode 49
-sleep 0.4
+open_appgrid_quick_search_with_retry
click_overlay_outside_quick_search
sleep 0.4
swift_assert overlay
diff --git a/TODO.md b/TODO.md
index 4e14362..1132d25 100644
--- a/TODO.md
+++ b/TODO.md
@@ -23,6 +23,12 @@
## Done
+- [2026-06-04] 修复新安装 App 后 App Grid / Quick Search 不能立即检索到的问题。
+ - 提交: 本次提交
+ - 结果: App Grid / Quick Search 打开时先轻量检查标准应用目录签名;签名未变化时不刷新也不重建界面,目录变化时才后台重扫应用索引。
+ - 范围: 不改搜索匹配规则、不改 App Grid 布局、不改窗口层级策略;只收敛应用索引刷新触发条件和 QA 脚本稳定性。
+ - 验证: 本地 `7.8.13 (20260604.1121)` targeted 输入 `notch` 命中 `TheBoringNotch` 且无“未找到”;`APP_BUILD=20260604.1121 zsh ./build.sh`、签名/版本校验、`Scripts/window_logic_qa.sh` 全部通过。
+
- [2026-06-04] 修复 Quick Search 刚打开即被过期 dismiss 事件关闭的 QA 失败。
- 提交: 本次提交
- 结果: quick-search-only 模式会忽略打开初期的过期鼠标/背板关闭事件;键盘 Esc 和再次按 `Fn + Space` 仍立即关闭;QA 脚本对合成 `Fn + Space` 投递增加重试,并把结果点击坐标修正到首条结果行中心。
--
Gitblit v1.9.3