# App Grid 容器排序与使用技巧 - 整理日期:2026-06-15 - 依据产品版本:TagLauncher 7.9.1,Build 20260614.1454 - 版本依据:`src/Apptag/Info.plist`、`src/CHANGELOG.md`、`src/Release/AppStore-7.9.1-20260614.1454/QA_RELEASE_EVIDENCE.md` - 配套图示:[08-AppGrid容器排序与使用技巧.drawio](./08-AppGrid容器排序与使用技巧.drawio) ## 专题定位 7.9.x 之后 App Grid 不只是“按标签分组展示 App”,还承担两个新的高频交互: - 容器内 App 手动排序:用户在同一容器内长按拖动 App,可改变显示顺序。 - 使用技巧悬浮条:App Grid 底部显示 8 条可翻页提示,帮助用户发现标签、拖拽、排序和备注能力。 这两个能力都在 App Grid 主交互层,和拖拽、滚动、备注气泡、Quick Search only 会话、设置项互相影响,后续改动需要作为独立专题看待。 ## 核心文件 | 文件 | 职责 | | --- | --- | | `ContentView.swift` | 持有 `containerAppOrder`、`selectedUsageTipIndex`、`usageTipsHovered`;处理排序回写、使用技巧显示条件和气泡互斥 | | `AppGridCollectionView.swift` | AppKit collection view、group card、自定义拖拽插入点、使用技巧 HUD、翻页按钮和 hover 命中 | | `DataLayer.swift` | `TagDatabase.Store.containerAppOrder` schema、归一化、迁移、导入导出和 category scheme 快照 | | `PreferencesView.swift` | `hideUsageTips` 设置入口 | | `Localization/*.json` | `usageTips.*` 文案;非中文语种使用一个 ASCII 逗号作为渲染换行标记 | | `Scripts/app_ordering_data_qa.sh` | 容器排序数据与静态门禁 | | `Scripts/usage_tips_qa.sh` | 使用技巧文案和两行渲染门禁 | ## 容器排序数据模型 `TagDatabase.Store` 新增: ```swift var containerAppOrder: [String: [String]] = [:] ``` key 是稳定容器 ID,value 是该容器内按用户意图排列的 App path: | 容器 | 稳定 ID | | --- | --- | | 未分类 | `__container.uncategorized` | | Apple 内置 | `__container.appleBuiltIn` | | 普通标签 | `tag:` | | Smart Start 系统分类 | `system:` | 稳定 ID 的目的,是避免 UI 显示名、本地化语言或系统分类展示名变化时,用户排序丢失。 ## 读取与展示链路 1. `ContentView.refreshApps` 获取 `AppLibrarySnapshot`。 2. `ContentView.applyAppLibrarySnapshot` 将 `snapshot.containerAppOrder` 写入 `@State containerAppOrder`。 3. `makeDisplayGroups` 调用 `AppIndexer.group(... containerAppOrder:)`。 4. `AppIndexer.group` 先按标签、系统分类、未分类、Apple 内置规则分组。 5. `TagDatabase.normalizedContainerAppOrder` 过滤不存在的 App、重复 path、已不属于该容器的 App。 6. `AppIndexer.orderedApps` 按排序数组优先排列,未在数组里的 App 按显示名自然排序追加。 7. `AppGridCollectionView` 用最终 `TagGroup.apps` 渲染容器。 这个设计保证了“排序只影响容器内顺序,不改变 App 与标签的归属关系”。 ## 写入链路 同容器拖拽排序从 AppKit 层开始: 1. `AppGridIconNSView` 长按进入 `AppDragCoordinator` 拖拽态。 2. `AppGridGroupCardView` 根据鼠标位置计算 `reorderInsertionIndex` 并绘制插入线。 3. 松手时 `reorderedAppPaths(moving:)` 生成新的 path 顺序。 4. `Coordinator.onReorderApps(containerID, orderedPaths)` 回调到 SwiftUI。 5. `ContentView.reorderApps(inContainer:orderedPaths:)` 更新内存态并重建分组。 6. `TagEditor.reorderApps` 归一化并保存到 `TagDatabase.Store`。 7. `saveUserCategorySchemeMutation(reason: "reorder-apps")` 将排序变化纳入上一套方案快照。 如果拖到其他容器,仍走 `dropApp` / `moveApp` 逻辑;如果按 Option,走复制语义;如果拖到容器外空白,走标签移除确认流。排序逻辑只在同容器内生效。 ## 归一化与迁移边界 `containerAppOrder` 在加载、保存、导入、分组展示和写入时都会归一化: - 清理空 path、重复 path。 - 清理无效容器 ID。 - 清理已删除标签对应的排序。 - 清理已不属于该容器的 App。 - 新增 App 不写入排序数组,展示时追加在排序末尾。 标签重命名或系统分类 ID 迁移时使用 `migrateContainerAppOrderKey`,把旧 key 下的顺序合并到新 key。重置为未分类会清空 `containerAppOrder`,避免旧排序污染新方案。 ## 使用技巧悬浮条 使用技巧由 `ContentView.appGridUsageTips` 提供 8 条数据,实际文案来自本地化 key: - `usageTips.tip1.*`:新增标签。 - `usageTips.tip2.*`:给 App 打标签。 - `usageTips.tip3.*`:移动 App。 - `usageTips.tip4.*`:Option 复制 App。 - `usageTips.tip5.*`:解除标签。 - `usageTips.tip6.*`:标签排序。 - `usageTips.tip7.*`:App 排序。 - `usageTips.tip8.*`:编辑备注。 显示条件由 `shouldShowUsageTips` 控制: - 用户没有开启 `hideUsageTips`。 - `allApps` 非空。 - 当前不是 Quick Search only 会话。 `AppGridUsageTipsMetrics` 预留底部高度,`AppGridCollectionView` 通过 `bottomContentPadding` 给网格内容让位,避免悬浮条遮挡底部 App。 ## 文案渲染策略 非中文语种的 usage tips 详情使用一个 ASCII 逗号作为逻辑换行标记。`formattedTipDetail` 会把 `,`、`>`、`->`、`-->`、`→`、`>` 统一替换为换行,并压缩多余空白。 当前 7.9.1 的质量约束是: - 简体中文、繁体中文保留原有 `>` 风格文案。 - 27 个非中文语种每条 detail 只有一个 ASCII 逗号。 - 非中文语种不得继续使用 `>`、`->`、`-->`、`→`、`>`。 - 渲染后每条 detail 稳定为两行逻辑文本。 这套策略不是通用 Markdown 或富文本解析,只服务于底部 HUD 的固定两行展示。 ## 交互互斥 使用技巧悬浮条会参与 App Grid 的交互抑制: - 鼠标进入悬浮条时,`usageTipsHovered = true`。 - `appBubbleDisabled` 会因此为 true,备注 hover bubble 被隐藏。 - 悬浮条按钮点击由 `handleMouseEventFromHost` 路由,避免事件穿透到底层 App 图标。 - 滚动、拖拽和 pending confirm 也会抑制备注 bubble,保证底部 HUD、拖拽层和备注层不会同时抢焦点。 ## QA 门禁 修改容器排序相关逻辑时至少执行: ```bash cd /Users/ar/Projects/Taglauncher/src bash Scripts/app_ordering_data_qa.sh ``` 修改使用技巧文案、HUD 布局或 formatter 时至少执行: ```bash cd /Users/ar/Projects/Taglauncher/src bash Scripts/usage_tips_qa.sh ``` 发布前还应结合人工 smoke: - 同容器拖动 App 后重启,顺序仍保持。 - 跨容器移动不被误识别成排序。 - Option 复制不删除来源标签。 - 拖到容器外空白移除标签仍弹正确确认。 - 使用技巧翻页按钮可点击,hover 时备注气泡不出现。 - 设置中开启“隐藏使用技巧”后 App Grid 底部不再预留悬浮条。 ## 修改风险提示 - 不要用本地化容器名作为排序 key;语言切换会破坏排序。 - 不要在 `AppIndexer.group` 之外重复实现排序合并;否则 App Grid 和 snapshot 可能分叉。 - 不要让 `containerAppOrder` 改变 App 的标签归属;排序和分类是两条独立语义。 - 不要把 usage tips detail 当普通自然语言自由编辑;非中文语种必须满足 QA 的一个逗号换行约束。 - 不要移除使用技巧 hover 对备注 bubble 的抑制;否则底部 HUD 和备注编辑层容易重叠或误触。