edit | blame | history | raw

标签数据模型与编辑流

  • 整理日期:2026-06-11
  • 复核日期:2026-06-15
  • 依据产品版本:TagLauncher 7.9.1,Build 20260614.1454
  • 版本依据:src/Apptag/Info.plistsrc/CHANGELOG.mdsrc/Release/AppStore-7.9.1-20260614.1454/QA_RELEASE_EVIDENCE.md
  • 配套图示:05-标签数据模型与编辑流.drawio

本地数据文件

TagLauncher 的用户数据集中在 TagDatabase.Store,默认保存到:

~/Library/Application Support/TagLauncher/tags.json

导入、导出、Smart Start 备份和分类方案自动快照都围绕这个 Store 进行。

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,系统不再补回。

这是防止“用户手动备注被语言切换覆盖”的核心机制。

标签 CRUD

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 的字段包括:

  • tags
  • appTags
  • tagOrder
  • containerAppOrder
  • uncommonAppPaths
  • uncommonSources
  • disabledSystemCategoryIDs

当这些字段发生用户侧变化时,saveUserCategorySchemeMutation 会:

  1. 比较当前和 previous fingerprint。
  2. 如有变化,记录自动 previous category scheme。
  3. 备份 previous store 到 CategorySchemeBackups
  4. 更新当前方案名称和时间。
  5. 执行保存。

为避免连续拖拽/批量编辑创建大量备份,有 90 秒 batch debounce。overlay 关闭、退出编辑模式、导入导出和退出 App 时会 flush pending batch。

容器内 App 排序

7.9.x 起,用户可以在同一 App Grid 容器内拖动 App 调整顺序。顺序保存在 TagDatabase.Store.containerAppOrder,key 不是本地化后的容器名称,而是稳定容器 ID:

  • __container.uncategorized:未分类容器。
  • __container.appleBuiltIn:Apple 内置容器。
  • tag:<tagName>:普通用户标签容器。
  • system:<SmartCategoryID>:Smart Start 系统分类容器。

排序写入路径是:

  1. AppGridCollectionView 根据拖拽位置计算目标插入点。
  2. ContentView.reorderApps(inContainer:orderedPaths:) 先更新内存态 containerAppOrder 并重建分组。
  3. TagEditor.reorderApps(inContainer:orderedPaths:) 归一化路径、过滤空值和重复值。
  4. TagDatabase.saveUserCategorySchemeMutation 保存并纳入上一套分类方案快照。

读取路径是 AppIndexer.group(... containerAppOrder:)。它会先按当前 app/tag 关系过滤排序数组,只保留仍属于该容器且仍存在的 App;排序数组以外的新 App 按显示名自然排序追加在后面。

标签重命名或系统分类 key 变化时必须调用 migrateContainerAppOrderKey 合并旧 key;删除标签或重置为未分类时要清理相关排序,避免孤儿顺序污染后续布局。

App Grid 编辑模式

ContentView 有三种 edit phase:

  • .none
  • .editingTags
  • .editingApps

进入编辑态时,会同步发送 .tagLauncherEditModeChangedAppDelegate 用它抑制 overlay 自动关闭。

editingTags 复用 TagEditorView,支持标签新增、重命名、删除、颜色调整。

editingApps 支持:

  • 选择多个 App。
  • 选择 add/remove 模式。
  • 勾选标签。
  • 批量追加或移除标签。
  • 删除模式下自动根据已选 App 同步可移除标签。
  • 操作后显示反馈气泡,并刷新 snapshot。

拖拽流

App Grid 的拖拽不是系统 NSDraggingSession,而是自定义 AppDragCoordinator

  1. AppGridIconNSView.mouseDown 启动 0.5 秒长按计时。
  2. 长按后进入 app drag mode,隐藏 hover bubble。
  3. 拖动距离超过阈值后,AppDragCoordinator.beginDrag 创建 CALayer 或 fallback drag window。
  4. 拖动过程中根据 screen point 命中最小面积 drop target。
  5. 松手后:
  • 命中 group card:dropApp
  • 命中 tag navigation button:dropAppOnTagNavigation
  • 命中空白容器区域:dropAppOutsideGroup

拖到普通标签组会调用 TagEditor.moveApp。按 Option 时是 copy,不移除来源标签。

拖到标签导航只追加目标标签,不删除原有标签。

拖到“未分类”或空白区域移除标签时,可能弹确认。用户可选择不再提醒,状态存到 skipUncategorizedDropConfirmskipTagRemovalDropConfirm

不常用 App

不常用 App 是特殊标记,不是普通标签:

  • key:TagDatabase.uncommonTagKey = "__system.uncommon"
  • UI 显示:tr("group.uncommon")
  • 数据字段:uncommonAppPathsuncommonSources

新增普通 App 默认自动标记为不常用;熟悉 Apple App 例外。自动不常用 App 被从 TagLauncher 打开达到 autoUncommonOpenThreshold=100 后,会自动移除不常用标记。用户手动标记的不会被自动移除。

导入导出与恢复

PreferencesView 的 Data tab 提供:

  • 导出:TagDatabase.exportTo
  • 导入:TagDatabase.importFrom
  • 应用系统初始方案:AppLibraryController.applySystemInitialScheme
  • 恢复上一套方案:SmartStartService.restoreBackup

导入时会:

  1. 读取 JSON。
  2. 应用 imported category scheme metadata。
  3. 给当前 store 创建 previous snapshot。
  4. 保存新 store。
  5. 刷新 App 列表并发送 .tagLauncherDataDidChange

修改风险提示

  • 不要绕过 TagEditor 直接改 TagDatabase.Store,除非是 reconcile、Smart Start 或 import/export 这种受控路径。
  • 删除系统标签时必须维护 disabledSystemCategoryIDs,否则 Smart Start 可能重新创建用户删除的分类。
  • 备注修改要同步写 appNoteMetadata,否则语言切换可能误覆盖用户备注。
  • 拖拽流依赖 AppDragCoordinator.shared 的全局状态,任何异常退出路径都要调用 cancelDrag 或重置 transient drag state。