Ariver
2026-06-05 e5627facd571293ebba8f05f970121716af39603
Include CoreServices apps in quick search index
3 files modified
1 files added
109 ■■■■■ changed files
Apptag/DataLayer.swift 2 ●●●●● patch | view | raw | blame | history
Apptag/Info.plist 4 ●●●● patch | view | raw | blame | history
CHANGELOG.md 6 ●●●●● patch | view | raw | blame | history
Scripts/quick_search_system_app_qa.sh 97 ●●●●● patch | view | raw | blame | history
Apptag/DataLayer.swift
@@ -109,6 +109,7 @@
    static let searchPaths: [URL] = [
        URL(fileURLWithPath: "/Applications"),
        URL(fileURLWithPath: "/System/Applications"),
        URL(fileURLWithPath: "/System/Library/CoreServices/Applications"),
        URL(fileURLWithPath: "/System/Cryptexes/App/System/Applications"),
        URL(fileURLWithPath: "/System/Volumes/Preboot/Cryptexes/App/System/Applications"),
        FileManager.default.homeDirectoryForCurrentUser
@@ -117,6 +118,7 @@
    private static let systemAppPathPrefixes = [
        "/System/Applications/",
        "/System/Library/CoreServices/Applications/",
        "/System/Cryptexes/App/System/Applications/",
        "/System/Volumes/Preboot/Cryptexes/App/System/Applications/"
    ]
Apptag/Info.plist
@@ -19,9 +19,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>7.8.13</string>
    <string>7.8.14</string>
    <key>CFBundleVersion</key>
    <string>20260604.1121</string>
    <string>20260605.0122</string>
    <key>LSApplicationCategoryType</key>
    <string>public.app-category.utilities</string>
    <key>LSMinimumSystemVersion</key>
CHANGELOG.md
@@ -1,5 +1,11 @@
# TagLauncher Changelog
## [7.8.14] — 2026-06-05
- 修复 Quick Search 和 App Grid 没有扫描 `/System/Library/CoreServices/Applications`,导致“钥匙串访问 / Keychain Access”等系统应用无法被检索和启动的问题
- 该目录纳入轻量应用目录签名检查;目录未变化时仍不会触发额外索引刷新,避免拖慢打开 App Grid / Quick Search 的体验
- 版本号更新为 `7.8.14`,Build 更新为 `20260605.0122`
## [7.8.13] — 2026-06-04
- 修复 App 运行期间新安装应用后,App Grid 和 Quick Search 仍复用旧应用索引、无法立即检索到新应用的问题
Scripts/quick_search_system_app_qa.sh
New file
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DATA_LAYER="$ROOT_DIR/Apptag/DataLayer.swift"
KEYCHAIN_APP="/System/Library/CoreServices/Applications/Keychain Access.app"
rg -Fq 'URL(fileURLWithPath: "/System/Library/CoreServices/Applications")' "$DATA_LAYER"
rg -Fq '"/System/Library/CoreServices/Applications/"' "$DATA_LAYER"
swift - "$KEYCHAIN_APP" <<'SWIFT'
import Foundation
import CoreServices
let appPath = CommandLine.arguments[1]
let appURL = URL(fileURLWithPath: appPath)
func fail(_ message: String) -> Never {
    FileHandle.standardError.write(Data("FAIL: \(message)\n".utf8))
    exit(1)
}
guard FileManager.default.fileExists(atPath: appPath) else {
    fail("Keychain Access app is missing at \(appPath)")
}
let indexedSearchPaths = [
    "/Applications",
    "/System/Applications",
    "/System/Library/CoreServices/Applications",
    "/System/Cryptexes/App/System/Applications",
    "/System/Volumes/Preboot/Cryptexes/App/System/Applications",
    FileManager.default.homeDirectoryForCurrentUser
        .appendingPathComponent("Applications")
        .path
]
guard indexedSearchPaths.contains(where: { appPath.hasPrefix($0 + "/") }) else {
    fail("Keychain Access is not under a configured app search path")
}
var names: [String] = [
    appURL.deletingPathExtension().lastPathComponent
]
if let item = MDItemCreate(nil, appPath as CFString),
   let displayName = MDItemCopyAttribute(item, kMDItemDisplayName) as? String {
    names.append(displayName)
}
if let bundle = Bundle(url: appURL),
   let loctableURL = bundle.url(forResource: "InfoPlist", withExtension: "loctable"),
   let loctable = NSDictionary(contentsOf: loctableURL) as? [String: Any],
   let zhTable = loctable["zh_CN"] as? [String: Any] {
    if let value = zhTable["CFBundleDisplayName"] as? String { names.append(value) }
    if let value = zhTable["CFBundleName"] as? String { names.append(value) }
}
func normalize(_ value: String) -> String {
    value
        .trimmingCharacters(in: .whitespacesAndNewlines)
        .folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current)
        .lowercased()
}
func pinyinCandidates(for value: String) -> [String] {
    let mutable = NSMutableString(string: value)
    CFStringTransform(mutable, nil, kCFStringTransformToLatin, false)
    CFStringTransform(mutable, nil, kCFStringTransformStripCombiningMarks, false)
    let spaced = normalize(mutable as String)
        .components(separatedBy: CharacterSet.alphanumerics.inverted)
        .filter { !$0.isEmpty }
        .joined(separator: " ")
    let compact = spaced.replacingOccurrences(of: " ", with: "")
    let initials = spaced
        .split(separator: " ")
        .compactMap(\.first)
        .map(String.init)
        .joined()
    var seen = Set<String>()
    return [spaced, compact, initials].filter { !$0.isEmpty && seen.insert($0).inserted }
}
let searchable = names.flatMap { name in
    [normalize(name)] + pinyinCandidates(for: name)
}
let queries = ["keychain", "钥匙串", "yaoshichuan", "ysc"]
for query in queries {
    let token = normalize(query)
    guard searchable.contains(where: { $0 == token || $0.hasPrefix(token) || $0.contains(token) }) else {
        fail("query \(query) did not match Keychain Access candidates: \(searchable)")
    }
}
print("PASS Keychain Access indexed search candidates: \(names.joined(separator: " | "))")
SWIFT