Ariver
2026-06-27 369c8f03db2f6d507697ab2296db5e6f81c8cd1e
Reduce SmartStart default categories to thirteen
8 files modified
85 ■■■■■ changed files
CODEGRAPH.md 6 ●●●● patch | view | raw | blame | history
src/Apptag/AppleDefaultAppCatalog.swift 3 ●●●● patch | view | raw | blame | history
src/Apptag/Info.plist 4 ●●●● patch | view | raw | blame | history
src/Apptag/SmartCategorization/SmartCategory.swift 8 ●●●● patch | view | raw | blame | history
src/Apptag/SmartCategorization/SmartStartService.swift 3 ●●●● patch | view | raw | blame | history
src/CHANGELOG.md 8 ●●●●● patch | view | raw | blame | history
src/Docs/Requirements/2026-06-27-smartstart-category-simplification.md 33 ●●●●● patch | view | raw | blame | history
src/Scripts/smartstart_default_categories_qa.sh 20 ●●●●● patch | view | raw | blame | history
CODEGRAPH.md
@@ -95,8 +95,8 @@
- `src/Apptag/SmartCategorization/SmartCategory.swift`
  - 内置智能分类定义。
  - `SmartCategoryDefaults.orderedIDs` 是 SmartStart 默认初始化标签事实源;当前默认方案为 15 个标签。
  - `SmartCategoryID.smartStartDefaultCategoryID` 负责把旧细分 catalog 分类归并到 15 个默认标签;这是新 SmartStart 写入前的归并,不做老用户旧标签自动迁移。
  - `SmartCategoryDefaults.orderedIDs` 是 SmartStart 默认初始化标签事实源;当前默认方案为 13 个标签。
  - `SmartCategoryID.smartStartDefaultCategoryID` 负责把旧细分 catalog 分类归并到 13 个默认标签;`finance` 和 `other` 返回 nil,回到未分类;这是新 SmartStart 写入前的归并,不做老用户旧标签自动迁移。
- `src/Apptag/SmartCategorization/SmartCategorizationDraft.swift`
  - 智能分类草稿、默认备注 provenance、未分配应用和 warning 模型。
- `src/Apptag/SmartCategorization/SmartStartService.swift`
@@ -148,7 +148,7 @@
- `src/Scripts/smart_category_localization_qa.sh`
  - SmartCategory/default system tag 初始化标签 29 语种本地化检查;禁止非英文语言包直接复制英文标签,保留 `PDF`、`DevOps` 等技术通用词例外。
- `src/Scripts/smartstart_default_categories_qa.sh`
  - SmartStart 默认初始化标签检查;验证 15 个默认分类、catalog 分类归并入口、首次 starter tags 和关键多语言目标文案。
  - SmartStart 默认初始化标签检查;验证 13 个默认分类、catalog 分类归并入口、`finance` / `other` 回未分类、首次 starter tags 和关键多语言目标文案。
- `src/Scripts/quick_search_app_name_qa.sh`
  - Quick Search 应用显示名回归。
- `src/Scripts/quick_search_system_app_qa.sh`
src/Apptag/AppleDefaultAppCatalog.swift
@@ -126,8 +126,7 @@
        guard let entry = entry(for: app) else { return nil }
        let categoryIDs = entry.defaultTag
            .compactMap { SmartCategoryID(rawValue: $0) }
            .filter { $0 != .other }
            .map(\.smartStartDefaultCategoryID)
            .compactMap(\.smartStartDefaultCategoryID)
        guard !categoryIDs.isEmpty else { return nil }
        return AppleDefaultCategorization(
            canonicalName: entry.canonicalName,
src/Apptag/Info.plist
@@ -19,9 +19,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>8.1.3</string>
    <string>8.1.4</string>
    <key>CFBundleVersion</key>
    <string>20260627.1605</string>
    <string>20260627.1643</string>
    <key>LSApplicationCategoryType</key>
    <string>public.app-category.utilities</string>
    <key>LSMinimumSystemVersion</key>
src/Apptag/SmartCategorization/SmartCategory.swift
@@ -249,7 +249,7 @@
        }
    }
    var smartStartDefaultCategoryID: SmartCategoryID {
    var smartStartDefaultCategoryID: SmartCategoryID? {
        switch self {
        case .pdf, .productivity, .writing:
            return .office
@@ -262,6 +262,8 @@
            return .design
        case .media, .video, .audio, .game:
            return .entertainment
        case .finance, .other:
            return nil
        default:
            return self
        }
@@ -301,9 +303,7 @@
        .design,
        .picturePhoto,
        .entertainment,
        .finance,
        .education,
        .other
        .education
    ]
    static let definitions: [SmartCategoryDefinition] = orderedIDs.map {
src/Apptag/SmartCategorization/SmartStartService.swift
@@ -528,8 +528,7 @@
        let entries = base.entries.compactMap { entry -> SmartStartCatalogEntry? in
            let tagIDs = entry.defaultTag
                .compactMap { SmartCategoryID(rawValue: $0) }
                .filter { $0 != .other }
                .map(\.smartStartDefaultCategoryID)
                .compactMap(\.smartStartDefaultCategoryID)
            guard !tagIDs.isEmpty else { return nil }
            return SmartStartCatalogEntry(
src/CHANGELOG.md
@@ -2,6 +2,14 @@
## [Unreleased]
## [8.1.4] — 2026-06-27
- 继续精简 SmartStart 智能初始化默认分类:新用户 / 重置智能初始化方案后的默认标签从 15 个收敛为 13 个
- `Finance / 财务` 和 `Other / 其他` 不再作为默认智能初始化标签;catalog 中命中这两类的应用会回到未分类,不会被强行归入其它标签
- 保持不做老用户迁移:已有用户的旧标签和手动分类关系不扫描、不改名、不删除
- 更新 `Scripts/smartstart_default_categories_qa.sh`,防止 `finance` / `other` 重新进入默认标签或 catalog 归一化结果
- 版本号更新为 `8.1.4`,Build 更新为 `20260627.1643`
## [8.1.3] — 2026-06-27
- 精简 SmartStart 智能初始化默认分类:新用户 / 重置智能初始化方案后的默认标签从 39 个收敛为 15 个,降低初次使用时的分类冗余
src/Docs/Requirements/2026-06-27-smartstart-category-simplification.md
@@ -2,11 +2,11 @@
日期:2026-06-27
状态:产品已确认 `network-tools` 归入 `System Tools / 系统工具`;确认 `system-tools` 的用户可见名称采用 `System Tools / 系统工具`;确认 `office`、`PDF`、`writing` 合并为 `Office & Docs / 办公文档`;确认 `Video / Audio / Games` 合并进 `Entertainment / 影音娱乐`,最终默认智能分类为 15 个。
状态:产品已确认 `network-tools` 归入 `System Tools / 系统工具`;确认 `system-tools` 的用户可见名称采用 `System Tools / 系统工具`;确认 `office`、`PDF`、`writing` 合并为 `Office & Docs / 办公文档`;确认 `Video / Audio / Games` 合并进 `Entertainment / 影音娱乐`;确认 `Finance / 财务` 和 `Other / 其他` 回到未分类,最终默认智能分类为 13 个。
## 1. 本轮目标
用户反馈当前智能初始化使用 39 个默认标签,过于冗余。本方案把默认标签精简为 15 个,并明确合并关系、最终标签列表与后续落地注意事项。
用户反馈当前智能初始化使用 39 个默认标签,过于冗余。本方案把默认标签精简为 13 个,并明确合并关系、最终标签列表与后续落地注意事项。
## 2. 当前 39 个初始化标签
@@ -139,6 +139,16 @@
- 合并后能进一步降低默认标签数量,避免新用户第一屏出现过多细分分类。
- `Entertainment / 影音娱乐`作为总标签更适合默认初始化;如果用户需要细分,可后续手动拆分。
### 改动 6:Finance / Other 回到未分类
`Finance / 财务` 和 `Other / 其他` 不再作为默认智能初始化标签。
确认理由:
- 财务类 App 对普通用户并非高频默认分类,默认展示会增加标签冗余。
- `Other / 其他` 本质上不是智能分类结果,而是“无法确定分类”的兜底状态,应该回到未分类。
- 本轮仍不做老用户迁移;已有用户手动创建或历史保留的财务 / 其他标签不自动删除、不改名、不迁移。
## 4. 命名确认
### System Tools
@@ -167,11 +177,11 @@
当前:39 个默认标签。
合并后:15 个默认标签。
合并后:13 个默认标签。
减少:24 个标签。
减少:26 个标签。
## 6. 最终 15 个默认标签列表
## 6. 最终 13 个默认标签列表
以下列表按“尽量保留原始顺序 + 合并项放在首次出现位置”的原则排列。
@@ -189,9 +199,7 @@
| 10 | `design` | Design | 设计 | 原 17、18、19、20 |
| 11 | `picture-photo` | Photos | 图片 | 原 25 |
| 12 | `entertainment` | Entertainment | 影音娱乐 | 原 22、23、24、34、35 |
| 13 | `finance` | Finance | 财务 | 原 36 |
| 14 | `education` | Education | 学习 | 原 37 |
| 15 | `other` | Other | 其他 | 原 39 |
| 13 | `education` | Education | 学习 | 原 37 |
## 7. 完整合并映射表
@@ -232,24 +240,25 @@
| 33 | `network-tools` | 网络工具 | `system-enhancement` | 系统工具 |
| 34 | `entertainment` | 影音娱乐 | `entertainment` | 影音娱乐 |
| 35 | `game` | 游戏 | `entertainment` | 影音娱乐 |
| 36 | `finance` | 财务 | `finance` | 财务 |
| 36 | `finance` | 财务 | 未分类 | 不创建默认标签 |
| 37 | `education` | 学习 | `education` | 学习 |
| 38 | `security` | 安全 | `system-enhancement` | 系统工具 |
| 39 | `other` | 其他 | `other` | 其他 |
| 39 | `other` | 其他 | 未分类 | 不创建默认标签 |
## 8. 后续落地建议
如果产品确认这版列表,建议后续实现按低风险路径做:
1. 调整 `SmartCategoryDefaults.orderedIDs`,把 39 个默认标签压缩为 15 个。
1. 调整 `SmartCategoryDefaults.orderedIDs`,把 39 个默认标签压缩为 13 个。
2. 复用既有 `SmartCategoryID.office`、`SmartCategoryID.development`、`SmartCategoryID.design`、`SmartCategoryID.systemEnhancement` 作为合并目标。
3. 建立旧 category id 到新 category id 的映射表,SmartStart 匹配结果统一归并后再写入标签。
4. 29 个语言包中将 `system-enhancement` 的用户可见文案改为 `System Tools / 系统工具`对应语义,将 `office` 改为 `Office & Docs / 办公文档`对应语义,将 `picture-photo` 简化为 `Photos / 图片`对应语义。
5. QA 覆盖:
   - 新用户智能初始化最终标签数是 15。
   - 新用户智能初始化最终标签数是 13。
   - 合并来源 app 进入正确目标标签。
   - 第 33 项 `network-tools`归入 `system-enhancement`,不重复创建标签。
   - `office`、`PDF`、`writing`都归入 `Office & Docs / 办公文档`。
   - `finance` 和 `other` 不生成默认标签,命中后回到未分类。
   - 新默认智能分类结果中 29 语种没有旧细分标签残留、没有英文兜底污染。
   - 老用户已有标签保持原样;本轮不做旧 39 标签自动迁移。
src/Scripts/smartstart_default_categories_qa.sh
@@ -51,9 +51,7 @@
    "design",
    "picturePhoto",
    "entertainment",
    "finance",
    "education",
    "other",
]
EXPECTED_RAW_IDS = {
@@ -69,10 +67,10 @@
    "design",
    "picture-photo",
    "entertainment",
    "finance",
    "education",
    "other",
}
DROP_RAW_IDS = {"finance", "other"}
MERGE_MAP = {
    "PDF": "office",
@@ -159,6 +157,8 @@
    "case .font, .uiPrototyping, .threeDCAD, .diagramming:",
    "case .media, .video, .audio, .game:",
    "return .entertainment",
    "case .finance, .other:",
    "return nil",
]
missing_snippets = [snippet for snippet in required_mapping_snippets if snippet not in swift]
if missing_snippets:
@@ -166,7 +166,7 @@
for path in [smartstart, apple_catalog]:
    text = path.read_text(encoding="utf-8")
    if ".map(\\.smartStartDefaultCategoryID)" not in text:
    if ".compactMap(\\.smartStartDefaultCategoryID)" not in text:
        fail(f"{path.name} must normalize catalog category IDs through smartStartDefaultCategoryID")
starter_match = re.search(
@@ -181,7 +181,9 @@
if legacy_starter_cases:
    fail("starterCategoryIDs contains non-default categories: " + ", ".join(sorted(legacy_starter_cases)))
def normalize(raw: str) -> str:
def normalize(raw):
    if raw in DROP_RAW_IDS:
        return None
    return MERGE_MAP.get(raw, raw)
for catalog_path in [smartstart_base, apple_base]:
@@ -189,9 +191,9 @@
    unexpected = {}
    for entry in catalog.get("entries", []):
        for raw in entry.get("defaultTag", []):
            if raw == "other":
                continue
            normalized = normalize(raw)
            if normalized is None:
                continue
            if normalized not in EXPECTED_RAW_IDS:
                unexpected.setdefault(raw, normalized)
    if unexpected:
@@ -206,6 +208,6 @@
print(
    "PASS SmartStart default categories: "
    f"{len(EXPECTED_ORDERED_CASES)} defaults, catalog IDs normalized, "
    "starter tags restricted, target labels checked"
    "finance/other uncategorized, starter tags restricted, target labels checked"
)
PY