| src/Apptag/DataLayer.swift | ●●●●● patch | view | raw | blame | history | |
| src/Apptag/Info.plist | ●●●●● patch | view | raw | blame | history | |
| src/Apptag/QuickSearch.swift | ●●●●● patch | view | raw | blame | history | |
| src/CHANGELOG.md | ●●●●● patch | view | raw | blame | history | |
| src/Scripts/quick_search_app_name_qa.sh | ●●●●● patch | view | raw | blame | history | |
| src/TODO.md | ●●●●● patch | view | raw | blame | history |
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] { 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> 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) 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 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"), 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。