Ariver
2026-06-04 1d19c0593ebe6abe60468e12e87459ab68586603
Refresh app index when application folders change
6 files modified
150 ■■■■ changed files
Apptag/ContentView.swift 35 ●●●● patch | view | raw | blame | history
Apptag/DataLayer.swift 10 ●●●●● patch | view | raw | blame | history
Apptag/Info.plist 4 ●●●● patch | view | raw | blame | history
CHANGELOG.md 8 ●●●●● patch | view | raw | blame | history
Scripts/window_logic_qa.sh 87 ●●●● patch | view | raw | blame | history
TODO.md 6 ●●●●● patch | view | raw | blame | history
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
            }
            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)
                }
            }
        }
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() }
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>
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` 仍可立即关闭
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
}
assert_fullscreen_overlay_stable() {
open_appgrid_quick_search_with_retry() {
  local output=""
  for _ in {1..20}; do
    if ! output="$(swift "$assert_swift" fullscreen-overlay 2>&1)"; then
  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
      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
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` 投递增加重试,并把结果点击坐标修正到首条结果行中心。