Ariver
2026-05-29 fb4eaa3834c5c62c5421dc67c3cdb546b1a5a0f9
Modularize settings smart start paths
5 files modified
1 files added
240 ■■■■ changed files
Apptag/AppLibraryController.swift 14 ●●●●● patch | view | raw | blame | history
Apptag/ContentView.swift 102 ●●●●● patch | view | raw | blame | history
Apptag/PreferencesView.swift 10 ●●●●● patch | view | raw | blame | history
Apptag/SmartStartNoticeOverlay.swift 99 ●●●●● patch | view | raw | blame | history
Scripts/window_logic_qa.sh 5 ●●●●● patch | view | raw | blame | history
TODO.md 10 ●●●●● patch | view | raw | blame | history
Apptag/AppLibraryController.swift
@@ -17,6 +17,11 @@
    let summary: SmartStartSummary?
}
struct AppLibrarySystemSchemeApplyResult {
    let snapshot: AppLibrarySnapshot
    let summary: SmartStartSummary?
}
enum AppLibraryController {
    static func refresh() -> AppLibraryRefreshResult {
        let scannedApps = AppIndexer.scan()
@@ -42,6 +47,15 @@
        )
    }
    static func applySystemInitialScheme() -> AppLibrarySystemSchemeApplyResult {
        let scannedApps = AppIndexer.scan(useCache: false)
        let result = SmartStartService.applySystemInitialScheme(apps: scannedApps)
        return AppLibrarySystemSchemeApplyResult(
            snapshot: makeSnapshot(scannedApps: scannedApps, store: result.store),
            summary: result.summary
        )
    }
    private static func makeSnapshot(
        scannedApps: [AppInfo],
        store: TagDatabase.Store
Apptag/ContentView.swift
@@ -340,20 +340,6 @@
    let tagName: String
}
private enum SmartStartNoticeMode {
    case autoApplied
    case suggestionOnly
    case manuallyApplied
}
private struct SmartStartNotice: Identifiable {
    let id = UUID()
    let mode: SmartStartNoticeMode
    let title: String
    let message: String
    let summary: SmartStartSummary
}
struct ContentView: View {
    let hideOverlay: () -> Void
    private let initialQuickSearchSource: String?
@@ -1100,88 +1086,12 @@
    }
    private var smartStartNoticeOverlay: some View {
        GeometryReader { proxy in
            if let notice = smartStartNotice {
                let panelWidth = min(560, max(320, proxy.size.width - 64))
                ZStack {
                    Color.black.opacity(0.10)
                        .ignoresSafeArea()
                    VStack(spacing: 18) {
                        Image(systemName: notice.mode == .suggestionOnly ? "sparkles" : "checkmark.seal.fill")
                            .font(.system(size: 30, weight: .semibold))
                            .foregroundStyle(Color.accentColor)
                            .frame(width: 58, height: 58)
                            .background(
                                Circle()
                                    .fill(Color.accentColor.opacity(0.12))
                            )
                        VStack(spacing: 8) {
                            Text(notice.title)
                                .font(.system(size: 21, weight: .semibold))
                                .foregroundStyle(.primary)
                                .multilineTextAlignment(.center)
                            Text(notice.message)
                                .font(.system(size: 15, weight: .regular))
                                .foregroundStyle(.secondary)
                                .multilineTextAlignment(.center)
                                .lineLimit(4)
                                .fixedSize(horizontal: false, vertical: true)
                        }
                        HStack(spacing: 12) {
                            switch notice.mode {
                            case .suggestionOnly:
                                Button(tr("smartstart.later")) {
                                    dismissSmartStartNotice()
                                }
                                .buttonStyle(.bordered)
                                .controlSize(.large)
                                Button(tr("smartstart.apply")) {
                                    applySmartStartSuggestion()
                                }
                                .buttonStyle(.borderedProminent)
                                .controlSize(.large)
                            case .autoApplied, .manuallyApplied:
                                if notice.summary.backupPath != nil {
                                    Button(tr("smartstart.undo")) {
                                        undoSmartStart()
                                    }
                                    .buttonStyle(.bordered)
                                    .controlSize(.large)
                                }
                                Button(tr("smartstart.ok")) {
                                    dismissSmartStartNotice()
                                }
                                .buttonStyle(.borderedProminent)
                                .controlSize(.large)
                            }
                        }
                    }
                    .padding(.horizontal, 30)
                    .padding(.vertical, 26)
                    .frame(width: panelWidth)
                    .background(
                        RoundedRectangle(cornerRadius: 16)
                            .fill(.ultraThickMaterial)
                            .shadow(color: .black.opacity(0.26), radius: 28, y: 16)
                    )
                    .overlay(
                        RoundedRectangle(cornerRadius: 16)
                            .stroke(.white.opacity(0.16), lineWidth: 1)
                    )
                }
                .frame(width: proxy.size.width, height: proxy.size.height)
                .transition(.scale(scale: 0.96).combined(with: .opacity))
                .zIndex(650)
            }
        }
        .ignoresSafeArea()
        .allowsHitTesting(smartStartNotice != nil)
        SmartStartNoticeOverlay(
            notice: smartStartNotice,
            onDismiss: dismissSmartStartNotice,
            onApply: applySmartStartSuggestion,
            onUndo: undoSmartStart
        )
    }
    // MARK: - Edit Tags View
Apptag/PreferencesView.swift
@@ -271,16 +271,12 @@
        isApplyingSystemScheme = true
        DispatchQueue.global(qos: .userInitiated).async {
            let scannedApps = AppIndexer.scan(useCache: false)
            let result = SmartStartService.applySystemInitialScheme(apps: scannedApps)
            let store = result.store
            let apps = TagEditor.annotate(apps: scannedApps, store: store)
            let colors = store.tags.mapValues { $0.color }
            let result = AppLibraryController.applySystemInitialScheme()
            DispatchQueue.main.async {
                isApplyingSystemScheme = false
                allApps = apps
                tagColors = colors
                allApps = result.snapshot.apps
                tagColors = result.snapshot.tagColors
                refreshDataState()
                notifyDataChanged()
Apptag/SmartStartNoticeOverlay.swift
New file
@@ -0,0 +1,99 @@
import SwiftUI
enum SmartStartNoticeMode {
    case autoApplied
    case suggestionOnly
    case manuallyApplied
}
struct SmartStartNotice: Identifiable {
    let id = UUID()
    let mode: SmartStartNoticeMode
    let title: String
    let message: String
    let summary: SmartStartSummary
}
struct SmartStartNoticeOverlay: View {
    let notice: SmartStartNotice?
    let onDismiss: () -> Void
    let onApply: () -> Void
    let onUndo: () -> Void
    var body: some View {
        GeometryReader { proxy in
            if let notice {
                let panelWidth = min(560, max(320, proxy.size.width - 64))
                ZStack {
                    Color.black.opacity(0.10)
                        .ignoresSafeArea()
                    VStack(spacing: 18) {
                        Image(systemName: notice.mode == .suggestionOnly ? "sparkles" : "checkmark.seal.fill")
                            .font(.system(size: 30, weight: .semibold))
                            .foregroundStyle(Color.accentColor)
                            .frame(width: 58, height: 58)
                            .background(
                                Circle()
                                    .fill(Color.accentColor.opacity(0.12))
                            )
                        VStack(spacing: 8) {
                            Text(notice.title)
                                .font(.system(size: 21, weight: .semibold))
                                .foregroundStyle(.primary)
                                .multilineTextAlignment(.center)
                            Text(notice.message)
                                .font(.system(size: 15, weight: .regular))
                                .foregroundStyle(.secondary)
                                .multilineTextAlignment(.center)
                                .lineLimit(4)
                                .fixedSize(horizontal: false, vertical: true)
                        }
                        HStack(spacing: 12) {
                            switch notice.mode {
                            case .suggestionOnly:
                                Button(tr("smartstart.later"), action: onDismiss)
                                    .buttonStyle(.bordered)
                                    .controlSize(.large)
                                Button(tr("smartstart.apply"), action: onApply)
                                    .buttonStyle(.borderedProminent)
                                    .controlSize(.large)
                            case .autoApplied, .manuallyApplied:
                                if notice.summary.backupPath != nil {
                                    Button(tr("smartstart.undo"), action: onUndo)
                                        .buttonStyle(.bordered)
                                        .controlSize(.large)
                                }
                                Button(tr("smartstart.ok"), action: onDismiss)
                                    .buttonStyle(.borderedProminent)
                                    .controlSize(.large)
                            }
                        }
                    }
                    .padding(.horizontal, 30)
                    .padding(.vertical, 26)
                    .frame(width: panelWidth)
                    .background(
                        RoundedRectangle(cornerRadius: 16)
                            .fill(.ultraThickMaterial)
                            .shadow(color: .black.opacity(0.26), radius: 28, y: 16)
                    )
                    .overlay(
                        RoundedRectangle(cornerRadius: 16)
                            .stroke(.white.opacity(0.16), lineWidth: 1)
                    )
                }
                .frame(width: proxy.size.width, height: proxy.size.height)
                .transition(.scale(scale: 0.96).combined(with: .opacity))
                .zIndex(650)
            }
        }
        .ignoresSafeArea()
        .allowsHitTesting(notice != nil)
    }
}
Scripts/window_logic_qa.sh
@@ -1143,8 +1143,9 @@
SWIFT
  done < <(swift "$screens_swift")
else
  rg -Fq 'screenContainingCurrentPointer() ??' "$ROOT_DIR/Apptag/ApptagApp.swift"
  rg -Fq 'NSMouseInRect(mousePoint, $0.frame, false)' "$ROOT_DIR/Apptag/ApptagApp.swift"
  rg -Fq 'statusMenuScreenForNextOverlay = overlayController.screenContainingCurrentPointer()' "$ROOT_DIR/Apptag/ApptagApp.swift"
  rg -Fq 'screenContainingCurrentPointer() ??' "$ROOT_DIR/Apptag/OverlayWindowController.swift"
  rg -Fq 'NSMouseInRect(mousePoint, $0.frame, false)' "$ROOT_DIR/Apptag/OverlayWindowController.swift"
  log "PASS screen-following static path: single physical display here; code selects NSScreen under current pointer"
fi
TODO.md
@@ -13,12 +13,14 @@
## Todo
- [2026-05-28] 设置页和 Smart Start 模块化。
  - 目标: 低频路径后置整理。
  - 验收: 语言、通用、快捷键、标签、数据、关于页都保持现有行为。
## Done
- [2026-05-29] 设置页和 Smart Start 模块化。
  - 提交: 本次提交
  - 结果: `SmartStartNoticeOverlay` 已从 `ContentView` 拆出;设置页“应用系统智能化初始分类”的扫描、Smart Start 应用和 snapshot 组装改由 `AppLibraryController` 统一处理;窗口 QA 脚本同步适配 `OverlayWindowController` 的屏幕选择静态检查。
  - 范围: 不改设置页 UI 布局、不改 Smart Start 分类规则、不改导入/导出/恢复行为。
  - 验证: `bash build.sh`、`codesign --verify --deep --strict`、设置页语言/通用/快捷键/标签/数据/关于六个 tab 屏幕复核、`Scripts/window_logic_qa.sh` 全部通过。
- [2026-05-29] AppLibraryController / 数据刷新管线整理。
  - 提交: 本次提交
  - 结果: 新增 `AppLibraryController` 和 `AppLibrarySnapshot`,把扫描、reconcile、Smart Start、annotate、Quick Search documents、标签颜色和标签顺序组装从 `ContentView` 的刷新路径中抽离。