From dd1c9fc4c531f1aceaa6d64bbee919ec464655b7 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 09 Jun 2026 20:09:32 +0800
Subject: [PATCH] Release 7.8.26 helper app filtering
---
src/CHANGELOG.md | 7 +++
src/TODO.md | 6 +-
src/Apptag/Info.plist | 4 +-
src/Apptag/DataLayer.swift | 30 ++++++++++++---
src/Scripts/quick_search_app_name_qa.sh | 43 +++++++++++++++++++--
src/Apptag/QuickSearch.swift | 2
6 files changed, 75 insertions(+), 17 deletions(-)
diff --git a/src/Apptag/DataLayer.swift b/src/Apptag/DataLayer.swift
index d6cd436..ccbec1a 100644
--- a/src/Apptag/DataLayer.swift
+++ b/src/Apptag/DataLayer.swift
@@ -334,7 +334,7 @@
}
}
- return deduplicated(apps).sorted {
+ return deduplicated(apps.filter { !isInternalHelperAppPath($0.path) }).sorted {
$0.name.localizedStandardCompare($1.name) == .orderedAscending
}
}
@@ -363,10 +363,10 @@
guard url.pathExtension.lowercased() == "app" else { return }
let displayURL = url.standardizedFileURL
- guard !isNestedInsideAppBundle(displayURL) else { return }
+ guard !isInternalHelperAppPath(displayURL) else { return }
let resolvedURL = displayURL.resolvingSymlinksInPath().standardizedFileURL
- guard !isNestedInsideAppBundle(resolvedURL) else { return }
+ guard !isInternalHelperAppPath(resolvedURL) else { return }
guard seenResolvedPaths.insert(resolvedURL.path).inserted else { return }
let bundle = Bundle(url: displayURL) ?? Bundle(url: resolvedURL)
@@ -624,11 +624,29 @@
}
static func isNestedInsideAppBundle(_ url: URL) -> Bool {
- let components = url.standardizedFileURL.pathComponents
- guard let lastAppIndex = components.lastIndex(where: { $0.lowercased().hasSuffix(".app") }) else {
+ isNestedInsideAppBundlePath(url.standardizedFileURL.path)
+ }
+
+ static func isInternalHelperAppPath(_ url: URL) -> Bool {
+ let path = url.standardizedFileURL.path
+ let lowercasedPath = path.lowercased()
+ return isNestedInsideAppBundlePath(path)
+ || lowercasedPath.contains("/contents/helpers/")
+ || lowercasedPath.contains("/contents/xpcservices/")
+ || lowercasedPath.contains("/wrapper/")
+ }
+
+ private static func isNestedInsideAppBundlePath(_ path: String) -> Bool {
+ let lowercasedPath = path.lowercased()
+ guard lowercasedPath.hasSuffix(".app") else { return false }
+ let components = lowercasedPath.split(separator: "/", omittingEmptySubsequences: true)
+ guard let lastAppIndex = components.lastIndex(where: { $0.hasSuffix(".app") }) else {
return false
}
- return components[..<lastAppIndex].contains { $0.lowercased().hasSuffix(".app") }
+ if components[..<lastAppIndex].contains(where: { $0.hasSuffix(".app") }) {
+ return true
+ }
+ return lowercasedPath.range(of: ".app/", options: .caseInsensitive) != nil
}
private static func deduplicated(_ apps: [AppInfo]) -> [AppInfo] {
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index e3c01a5..dbe7ddb 100644
--- a/src/Apptag/Info.plist
+++ b/src/Apptag/Info.plist
@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>7.8.25</string>
+ <string>7.8.26</string>
<key>CFBundleVersion</key>
- <string>20260609.1741</string>
+ <string>20260609.2004</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
diff --git a/src/Apptag/QuickSearch.swift b/src/Apptag/QuickSearch.swift
index 1fecfb4..ccbf454 100644
--- a/src/Apptag/QuickSearch.swift
+++ b/src/Apptag/QuickSearch.swift
@@ -295,7 +295,7 @@
enum QuickSearchEngine {
static func makeDocuments(apps: [AppInfo], store: TagDatabase.Store) -> [QuickSearchDocument] {
apps
- .filter { !AppIndexer.isNestedInsideAppBundle($0.path) }
+ .filter { !AppIndexer.isInternalHelperAppPath($0.path) }
.map { app in
let localizedNames = uniqueOrdered(AppDisplayNameResolver.searchAliases(for: app))
let internalBundleNames = internalBundleNames(for: app)
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 0257aa3..d7b078d 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -1,5 +1,12 @@
# TagLauncher Changelog
+## [7.8.26] — 2026-06-09
+
+- 加固 Quick Search wrapper/helper 过滤:除 URL 组件判断外,直接识别 `.app/.../*.app`、`/Wrapper/`、`/Contents/Helpers/`、`/Contents/XPCServices/` 等内部 app 路径,避免内层 helper 继续作为独立搜索结果显示
+- App 扫描层和 Quick Search 文档层同时应用内部 helper 过滤,确保“贝锐向日葵被控 / isunclient”这类 iOS-on-Mac wrapper 不再漏出内部英文技术名
+- Quick Search 名称 QA 增加 helper path fixture,覆盖 `/Wrapper/`、`/Contents/Helpers/` 和 XPCServices 内部 app
+- 版本号更新为 `7.8.26`,Build 更新为 `20260609.2004`
+
## [7.8.25] — 2026-06-09
- 新增统一 App 显示名解析策略:App Grid 和 Quick Search 标题优先使用当前语种的官方本地化名,再按同语系、系统/Finder/Spotlight 可见名、英文官方名和基础 bundle 名逐级 fallback
diff --git a/src/Scripts/quick_search_app_name_qa.sh b/src/Scripts/quick_search_app_name_qa.sh
index e93b47d..f3da8c8 100755
--- a/src/Scripts/quick_search_app_name_qa.sh
+++ b/src/Scripts/quick_search_app_name_qa.sh
@@ -11,9 +11,10 @@
rg -Fq 'bundleDisplayNames' "$DATA_LAYER"
rg -Fq '.localizedNameKey' "$DATA_LAYER"
rg -Fq 'isNestedInsideAppBundle' "$DATA_LAYER"
+rg -Fq 'isInternalHelperAppPath' "$DATA_LAYER"
rg -Fq '.skipsPackageDescendants' "$DATA_LAYER"
rg -Fq 'AppDisplayNameResolver.searchAliases' "$QUICK_SEARCH"
-rg -Fq 'AppIndexer.isNestedInsideAppBundle($0.path)' "$QUICK_SEARCH"
+rg -Fq 'AppIndexer.isInternalHelperAppPath($0.path)' "$QUICK_SEARCH"
swift - "$SUNLOGIN_APP" <<'SWIFT'
import Foundation
@@ -189,11 +190,29 @@
}
func isNestedInsideAppBundle(_ url: URL) -> Bool {
- let components = url.standardizedFileURL.pathComponents
- guard let lastAppIndex = components.lastIndex(where: { $0.lowercased().hasSuffix(".app") }) else {
+ isNestedInsideAppBundlePath(url.standardizedFileURL.path)
+}
+
+func isInternalHelperAppPath(_ url: URL) -> Bool {
+ let path = url.standardizedFileURL.path
+ let lowercasedPath = path.lowercased()
+ return isNestedInsideAppBundlePath(path)
+ || lowercasedPath.contains("/contents/helpers/")
+ || lowercasedPath.contains("/contents/xpcservices/")
+ || lowercasedPath.contains("/wrapper/")
+}
+
+func isNestedInsideAppBundlePath(_ path: String) -> Bool {
+ let lowercasedPath = path.lowercased()
+ guard lowercasedPath.hasSuffix(".app") else { return false }
+ let components = lowercasedPath.split(separator: "/", omittingEmptySubsequences: true)
+ guard let lastAppIndex = components.lastIndex(where: { $0.hasSuffix(".app") }) else {
return false
}
- return components[..<lastAppIndex].contains { $0.lowercased().hasSuffix(".app") }
+ if components[..<lastAppIndex].contains(where: { $0.hasSuffix(".app") }) {
+ return true
+ }
+ return lowercasedPath.range(of: ".app/", options: .caseInsensitive) != nil
}
func spotlightDisplayName(for url: URL) -> String? {
@@ -215,7 +234,7 @@
func makeDocumentPaths(appPaths: [URL]) -> [String] {
appPaths
- .filter { !isNestedInsideAppBundle($0) }
+ .filter { !isInternalHelperAppPath($0) }
.map(\.path)
}
@@ -290,11 +309,25 @@
guard isNestedInsideAppBundle(nestedURL) else {
fail("nested helper app was not classified as nested: \(nestedURL.path)")
}
+ guard isInternalHelperAppPath(nestedURL) else {
+ fail("nested helper app was not classified as internal helper: \(nestedURL.path)")
+ }
guard bundleDisplayName(for: nestedURL) == expectedDisplayName else {
fail("nested helper bundle display name did not expose localized name")
}
}
+let helperPathFixtures = [
+ "/Applications/Outer.app/Contents/Helpers/Inner.app",
+ "/Applications/Outer.app/Contents/XPCServices/Inner.app",
+ "/Applications/Outer.app/Wrapper/Inner.app"
+]
+for helperPath in helperPathFixtures {
+ guard isInternalHelperAppPath(URL(fileURLWithPath: helperPath)) else {
+ fail("helper path fixture was not filtered: \(helperPath)")
+ }
+}
+
var enumeratedHits: [String] = []
if let enumerator = FileManager.default.enumerator(
at: URL(fileURLWithPath: "/Applications"),
diff --git a/src/TODO.md b/src/TODO.md
index 81984bb..08a61ca 100644
--- a/src/TODO.md
+++ b/src/TODO.md
@@ -24,10 +24,10 @@
## Done
- [2026-06-09] 多语种 App 显示名通用加固。
- - 结果: 新增统一 `AppDisplayNameResolver`;App Grid 与 Quick Search 标题优先走当前语种/同语系/系统可见名/英文官方名/基础 bundle 名 fallback,内部 bundle 名和 bundle id 只作为搜索别名。
- - 范围: `AppInfo` 增加系统显示名与 bundle 显示名候选;Quick Search 文档使用 resolver 生成别名并二次过滤嵌套 `.app`;版本更新为 `7.8.25 (20260609.1741)`。
+ - 结果: 新增统一 `AppDisplayNameResolver`;App Grid 与 Quick Search 标题优先走当前语种/同语系/系统可见名/英文官方名/基础 bundle 名 fallback,内部 bundle 名和 bundle id 只作为搜索别名;`7.8.26` 进一步加固 `/Wrapper/`、`/Contents/Helpers/`、`.app/.../*.app` 内部 helper 过滤。
+ - 范围: `AppInfo` 增加系统显示名与 bundle 显示名候选;App 扫描层和 Quick Search 文档层都过滤内部 helper;Quick Search 文档使用 resolver 生成别名;版本更新为 `7.8.26 (20260609.2004)`。
- 不改范围: 未做第三方 App 名称机器翻译,未改 Quick Search 排名算法、App Grid 布局、窗口层级或 Smart Start 分类规则。
- - 验证: `APP_BUILD=20260609.1741 bash build.sh`、`codesign --verify --deep --strict src/build/TagLauncher.app`、`Scripts/quick_search_app_name_qa.sh`、`Scripts/quick_search_system_app_qa.sh` 通过;构建产物 Info.plist 为 `7.8.25 (20260609.1741)`。
+ - 验证: `APP_BUILD=20260609.2004 bash build.sh`、`codesign --verify --deep --strict src/build/TagLauncher.app`、`Scripts/quick_search_app_name_qa.sh`、`Scripts/quick_search_system_app_qa.sh` 通过;构建产物 Info.plist 为 `7.8.26 (20260609.2004)`。
- [2026-06-09] 修复首安 Smart Start 提示确认后 App Grid 消失的问题。
- 结果: Smart Start 自动整理完成提示出现期间会抑制 AppKit 级 backdrop dismiss;点击“OK/好的”只关闭提示弹窗,不再关闭整个 App Grid。
--
Gitblit v1.9.3