src/Apptag/Info.plist、src/CHANGELOG.md、src/Release/AppStore-7.9.1-20260614.1454/QA_RELEASE_EVIDENCE.mdTagLauncher 的用户数据集中在 TagDatabase.Store,默认保存到:
~/Library/Application Support/TagLauncher/tags.json
导入、导出、Smart Start 备份和分类方案自动快照都围绕这个 Store 进行。
| 字段 | 含义 |
|---|---|
tags |
标签名到 TagDef 的映射,包含颜色和可选系统分类 ID |
appTags |
App path 到标签名数组的映射 |
tagOrder |
用户自定义标签展示顺序 |
containerAppOrder |
稳定容器 ID 到 App path 顺序数组的映射 |
uncommonAppPaths |
不常用 App 标记 |
uncommonSources |
不常用来源:自动或手动 |
appOpenCounts |
从 TagLauncher 成功打开 App 的次数 |
appLastOpenedAt |
从 TagLauncher 最近打开时间 |
knownAppPaths |
已知 App 基线,用于识别新增/删除 |
appNotes |
App path 到备注文本 |
appNoteMetadata |
备注来源和 fingerprint,保护默认备注/手动备注边界 |
disabledSystemCategoryIDs |
用户删除过的系统分类,Smart Start 不应擅自恢复 |
smartStart |
Smart Start catalog 运行、应用、备份状态 |
categoryScheme |
当前/上一套分类方案的名称、时间和备份路径 |
备注最长由 TagDatabase.maxAppNoteLength=80 控制。
AppNoteOrigin 有三类:
catalogDefault:Smart Start catalog 默认备注。appleDefault:Apple 默认应用 catalog 备注。manual:用户手动编辑或清空。每个默认备注都记录 noteFingerprint。语言切换或 catalog 重本地化时,只有当前备注仍匹配原默认值 fingerprint,才会替换为新语言备注。只要用户改过或清空,origin 变为 manual,系统不再补回。
这是防止“用户手动备注被语言切换覆盖”的核心机制。
TagEditor 是 Store 写操作门面:
createTag:创建标签和颜色,插入 tagOrder。renameTag:重命名标签定义、tagOrder 和所有 App assignment。deleteTagCompletely:删除标签定义、tagOrder 和 App assignment;如果删除系统分类,记录到 disabledSystemCategoryIDs。setColor:更新标签颜色。reorderTags:保存标签顺序。reorderApps:保存某个稳定容器内的 App 顺序。assignTag / appendTags / removeTags / setTags:维护 App 标签集合。moveApp:拖拽 App 从一个组移动/复制到另一个组。setAppNote:写备注,空备注也记录 manual metadata。除了首次 seed 和部分 reconcile 场景,用户分类相关修改通常走 TagDatabase.saveUserCategorySchemeMutation,以便自动创建上一套方案快照。
TagLauncher 把“标签体系”视为一个 category scheme。会被纳入 fingerprint 的字段包括:
tagsappTagstagOrdercontainerAppOrderuncommonAppPathsuncommonSourcesdisabledSystemCategoryIDs当这些字段发生用户侧变化时,saveUserCategorySchemeMutation 会:
CategorySchemeBackups。为避免连续拖拽/批量编辑创建大量备份,有 90 秒 batch debounce。overlay 关闭、退出编辑模式、导入导出和退出 App 时会 flush pending batch。
7.9.x 起,用户可以在同一 App Grid 容器内拖动 App 调整顺序。顺序保存在 TagDatabase.Store.containerAppOrder,key 不是本地化后的容器名称,而是稳定容器 ID:
__container.uncategorized:未分类容器。__container.appleBuiltIn:Apple 内置容器。tag:<tagName>:普通用户标签容器。system:<SmartCategoryID>:Smart Start 系统分类容器。排序写入路径是:
AppGridCollectionView 根据拖拽位置计算目标插入点。ContentView.reorderApps(inContainer:orderedPaths:) 先更新内存态 containerAppOrder 并重建分组。TagEditor.reorderApps(inContainer:orderedPaths:) 归一化路径、过滤空值和重复值。TagDatabase.saveUserCategorySchemeMutation 保存并纳入上一套分类方案快照。读取路径是 AppIndexer.group(... containerAppOrder:)。它会先按当前 app/tag 关系过滤排序数组,只保留仍属于该容器且仍存在的 App;排序数组以外的新 App 按显示名自然排序追加在后面。
标签重命名或系统分类 key 变化时必须调用 migrateContainerAppOrderKey 合并旧 key;删除标签或重置为未分类时要清理相关排序,避免孤儿顺序污染后续布局。
ContentView 有三种 edit phase:
.none.editingTags.editingApps进入编辑态时,会同步发送 .tagLauncherEditModeChanged,AppDelegate 用它抑制 overlay 自动关闭。
editingTags 复用 TagEditorView,支持标签新增、重命名、删除、颜色调整。
editingApps 支持:
App Grid 的拖拽不是系统 NSDraggingSession,而是自定义 AppDragCoordinator:
AppGridIconNSView.mouseDown 启动 0.5 秒长按计时。AppDragCoordinator.beginDrag 创建 CALayer 或 fallback drag window。dropApp。dropAppOnTagNavigation。dropAppOutsideGroup。拖到普通标签组会调用 TagEditor.moveApp。按 Option 时是 copy,不移除来源标签。
拖到标签导航只追加目标标签,不删除原有标签。
拖到“未分类”或空白区域移除标签时,可能弹确认。用户可选择不再提醒,状态存到 skipUncategorizedDropConfirm 或 skipTagRemovalDropConfirm。
不常用 App 是特殊标记,不是普通标签:
TagDatabase.uncommonTagKey = "__system.uncommon"tr("group.uncommon")uncommonAppPaths 和 uncommonSources新增普通 App 默认自动标记为不常用;熟悉 Apple App 例外。自动不常用 App 被从 TagLauncher 打开达到 autoUncommonOpenThreshold=100 后,会自动移除不常用标记。用户手动标记的不会被自动移除。
PreferencesView 的 Data tab 提供:
TagDatabase.exportToTagDatabase.importFromAppLibraryController.applySystemInitialSchemeSmartStartService.restoreBackup导入时会:
.tagLauncherDataDidChange。TagEditor 直接改 TagDatabase.Store,除非是 reconcile、Smart Start 或 import/export 这种受控路径。disabledSystemCategoryIDs,否则 Smart Start 可能重新创建用户删除的分类。appNoteMetadata,否则语言切换可能误覆盖用户备注。AppDragCoordinator.shared 的全局状态,任何异常退出路径都要调用 cancelDrag 或重置 transient drag state。