Ariver
2026-05-29 b7f869facbb64b6415a31af0b5c46b5b3df14800
Extract app library refresh pipeline
2 files modified
1 files added
120 ■■■■■ changed files
Apptag/AppLibraryController.swift 57 ●●●●● patch | view | raw | blame | history
Apptag/ContentView.swift 53 ●●●●● patch | view | raw | blame | history
TODO.md 10 ●●●●● patch | view | raw | blame | history
Apptag/AppLibraryController.swift
New file
@@ -0,0 +1,57 @@
import Foundation
struct AppLibrarySnapshot {
    let apps: [AppInfo]
    let quickSearchDocuments: [QuickSearchDocument]
    let tagColors: [String: Int]
    let tagOrder: [String]
}
struct AppLibraryRefreshResult {
    let snapshot: AppLibrarySnapshot
    let smartStartResult: SmartStartRunResult
}
struct AppLibrarySmartStartApplyResult {
    let snapshot: AppLibrarySnapshot
    let summary: SmartStartSummary?
}
enum AppLibraryController {
    static func refresh() -> AppLibraryRefreshResult {
        let scannedApps = AppIndexer.scan()
        let reconciledStore = TagEditor.reconcileScannedApps(scannedApps)
        let smartStartResult = SmartStartService.runIfNeeded(
            apps: scannedApps,
            store: reconciledStore
        )
        return AppLibraryRefreshResult(
            snapshot: makeSnapshot(scannedApps: scannedApps, store: smartStartResult.store),
            smartStartResult: smartStartResult
        )
    }
    static func applySmartStartSuggestion(
        _ draft: SmartCategorizationDraft,
        scannedApps: [AppInfo]
    ) -> AppLibrarySmartStartApplyResult {
        let result = SmartStartService.applySuggestion(draft)
        return AppLibrarySmartStartApplyResult(
            snapshot: makeSnapshot(scannedApps: scannedApps, store: result.store),
            summary: result.summary
        )
    }
    private static func makeSnapshot(
        scannedApps: [AppInfo],
        store: TagDatabase.Store
    ) -> AppLibrarySnapshot {
        let apps = TagEditor.annotate(apps: scannedApps, store: store)
        return AppLibrarySnapshot(
            apps: apps,
            quickSearchDocuments: QuickSearchEngine.makeDocuments(apps: apps, store: store),
            tagColors: store.tags.mapValues { $0.color },
            tagOrder: TagEditor.orderedTagNames()
        )
    }
}
Apptag/ContentView.swift
@@ -1702,23 +1702,14 @@
        let scannedApps = allApps
        DispatchQueue.global(qos: .userInitiated).async {
            let result = SmartStartService.applySuggestion(draft)
            let store = result.store
            let apps = TagEditor.annotate(apps: scannedApps, store: store)
            let quickSearchDocs = QuickSearchEngine.makeDocuments(apps: apps, store: store)
            let colors = store.tags.mapValues { $0.color }
            let order = TagEditor.orderedTagNames()
            let result = AppLibraryController.applySmartStartSuggestion(
                draft,
                scannedApps: scannedApps
            )
            DispatchQueue.main.async {
                pendingSmartStartDraft = nil
                allApps = apps
                quickSearchDocuments = quickSearchDocs
                tagColors = colors
                draggedTagNames = order
                rebuildDisplayGroups(apps: apps, tagOrder: order)
                if quickSearchVisible {
                    refreshQuickSearchResults()
                }
                applyAppLibrarySnapshot(result.snapshot)
                if let summary = result.summary {
                    showSmartStartNotice(mode: .manuallyApplied, summary: summary)
                } else {
@@ -2370,27 +2361,10 @@
        refreshInProgress = true
        DispatchQueue.global(qos: .userInitiated).async {
            let scannedApps = AppIndexer.scan()
            let reconciledStore = TagEditor.reconcileScannedApps(scannedApps)
            let smartStartResult = SmartStartService.runIfNeeded(
                apps: scannedApps,
                store: reconciledStore
            )
            let store = smartStartResult.store
            let apps = TagEditor.annotate(apps: scannedApps, store: store)
            let quickSearchDocs = QuickSearchEngine.makeDocuments(apps: apps, store: store)
            let colors = store.tags.mapValues { $0.color }
            let order = TagEditor.orderedTagNames()
            let result = AppLibraryController.refresh()
            DispatchQueue.main.async {
                allApps = apps
                quickSearchDocuments = quickSearchDocs
                tagColors = colors
                draggedTagNames = order
                rebuildDisplayGroups(apps: apps, tagOrder: order)
                if quickSearchVisible {
                    refreshQuickSearchResults()
                }
                handleSmartStartRunResult(smartStartResult)
                applyAppLibrarySnapshot(result.snapshot)
                handleSmartStartRunResult(result.smartStartResult)
                if forceLayoutRefresh {
                    finishDropRefreshAfterMinimumDuration()
                }
@@ -2405,6 +2379,17 @@
        }
    }
    private func applyAppLibrarySnapshot(_ snapshot: AppLibrarySnapshot) {
        allApps = snapshot.apps
        quickSearchDocuments = snapshot.quickSearchDocuments
        tagColors = snapshot.tagColors
        draggedTagNames = snapshot.tagOrder
        rebuildDisplayGroups(apps: snapshot.apps, tagOrder: snapshot.tagOrder)
        if quickSearchVisible {
            refreshQuickSearchResults()
        }
    }
    private func launchApp(
        _ app: AppInfo,
        closeQuickSearchOnSuccess: Bool = false,
TODO.md
@@ -13,16 +13,18 @@
## Todo
- [2026-05-28] AppLibraryController / 数据刷新管线整理。
  - 目标: 把扫描、reconcile、Smart Start、annotate、Quick Search documents 更新从 `ContentView` 中抽离。
  - 验收: 启动扫描、拖拽改标签、Quick Search 索引同步、新安装 App 后刷新。
- [2026-05-28] 设置页和 Smart Start 模块化。
  - 目标: 低频路径后置整理。
  - 验收: 语言、通用、快捷键、标签、数据、关于页都保持现有行为。
## Done
- [2026-05-29] AppLibraryController / 数据刷新管线整理。
  - 提交: 本次提交
  - 结果: 新增 `AppLibraryController` 和 `AppLibrarySnapshot`,把扫描、reconcile、Smart Start、annotate、Quick Search documents、标签颜色和标签顺序组装从 `ContentView` 的刷新路径中抽离。
  - 范围: 不改扫描、数据库、搜索匹配、拖拽改标签逻辑;`ContentView` 只应用 snapshot 并刷新 UI。
  - 验证: `bash build.sh`、`codesign --verify --deep --strict`、启动 App Grid 屏幕复核、Quick Search `sor` 索引复核、上下键复核、`Scripts/window_logic_qa.sh` 全部通过。
- [2026-05-29] OverlayWindowController 收口。
  - 提交: 本次提交
  - 结果: 新增 `OverlayWindowController`,将 overlay window 实例、generation、Space 避让、show/focus/hide 编排、panel 创建、屏幕选择、全屏/Split View 判定从 `AppDelegate` 中收口出去。