From b7f869facbb64b6415a31af0b5c46b5b3df14800 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 29 May 2026 05:23:24 +0800
Subject: [PATCH] Extract app library refresh pipeline

---
 TODO.md                           |   10 +++--
 Apptag/ContentView.swift          |   53 +++++++++-----------------
 Apptag/AppLibraryController.swift |   57 ++++++++++++++++++++++++++++
 3 files changed, 82 insertions(+), 38 deletions(-)

diff --git a/Apptag/AppLibraryController.swift b/Apptag/AppLibraryController.swift
new file mode 100644
index 0000000..7737960
--- /dev/null
+++ b/Apptag/AppLibraryController.swift
@@ -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()
+        )
+    }
+}
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index 900cf4d..033efcc 100644
--- a/Apptag/ContentView.swift
+++ b/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,
diff --git a/TODO.md b/TODO.md
index ce466f2..757d5d5 100644
--- a/TODO.md
+++ b/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` 中收口出去。

--
Gitblit v1.9.3