Ariver
2026-06-11 f7495650aa7067dfbb587a1af76c90b12bc5a40a
Add 7.9 app ordering planning docs
1 files modified
3 files added
513 ■■■■■ changed files
docs/7.90/03-产品PRD.md 125 ●●●●● patch | view | raw | blame | history
docs/7.90/04-QA测试用例集.md 123 ●●●●● patch | view | raw | blame | history
docs/7.90/05-技术设计方案.md 262 ●●●●● patch | view | raw | blame | history
docs/7.90/README.md 3 ●●●●● patch | view | raw | blame | history
docs/7.90/03-产品PRD.md
New file
@@ -0,0 +1,125 @@
# 7.9.0 产品 PRD:容器内 App 手动排序
## 1. 背景
TagLauncher 当前支持按标签/容器展示 App,也支持拖动 App 改变标签归属。但在同一个容器内,App 的显示顺序主要来自扫描后的默认名称排序,用户无法把高频 App 固定到自己习惯的位置。
对重度用户来说,容器不仅是分类,也是工作流入口。用户希望像整理桌面图标一样,在每个容器中把 App 摆成自己的顺序,并且这个顺序能随“分类与布局”一起保存、导入、恢复。
## 2. 目标
- 用户可以在同一个容器/分组内拖动 App,改变该容器内显示顺序。
- 自定义顺序在刷新、重启、语言切换、导入导出、恢复上一方案、SmartStart 回滚后保持一致。
- 不破坏现有跨容器拖动改标签、Option 复制、拖到空白移除标签、Apple 内置组保护等既有行为。
- 将该需求作为 `7.9.0` 大版本需求管理。
## 3. 用户价值
- 高频 App 可以放到容器最前面,减少寻找成本。
- 每个标签/容器可以形成独立工作流顺序。
- 多标签 App 在不同容器里可以有不同位置,符合不同使用语境。
- 导出“分类与布局”时真正包含布局顺序,便于迁移和恢复。
## 4. 适用用户
- 安装 App 数量较多、依赖 TagLauncher 做日常启动入口的用户。
- 已经维护多个标签/容器,并希望在容器内进一步精细整理的用户。
- 需要在多台 Mac 或重装后恢复分类与布局的用户。
## 5. 范围
### 5.1 版本 7.9.0 必做
- 同一容器内拖动 App 改变顺序。
- 每个容器独立保存顺序。
- 多标签 App 在不同容器中的顺序互不影响。
- 新 App 或未记录 App 在自定义顺序之后按默认规则显示。
- 删除/卸载 App 后不显示空位、不崩溃。
- 顺序纳入分类与布局数据,可导出、导入、恢复。
- 旧用户数据无顺序字段时保持原有默认排序。
### 5.2 版本 7.9.0 不做
- 不做跨容器插入位置排序。
- 不改变跨容器拖动的标签归属语义。
- 不改变 Quick Search 结果排序。
- 不提供“按名称重新排序”“重置单个容器顺序”等高级管理入口。
- 不做跨机器 bundle id/name fallback 的完整稳定匹配;第一版以 path 为 App 顺序身份。
## 6. 核心交互
### 6.1 同容器排序
用户在 App Grid 普通展示态中,长按并拖动某个 App 到同一容器中的另一个位置。
期望:
- 拖动过程中显示清晰的插入位置提示。
- 松手后该 App 移动到目标位置。
- 不改变该 App 的标签归属。
- 不触发 App 启动。
### 6.2 跨容器拖动
用户把 App 拖到其他容器/标签。
期望:
- 沿用现有移动/复制标签逻辑。
- 按住 Option 时继续执行复制标签语义。
- 不把跨容器拖动解释为排序。
### 6.3 拖到空白区域
用户把 App 拖到容器外空白区域。
期望:
- 继续沿用现有移除来源标签或归为未分类确认逻辑。
- 不因为新增排序功能改变提醒、确认或“不再提醒”逻辑。
### 6.4 特殊容器
- “未分类”容器:第一版允许读取排序;是否开放拖动排序按实现风险在开发阶段确认。
- “Mac 自带 / Apple 内置”容器:保持现有保护逻辑,不允许通过拖动改变系统标签归属;排序是否开放需单独评估,默认不作为第一优先级。
## 7. 数据行为
- 自定义顺序属于“分类与布局方案”。
- 导出分类与布局时必须包含顺序。
- 导入分类与布局时必须恢复顺序。
- 恢复上一方案时必须恢复顺序。
- SmartStart 应用新方案前的备份必须包含顺序。
- “重置为未分类”应清理旧容器顺序,避免旧布局在未来意外复活。
## 8. 兼容性要求
- macOS 14 起可用。
- 旧 `tags.json` 无新增字段时可正常读取。
- 旧版本 App 读取新 JSON 时不应破坏已知字段;新版本 App 重新读取后仍应尽量保留顺序。
- 语言切换不影响顺序,因为顺序 key 不能依赖本地化显示名。
## 9. 验收标准
- 用户可在同容器内完成拖动排序。
- 排序结果立即显示。
- 关闭并重新打开 App Grid 后顺序保持。
- 重启 App 后顺序保持。
- 切换语言后顺序保持。
- 导出再导入后顺序保持。
- 恢复上一方案后顺序恢复。
- 跨容器拖动、Option 复制、拖到空白、Apple 内置保护保持现有行为。
- 旧数据用户升级后默认顺序不变。
## 10. 发布要求
- 版本号从 `7.9.0` 开始。
- `CHANGELOG.md` 必须记录数据模型、交互范围和兼容性说明。
- 发布前必须有 QA 报告,覆盖数据层、UI 手工拖拽、macOS 14、导入导出、恢复、SmartStart 回归。
## 11. 未决问题
- “未分类”容器是否开放拖动排序。
- “Mac 自带 / Apple 内置”容器是否开放仅显示顺序排序。
- 是否需要在后续版本提供“重置当前容器顺序”入口。
- 是否需要在后续版本支持跨机器导入时基于 bundle id/name 匹配顺序。
docs/7.90/04-QA测试用例集.md
New file
@@ -0,0 +1,123 @@
# 7.9.0 QA 测试用例集:容器内 App 手动排序
## 1. 测试目标
验证用户可以在同一容器内拖动 App 改变显示顺序,并确保顺序在刷新、重启、语言切换、导入导出、恢复、SmartStart 等路径中保持一致,同时不破坏现有跨容器拖动改标签行为。
## 2. 测试范围
- 数据模型兼容性。
- 同容器拖动排序。
- 旧拖拽行为回归。
- 多显示模式和标签位置。
- 导入导出、恢复、重置、SmartStart。
- macOS 14 兼容性。
## 3. 测试环境
- macOS 14 Apple Silicon 真机。
- 当前开发机最新 macOS。
- 干净用户数据一份。
- 旧版 `tags.json` fixture 一份。
- 含多标签 App、未分类 App、Apple 内置 App、已卸载 path 的测试数据一份。
## 4. 自动化用例
| ID | 优先级 | 类型 | 用例 | 期望结果 |
| --- | --- | --- | --- | --- |
| DATA-001 | P0 | 自动 | 旧 `tags.json` 无 `containerAppOrder` 字段时解码 | 加载成功,默认顺序与旧版本一致 |
| DATA-002 | P0 | 自动 | 新 `tags.json` 含 `containerAppOrder` 字段时解码 | 加载成功,顺序字段保留 |
| DATA-003 | P0 | 自动 | 导出后再导入含顺序的分类与布局 | 导入后每个容器顺序一致 |
| DATA-004 | P0 | 自动 | `CategorySchemeFingerprint` 包含顺序字段 | 只改变顺序也能触发上一方案快照 |
| DATA-005 | P0 | 自动 | 恢复上一方案 | 标签、App 归属、容器内顺序都恢复 |
| DATA-006 | P0 | 自动 | 重置为未分类 | 清空旧普通标签归属,并按既定策略清理旧容器顺序 |
| DATA-007 | P0 | 自动 | SmartStart replace | 新方案顺序可预测,不复用旧容器顺序 |
| DATA-008 | P1 | 自动 | tag rename/delete/relocalize | 顺序 key 正确迁移或清理 |
| DATA-009 | P1 | 自动 | 卸载 App path 出现在顺序字段 | 不显示空位,不崩溃,必要时懒清理 |
| DATA-010 | P1 | 自动 | 新 App 不在顺序字段内 | 已排序 App 保持,新增 App 追加并按默认名称排序 |
## 5. 手工功能用例
| ID | 优先级 | 用例 | 步骤 | 期望结果 |
| --- | --- | --- | --- | --- |
| UI-001 | P0 | 同容器拖动到最前 | 在某容器中把第 3 个 App 拖到第 1 个位置 | App 顺序立即改变,不启动 App |
| UI-002 | P0 | 同容器拖动到中间 | 把最后一个 App 拖到中间位置 | 插入位置正确,顺序保存 |
| UI-003 | P0 | 跨行/跨列拖动 | 在网格容器中跨行拖动 App | 目标位置正确,无错位 |
| UI-004 | P0 | 拖回原位 | 长按拖动后放回原位置 | 不写入无意义变更,不闪烁 |
| UI-005 | P0 | 松手未移动 | 长按 App 后直接松手 | 不启动 App,不改变顺序 |
| UI-006 | P0 | 多标签 App 独立顺序 | App 同时在 A/B 容器,重排 A | A 顺序改变,B 顺序不变 |
| UI-007 | P0 | 重开 App Grid | 排序后关闭再打开 App Grid | 顺序保持 |
| UI-008 | P0 | 重启 App | 排序后退出并重启 TagLauncher | 顺序保持 |
| UI-009 | P1 | 语言切换 | 排序后切换中英文/日文 | 顺序保持,显示名变化不影响位置 |
| UI-010 | P1 | 新安装 App | 排序后新增一个 App 并刷新 | 旧顺序保持,新 App 按默认规则追加 |
## 6. 旧拖拽行为回归
| ID | 优先级 | 用例 | 步骤 | 期望结果 |
| --- | --- | --- | --- | --- |
| DRAG-001 | P0 | 跨容器移动标签 | 从 A 容器拖 App 到 B 容器 | App 获得/移动到 B 标签,旧逻辑保持 |
| DRAG-002 | P0 | Option 复制标签 | 按住 Option 从 A 拖到 B | App 同时保留 A/B 标签 |
| DRAG-003 | P0 | 拖到空白移除来源标签 | 从容器拖到空白区域 | 继续出现现有确认逻辑 |
| DRAG-004 | P0 | 拖到未分类 | 拖到未分类目标 | 继续走未分类确认与记忆逻辑 |
| DRAG-005 | P0 | 拖到 Apple 内置组 | 拖到 Mac 自带 / Apple 内置组 | 继续被保护,不改变标签 |
| DRAG-006 | P1 | 标签栏拖拽排序 | 拖动顶部/侧边标签排序 | 不受 App 排序拖拽影响 |
## 7. 显示模式矩阵
| ID | 优先级 | displayMode | 验收 |
| --- | --- | --- | --- |
| MODE-001 | P0 | `flat` | 顺序读取一致;是否允许拖动按产品最终边界验收 |
| MODE-002 | P0 | `container` | 可同容器排序 |
| MODE-003 | P0 | `coloredContainer` | 可同容器排序,颜色填充不遮挡插入提示 |
| MODE-004 | P0 | `gridContainer` | 可同容器排序,跨行插入正确 |
| MODE-005 | P0 | `coloredGridContainer` | 可同容器排序,跨行插入正确 |
## 8. 标签位置矩阵
| ID | 优先级 | 标签位置 | 验收 |
| --- | --- | --- | --- |
| NAV-001 | P1 | 左侧 | App 排序和标签导航互不冲突 |
| NAV-002 | P1 | 右侧 | App 排序和标签导航互不冲突 |
| NAV-003 | P1 | 顶部 | App 排序和标签导航互不冲突 |
## 9. 尺寸与显示矩阵
| ID | 优先级 | 条件 | 验收 |
| --- | --- | --- | --- |
| VIEW-001 | P1 | hide app names 开 | 拖动排序时不因名称隐藏误触发气泡 |
| VIEW-002 | P1 | hide app names 关 | 名称不遮挡插入提示 |
| VIEW-003 | P1 | icon size 40 | 小图标可拖动,命中正确 |
| VIEW-004 | P1 | icon size 64 | 默认尺寸可拖动 |
| VIEW-005 | P1 | icon size 80 | 大图标可拖动,布局不重叠 |
## 10. 异常与取消
| ID | 优先级 | 用例 | 期望结果 |
| --- | --- | --- | --- |
| EDGE-001 | P0 | 拖出窗口后松手 | 状态清理干净,不残留拖拽影子 |
| EDGE-002 | P1 | 拖动中按 Esc | 取消拖动,顺序不变 |
| EDGE-003 | P1 | 拖动中滚动 | 不出现错位插入;若暂不支持滚动拖拽,边界表现明确 |
| EDGE-004 | P1 | 拖动中切换 Space/窗口 | 不崩溃,不残留 hover/bubble 状态 |
| EDGE-005 | P1 | 拖动中打开 Quick Search/Settings | 拖动状态清理,窗口行为正常 |
## 11. 现有 QA 脚本
实现完成后至少运行:
```bash
cd /Users/ar/Projects/Taglauncher/src
bash Scripts/macos14_availability_typecheck_qa.sh
bash Scripts/macos14_build_metadata_qa.sh
bash Scripts/quick_search_app_name_qa.sh
bash Scripts/quick_search_system_app_qa.sh
bash Scripts/smartstart_catalog_resource_qa.sh
bash Scripts/window_logic_qa.sh
```
## 12. 发布阻断条件
- 顺序在刷新、重启、语言切换、导入导出、恢复上一方案任一路径丢失。
- 同容器排序破坏跨容器移动、Option 复制、拖空白移除、Apple 内置保护。
- 旧数据无法加载或升级后默认顺序改变。
- macOS 14 typecheck/build metadata/window logic 任一失败。
- 手工发现拖拽残影、误启动 App、窗口关闭、UI 重叠或插入位置不可理解。
docs/7.90/05-技术设计方案.md
New file
@@ -0,0 +1,262 @@
# 7.9.0 技术设计方案:容器内 App 手动排序
## 1. 目标
在现有 TagLauncher App Grid 中支持同一容器内 App 拖动排序,并将顺序作为“分类与布局方案”的一部分持久化。
## 2. 当前架构现状
- `AppInfo` 不包含用户顺序字段,定义在 `src/Apptag/DataLayer.swift`。
- `TagDatabase.Store` 当前有 `appTags` 和 `tagOrder`,没有容器内 App 顺序字段。
- `AppIndexer.group()` 负责按 tag 分组,组内 App 顺序来自扫描结果 append 顺序。
- `ContentView.makeDisplayGroups()` 将分组名本地化后传给 App Grid。
- `AppGridCollectionView` 是 `NSViewRepresentable` + AppKit 自绘 grid。
- `AppGridGroupCardView` 按 `group.apps.indices` 布局 icon。
- `AppGridIconNSView` 当前长按拖动表示跨 tag 移动/复制,不包含目标插入 index。
## 3. 设计原则
- 顺序是数据,不是临时 UI 状态。
- 容器 ID 必须稳定,不能依赖本地化显示名。
- 同容器排序不改变 `appTags`。
- 旧拖拽语义优先保护,不为排序重写跨容器拖动。
- 第一版以 path 作为 App 顺序身份,降低实现复杂度。
- 不存像素坐标、row/column、`IndexPath`,因为布局会随窗口尺寸、displayMode、iconSize 改变。
## 4. 数据模型
### 4.1 Store 新字段
建议在 `TagDatabase.Store` 增加:
```swift
var containerAppOrder: [String: [String]] = [:]
```
含义:
- key:稳定容器 ID。
- value:该容器内 app path 的完整或部分顺序。
旧数据兼容:
```swift
containerAppOrder = try container.decodeIfPresent(
    [String: [String]].self,
    forKey: .containerAppOrder
) ?? [:]
```
### 4.2 容器 ID
建议新增 helper,统一生成容器 ID:
```swift
enum AppContainerID {
    static let uncategorized = "__container.uncategorized"
    static let appleBuiltIn = "__container.appleBuiltIn"
    static func tag(_ name: String) -> String {
        "tag:\(name)"
    }
    static func system(_ id: SmartCategoryID) -> String {
        "system:\(id.rawValue)"
    }
}
```
第一版可以先支持普通 tag 和特殊容器,系统分类 ID 映射按现有 `TagDef.systemCategoryID` 补齐。
### 4.3 TagGroup 扩展
当前 `TagGroup.id` 使用 `name`。建议增加:
```swift
struct TagGroup: Identifiable {
    var id: String { containerID }
    let containerID: String
    let name: String
    let apps: [AppInfo]
}
```
这样 UI 显示名可以本地化,但排序和滚动定位使用稳定 ID。
兼容注意:
- 现有按 `group.name` 判断特殊组的逻辑要逐步改为 container ID。
- 滚动目标如果仍传显示名,需增加兼容查找。
## 5. 排序算法
输入:
- 某个容器原始 apps。
- `containerAppOrder[containerID]`。
输出:
- 已命中顺序的 App 先按记录顺序排列。
- 未命中记录的新 App 追加到后面,按现有默认名称排序。
- 顺序字段中不存在的 path 忽略,可在保存或 reconcile 时清理。
伪代码:
```swift
func applyAppOrder(apps: [AppInfo], order: [String]) -> [AppInfo] {
    let byPath = Dictionary(uniqueKeysWithValues: apps.map { ($0.path.path, $0) })
    let ordered = order.compactMap { byPath[$0] }
    let orderedPaths = Set(ordered.map { $0.path.path })
    let remaining = apps
        .filter { !orderedPaths.contains($0.path.path) }
        .sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
    return ordered + remaining
}
```
## 6. 写入 API
建议新增:
```swift
extension TagEditor {
    static func reorderApps(inContainer containerID: String, orderedPaths: [String])
}
```
写入规则:
- 只保存当前容器内可见 App 的完整 path 顺序。
- 去重。
- 过滤空 path。
- 如结果等于当前有效顺序则不写。
- 走 `TagDatabase.saveUserCategorySchemeMutation(reason: "reorder-apps")`。
## 7. 分类方案与导入导出
必须更新:
- `TagDatabase.Store.CodingKeys`:加入 `containerAppOrder`。
- `CategorySchemeFingerprint`:加入规范化后的 `containerAppOrder`。
- `exportTo` / `importFrom`:自动随 Store 编码,但要补 roundtrip QA。
- `restore(fromBackupAt:)`:恢复顺序字段。
- `resetAppTagAssignmentsToUncategorized()`:清理或重建顺序字段,建议清空旧普通容器顺序。
- `SmartStartService.applyDraft(replaceExistingScheme: true)`:清空旧顺序。
- tag rename/delete/relocalize:迁移或清理 `tag:<name>` 对应顺序。
- reconcile removed apps:清理顺序字段里的 orphan path。
## 8. UI 集成
### 8.1 回调
`AppGridCollectionView` 增加:
```swift
let onReorderApps: (String, [String]) -> Void
```
其中:
- 第一个参数为 `containerID`。
- 第二个参数为排序后的 app path 列表。
`ContentView` 接收后调用 `TagEditor.reorderApps`,再 `refreshApps(forceLayoutRefresh: true)` 或局部刷新。
### 8.2 Hit-test
在 `AppGridGroupCardView` 内根据鼠标位置计算 insertion index:
- 使用当前 `iconViews` frame。
- 同容器内拖动时显示插入线/占位。
- 松手时生成新 path 顺序。
- 拖出当前 group 或命中其他 drop target 时走旧拖拽逻辑。
### 8.3 拖拽 intent
现有 payload 是 `path + sourceTag` 字符串,不适合继续扩展复杂状态。建议内部引入结构化状态:
```swift
struct AppDragPayload {
    let path: String
    let sourceContainerID: String
    let sourceDisplayName: String
}
```
第一版如果为了控制改动范围暂不完全替换 `AppDragCoordinator` payload,也必须在同容器排序分支中独立判断 source/target 和 insertion index,避免误触发旧 drop。
### 8.4 状态清理
排序开始:
- 抑制 hover bubble。
- 结束 tag nav reorder。
- 标记 app drag mode active。
排序结束或取消:
- 清除插入提示。
- 恢复 bubble 状态。
- 清理 drag image / hover target。
## 9. 不改范围
- 不改 Quick Search rank。
- 不改 App 扫描规则。
- 不改 SmartStart 分类匹配逻辑。
- 不改跨容器拖动的标签归属规则。
- 不改设置页现有数据面板结构。
## 10. 迁移策略
- 不需要一次性迁移用户数据。
- 新字段缺省为空,表示使用旧默认顺序。
- 第一次用户在某容器排序后,只写该容器顺序。
- 版本升级到 `7.9.0` 时在 changelog 说明新增布局顺序字段。
## 11. 开发顺序
1. 数据层:
   - Store 字段、容器 ID、排序 helper、写入 API、fingerprint、导入导出/恢复/重置/SmartStart 处理。
   - 配套数据 QA。
2. UI 层:
   - 同容器 insertion index hit-test。
   - 插入提示。
   - 本地顺序预览。
3. 集成:
   - drop/end 写入。
   - refresh 后顺序保持。
   - 旧拖拽行为回归。
4. 发布:
   - 版本号 `7.9.0`。
   - `CHANGELOG.md`。
   - DMG / App Store 资料按实际发布目标准备。
## 12. 风险与控制
| 风险 | 等级 | 控制 |
| --- | --- | --- |
| 顺序未持久化导致刷新丢失 | 高 | 数据层先行,QA roundtrip |
| 本地化显示名作为 key 导致语言切换错位 | 高 | 统一 container ID |
| 同容器排序误触发跨标签移动 | 高 | 独立 intent 和 hit-test |
| 导入导出/恢复漏字段 | 高 | fingerprint + export/import QA |
| 拖拽状态残留 | 中 | 统一 cancel/cleanup |
| 新字段污染旧用户默认顺序 | 中 | 空字段即旧排序 |
## 13. 必跑验证
```bash
cd /Users/ar/Projects/Taglauncher/src
bash Scripts/macos14_availability_typecheck_qa.sh
bash Scripts/macos14_build_metadata_qa.sh
bash Scripts/quick_search_app_name_qa.sh
bash Scripts/quick_search_system_app_qa.sh
bash Scripts/smartstart_catalog_resource_qa.sh
bash Scripts/window_logic_qa.sh
```
同时必须做真实鼠标手工验证,覆盖同容器排序、跨容器拖动、Option 复制、拖空白移除、macOS 14、5 种 displayMode 和 3 种标签位置。
docs/7.90/README.md
@@ -21,6 +21,9 @@
- [01-容器内App手动排序-TODO.md](./01-容器内App手动排序-TODO.md):可执行开发 TODO、切分和 QA 阻断项。
- [02-专题讨论纪要.md](./02-专题讨论纪要.md):2 个架构师、1 个代码审核员、1 个 QA 的专题讨论结论。
- [03-产品PRD.md](./03-产品PRD.md):产品目标、范围、交互、验收标准和发布要求。
- [04-QA测试用例集.md](./04-QA测试用例集.md):QA 测试矩阵、自动化用例、手工用例和发布阻断条件。
- [05-技术设计方案.md](./05-技术设计方案.md):数据模型、UI 集成、迁移策略和技术风险控制。
## 后续记录规则