Ariver
2026-05-19 099cd85c325a605e0d6606817174bf99ea88c351
Refresh SmartStart defaults for 7.3.5 build 744
8 files modified
210846 ■■■■ changed files
AppStore_Submission.md 4 ●●● patch | view | raw | blame | history
Apptag/AppleDefaultAppNotes.swift 146 ●●●● patch | view | raw | blame | history
Apptag/SmartCategorization/SmartCategory.swift 26 ●●●● patch | view | raw | blame | history
Apptag/SmartCategorization/SmartStartService.swift 4 ●●●● patch | view | raw | blame | history
CHANGELOG.md 2 ●●●●● patch | view | raw | blame | history
Research/SmartStart/Scripts/build-ultimate-default-catalog.mjs 466 ●●●●● patch | view | raw | blame | history
Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.csv 10464 ●●●● patch | view | raw | blame | history
Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.json 199734 ●●●● patch | view | raw | blame | history
AppStore_Submission.md
@@ -1,6 +1,6 @@
# Apptag — Mac App Store 提审资料
> 版本: 7.3.5 | Build: 744 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-18
> 版本: 7.3.5 | Build: 744 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-19
---
@@ -198,6 +198,7 @@
- Fixed Safari and other modern macOS system apps being missed when they live behind Cryptex system paths
- Improved settings and edit controls, including more stable settings access from the overlay and better multilingual layout behavior
- Improved menu bar item stability with a persistent status item identity so macOS and menu bar managers can recognize it consistently
- Refreshed the Smart Start initialization catalog from the latest curated data, with richer default app notes and cleaner first-run organization
- Fixed localized default notes for Apple apps during first launch in non-Chinese languages
- Fixed Uncategorized drag-and-drop confirmation so cancel/confirm no longer leaves icon drag state stuck
- Improved tag-list reordering feedback by replacing persistent wiggle animation with clearer opacity-based drag state
@@ -205,6 +206,7 @@
- Fixed right-side tag navigation getting stuck in a dimmed drag visual state after a tag click
- Fixed an icon grid rendering issue that could make many app icons disappear after drag-state resets
- Fixed stale hover bubbles so app notes no longer remain pinned to the previous app while hovering another app
- Removed trailing punctuation from bundled default app notes so first-run app remarks read more naturally
- Version updated to 7.3.5, Build updated to 744
```
Apptag/AppleDefaultAppNotes.swift
@@ -17,82 +17,82 @@
    ]
    private static let byBundleID: [String: String] = [
        "com.apple.activitymonitor": "Mac 的仪表盘,专看谁在耗电、吃内存、拖慢电脑。",
        "com.apple.airport.airportutility": "管理 AirPort 和 Time Capsule 的老派网络工具。",
        "com.apple.appstore": "Mac 官方软件商店,找应用、更新和订阅都从这里开始。",
        "com.apple.apps.launcher": "一个轻量应用入口,帮你从系统层面快速打开 App。",
        "com.apple.audio.audiomidisetup": "管音频设备和 MIDI 路由,音乐人和调试党很常用。",
        "com.apple.automator": "不写代码也能串起一串 Mac 自动化动作。",
        "com.apple.bluetoothfileexchange": "用蓝牙收发文件,古早但关键时能救急。",
        "com.apple.ibooksx": "阅读和管理电子书的入口,适合把 Mac 变成书桌。",
        "com.apple.bootcampassistant": "Intel Mac 安装 Windows 的官方向导。",
        "com.apple.calculator": "小计算器,单位换算和程序员模式也藏在里面。",
        "com.apple.ical": "把日程从脑子里搬出来,给时间一张可视化地图。",
        "com.apple.chess": "系统自带国际象棋,摸鱼时比刷网页更优雅。",
        "com.apple.clock": "闹钟、计时器、秒表和世界时钟的统一入口。",
        "com.apple.colorsyncutility": "查看和修复色彩配置文件,处理显示和打印色差。",
        "com.apple.console": "看系统日志的窗口,排查异常时很有用。",
        "com.apple.addressbook": "通讯录中枢,邮件、信息和 FaceTime 都会用到它。",
        "com.apple.dictionary": "离线词典和同义词库,写作查词不用开浏览器。",
        "com.apple.digitalcolormeter": "屏幕取色器,设计师临时取色的小神器。",
        "com.apple.diskutility": "管磁盘、分区、格式化和急救,别乱点但很重要。",
        "com.apple.facetime": "苹果生态里的音视频通话入口。",
        "com.apple.findmy": "找设备、找物品,也让丢失的 Mac 有回家的线索。",
        "com.apple.fontbook": "字体安装和管理中心,设计前先管好字体。",
        "com.apple.freeform": "无限白板,适合头脑风暴、草图和乱七八糟的想法。",
        "com.apple.games": "Apple 游戏入口,集中管理可玩的 Apple Arcade 内容。",
        "com.apple.grapher": "把方程画成图,数学可视化的隐藏工具。",
        "com.apple.home": "控制 HomeKit 设备,让 Mac 也能管家里的灯和空调。",
        "com.apple.image_capture": "从相机或扫描仪导入图片,朴素但稳定。",
        "com.apple.generativeplaygroundapp": "用 Apple 智能生成图像,适合快速玩创意草稿。",
        "com.apple.imovieapp": "入门级视频剪辑,家庭视频和轻量短片够用。",
        "com.apple.screencontinuity": "在 Mac 上直接操作 iPhone,少一次伸手拿手机。",
        "com.apple.journal": "记录日常片段,让碎碎念慢慢变成个人时间线。",
        "com.apple.iwork.keynote": "苹果式演示文稿工具,做漂亮幻灯片很顺手。",
        "com.apple.magnifier": "放大屏幕细节,给视力和精细检查一个帮手。",
        "com.apple.mail": "系统邮件客户端,多个邮箱可以集中处理。",
        "com.apple.maps": "查路线、看城市,也能把目的地推给 iPhone。",
        "com.apple.mobilesms": "iMessage 和短信入口,Mac 上回消息更快。",
        "com.apple.migrateassistant": "换新 Mac 时搬家,把旧资料迁到新机器。",
        "com.apple.exposelauncher": "窗口和桌面的空中俯视图,乱了就看它。",
        "com.apple.music": "本地音乐和 Apple Music 的播放器。",
        "com.apple.news": "苹果新闻入口,适合看精选媒体内容。",
        "com.apple.notes": "最快的系统备忘录,文字、图片、清单都能收。",
        "com.apple.iwork.numbers": "苹果表格工具,轻量报表比 Excel 更清爽。",
        "com.apple.iwork.pages": "苹果文字处理器,写文档和排版都好看。",
        "com.apple.passwords": "系统密码管家,账号、验证码和通行密钥集中管。",
        "com.apple.mobilephone": "通过 iPhone 在 Mac 上接打电话。",
        "com.apple.photobooth": "摄像头自拍和小特效,测试摄像头也方便。",
        "com.apple.photos": "系统照片库,管理、编辑和同步照片的主入口。",
        "com.apple.podcasts": "听播客的官方入口,通勤和学习都能用。",
        "com.apple.preview": "Mac 最被低估的工具:看图、批注 PDF、简单编辑全能。",
        "com.apple.printcenter": "查看和管理打印队列,卡纸卡任务先看这里。",
        "com.apple.quicktimeplayerx": "播放、录屏、录音都能干的老牌工具。",
        "com.apple.reminders": "待办和提醒事项,适合抓住日常小任务。",
        "com.apple.safari": "Apple 官方浏览器,省电、隐私和生态联动是强项。",
        "com.apple.screensharing": "远程控制另一台 Mac,帮人修电脑很方便。",
        "com.apple.screenshot.launcher": "截图和录屏入口,Shift-Command-5 的背后就是它。",
        "com.apple.scripteditor2": "写和运行 AppleScript,控制老派 Mac 自动化。",
        "com.apple.sfsymbols": "Apple 官方符号库,做原生界面必备。",
        "com.apple.shortcuts": "把多个动作串成一键流程,是现代版 Automator。",
        "com.apple.siri.launcher": "语音助手入口,能查信息、开 App、控制系统。",
        "com.apple.stickies": "桌面便利贴,临时记事就贴在眼前。",
        "com.apple.stocks": "看股票和市场动态的小面板。",
        "com.apple.systemprofiler": "查看硬件、软件和设备详情,查配置最准确。",
        "com.apple.systempreferences": "Mac 设置总入口,几乎所有系统开关都在这里。",
        "com.apple.terminal": "命令行入口,Mac 的隐藏驾驶舱。",
        "com.apple.textedit": "最朴素的文本编辑器,开 TXT 和 RTF 很快。",
        "com.apple.backup.launcher": "系统备份工具,后悔药就靠它提前准备。",
        "com.apple.helpviewer": "苹果官方小技巧,偶尔能发现你没用过的功能。",
        "com.apple.tv": "Apple TV 和本地影视内容的入口。",
        "com.apple.voicememos": "快速录音,会议灵感和采访素材先存下来。",
        "com.apple.voiceoverutility": "管理读屏辅助功能,让 Mac 更适合视觉障碍用户。",
        "com.apple.weather": "天气预报入口,出门前看一眼更稳。",
        "com.apple.dt.xcode": "苹果开发全家桶,写 iOS 和 macOS App 的主战场。"
        "com.apple.activitymonitor": "Mac 的仪表盘,专看谁在耗电、吃内存、拖慢电脑",
        "com.apple.airport.airportutility": "管理 AirPort 和 Time Capsule 的老派网络工具",
        "com.apple.appstore": "Mac 官方软件商店,找应用、更新和订阅都从这里开始",
        "com.apple.apps.launcher": "一个轻量应用入口,帮你从系统层面快速打开 App",
        "com.apple.audio.audiomidisetup": "管音频设备和 MIDI 路由,音乐人和调试党很常用",
        "com.apple.automator": "不写代码也能串起一串 Mac 自动化动作",
        "com.apple.bluetoothfileexchange": "用蓝牙收发文件,古早但关键时能救急",
        "com.apple.ibooksx": "阅读和管理电子书的入口,适合把 Mac 变成书桌",
        "com.apple.bootcampassistant": "Intel Mac 安装 Windows 的官方向导",
        "com.apple.calculator": "小计算器,单位换算和程序员模式也藏在里面",
        "com.apple.ical": "把日程从脑子里搬出来,给时间一张可视化地图",
        "com.apple.chess": "系统自带国际象棋,摸鱼时比刷网页更优雅",
        "com.apple.clock": "闹钟、计时器、秒表和世界时钟的统一入口",
        "com.apple.colorsyncutility": "查看和修复色彩配置文件,处理显示和打印色差",
        "com.apple.console": "看系统日志的窗口,排查异常时很有用",
        "com.apple.addressbook": "通讯录中枢,邮件、信息和 FaceTime 都会用到它",
        "com.apple.dictionary": "离线词典和同义词库,写作查词不用开浏览器",
        "com.apple.digitalcolormeter": "屏幕取色器,设计师临时取色的小神器",
        "com.apple.diskutility": "管磁盘、分区、格式化和急救,别乱点但很重要",
        "com.apple.facetime": "苹果生态里的音视频通话入口",
        "com.apple.findmy": "找设备、找物品,也让丢失的 Mac 有回家的线索",
        "com.apple.fontbook": "字体安装和管理中心,设计前先管好字体",
        "com.apple.freeform": "无限白板,适合头脑风暴、草图和乱七八糟的想法",
        "com.apple.games": "Apple 游戏入口,集中管理可玩的 Apple Arcade 内容",
        "com.apple.grapher": "把方程画成图,数学可视化的隐藏工具",
        "com.apple.home": "控制 HomeKit 设备,让 Mac 也能管家里的灯和空调",
        "com.apple.image_capture": "从相机或扫描仪导入图片,朴素但稳定",
        "com.apple.generativeplaygroundapp": "用 Apple 智能生成图像,适合快速玩创意草稿",
        "com.apple.imovieapp": "入门级视频剪辑,家庭视频和轻量短片够用",
        "com.apple.screencontinuity": "在 Mac 上直接操作 iPhone,少一次伸手拿手机",
        "com.apple.journal": "记录日常片段,让碎碎念慢慢变成个人时间线",
        "com.apple.iwork.keynote": "苹果式演示文稿工具,做漂亮幻灯片很顺手",
        "com.apple.magnifier": "放大屏幕细节,给视力和精细检查一个帮手",
        "com.apple.mail": "系统邮件客户端,多个邮箱可以集中处理",
        "com.apple.maps": "查路线、看城市,也能把目的地推给 iPhone",
        "com.apple.mobilesms": "iMessage 和短信入口,Mac 上回消息更快",
        "com.apple.migrateassistant": "换新 Mac 时搬家,把旧资料迁到新机器",
        "com.apple.exposelauncher": "窗口和桌面的空中俯视图,乱了就看它",
        "com.apple.music": "本地音乐和 Apple Music 的播放器",
        "com.apple.news": "苹果新闻入口,适合看精选媒体内容",
        "com.apple.notes": "最快的系统备忘录,文字、图片、清单都能收",
        "com.apple.iwork.numbers": "苹果表格工具,轻量报表比 Excel 更清爽",
        "com.apple.iwork.pages": "苹果文字处理器,写文档和排版都好看",
        "com.apple.passwords": "系统密码管家,账号、验证码和通行密钥集中管",
        "com.apple.mobilephone": "通过 iPhone 在 Mac 上接打电话",
        "com.apple.photobooth": "摄像头自拍和小特效,测试摄像头也方便",
        "com.apple.photos": "系统照片库,管理、编辑和同步照片的主入口",
        "com.apple.podcasts": "听播客的官方入口,通勤和学习都能用",
        "com.apple.preview": "Mac 最被低估的工具:看图、批注 PDF、简单编辑全能",
        "com.apple.printcenter": "查看和管理打印队列,卡纸卡任务先看这里",
        "com.apple.quicktimeplayerx": "播放、录屏、录音都能干的老牌工具",
        "com.apple.reminders": "待办和提醒事项,适合抓住日常小任务",
        "com.apple.safari": "Apple 官方浏览器,省电、隐私和生态联动是强项",
        "com.apple.screensharing": "远程控制另一台 Mac,帮人修电脑很方便",
        "com.apple.screenshot.launcher": "截图和录屏入口,Shift-Command-5 的背后就是它",
        "com.apple.scripteditor2": "写和运行 AppleScript,控制老派 Mac 自动化",
        "com.apple.sfsymbols": "Apple 官方符号库,做原生界面必备",
        "com.apple.shortcuts": "把多个动作串成一键流程,是现代版 Automator",
        "com.apple.siri.launcher": "语音助手入口,能查信息、开 App、控制系统",
        "com.apple.stickies": "桌面便利贴,临时记事就贴在眼前",
        "com.apple.stocks": "看股票和市场动态的小面板",
        "com.apple.systemprofiler": "查看硬件、软件和设备详情,查配置最准确",
        "com.apple.systempreferences": "Mac 设置总入口,几乎所有系统开关都在这里",
        "com.apple.terminal": "命令行入口,Mac 的隐藏驾驶舱",
        "com.apple.textedit": "最朴素的文本编辑器,开 TXT 和 RTF 很快",
        "com.apple.backup.launcher": "系统备份工具,后悔药就靠它提前准备",
        "com.apple.helpviewer": "苹果官方小技巧,偶尔能发现你没用过的功能",
        "com.apple.tv": "Apple TV 和本地影视内容的入口",
        "com.apple.voicememos": "快速录音,会议灵感和采访素材先存下来",
        "com.apple.voiceoverutility": "管理读屏辅助功能,让 Mac 更适合视觉障碍用户",
        "com.apple.weather": "天气预报入口,出门前看一眼更稳",
        "com.apple.dt.xcode": "苹果开发全家桶,写 iOS 和 macOS App 的主战场"
    ]
    private static let byName: [String: String] = [
        "SF Symbols": "Apple 官方符号库,做原生界面必备。"
        "SF Symbols": "Apple 官方符号库,做原生界面必备"
    ]
    static func note(for app: AppInfo) -> String? {
Apptag/SmartCategorization/SmartCategory.swift
@@ -100,43 +100,43 @@
        case .browser:
            return 4
        case .communication:
            return 3
            return 4
        case .productivity:
            return 2
        case .fileManagement:
            return 5
        case .transfer:
            return 2
        case .development:
            return 4
        case .design:
        case .transfer:
            return 4
        case .development:
            return 3
        case .design:
            return 7
        case .writing:
            return 1
            return 2
        case .media:
            return 7
        case .video:
            return 7
        case .audio:
            return 3
            return 7
        case .picturePhoto:
            return 4
            return 7
        case .utilities:
            return 5
        case .system:
            return 0
        case .systemEnhancement:
            return 6
            return 0
        case .entertainment:
            return 6
            return 7
        case .game:
            return 6
        case .finance:
            return 2
        case .education:
            return 5
            return 2
        case .aiTools:
            return 6
            return 3
        case .security:
            return 1
        case .other:
Apptag/SmartCategorization/SmartStartService.swift
@@ -315,6 +315,10 @@
                continue
            }
            if let result = TagDatabase.ensureSystemTag(for: categoryID, in: &store) {
                if var tagDef = store.tags[result.name] {
                    tagDef.color = categoryID.defaultColorIndex
                    store.tags[result.name] = tagDef
                }
                if result.created {
                    createdTagCount += 1
                }
CHANGELOG.md
@@ -12,6 +12,8 @@
- 修复右侧标签列表点击后可能停留在拖拽视觉状态,导致后续标签无法正常点击切换的问题
- 修复拖拽状态重置后 App 图标网格可能大量不渲染的问题,确保平铺、容器、网格视图切换后图标完整显示
- 修复快速滑过 App 图标时旧用途气泡可能冻结在上一 App 上的问题,让新 hover 目标主动接管气泡状态
- Smart Start 初始化目录切换为使用最新整理的 `tager` 字段生成运行时 JSON,覆盖 `5231` 个 App,并将 `4431` 条中文默认备注扩展为 29 种语言运行时备注
- 统一 Smart Start 默认系统标签颜色分组,让网络/文件流、工作知识、开发 AI、内容媒体、系统能力等语义更接近的标签使用更一致的默认颜色
- 版本号更新为 `7.3.5`,Build 更新为 `744`
## [7.3.4] — 2026-05-18
Research/SmartStart/Scripts/build-ultimate-default-catalog.mjs
@@ -10,10 +10,7 @@
const outputDir = path.join(researchDir, "UltimateDefaultCatalog");
const sources = {
  master5000: path.join(researchDir, "MacCommonApps_CandidateTop5000_Master.csv"),
  persona: path.join(researchDir, "PersonaTopApps", "MacPersonaTopApps_Unique.csv"),
  game: path.join(researchDir, "game.csv"),
  top1000: path.join(researchDir, "AppDefaultTags_Review.csv"),
  curatedCatalog: path.join(outputDir, "SmartStart_UltimateDefaultCatalog.csv"),
  appleNotes: path.join(projectDir, "Apptag", "AppleDefaultAppNotes.swift"),
  localizationDir: path.join(projectDir, "Apptag", "Localization"),
};
@@ -53,98 +50,46 @@
const stableTags = new Set(stableTagOrder);
const noteLimit = 80;
const sourcePriority = {
  apple: 100,
  persona: 90,
  top1000: 80,
  game: 70,
  master5000: 60,
};
const tagOverridesByNormalizedName = new Map(Object.entries({
  "android-ndk": ["development", "utilities"],
  "codeql": ["development", "security"],
  "prince": ["writing", "development", "utilities"],
  "qlstephen": ["utilities", "system-enhancement"],
  "kotlin-lsp": ["development"],
  "ghdl": ["development"],
  "android-sdk-platform-tools": ["development", "utilities"],
  "android-sdk-command-line-tools": ["development", "utilities"],
}));
const appleCatalogMetadata = new Map(Object.entries({
  "com.apple.activitymonitor": { name: "Activity Monitor", tags: ["system", "utilities", "system-enhancement"] },
  "com.apple.airport.airportutility": { name: "AirPort Utility", tags: ["system", "utilities"] },
  "com.apple.appstore": { name: "App Store", tags: ["system", "utilities"] },
  "com.apple.apps.launcher": { name: "Apps", tags: ["system", "utilities"] },
  "com.apple.audio.audiomidisetup": { name: "Audio MIDI Setup", tags: ["audio", "system", "utilities"] },
  "com.apple.automator": { name: "Automator", tags: ["system-enhancement", "utilities"] },
  "com.apple.bluetoothfileexchange": { name: "Bluetooth File Exchange", tags: ["transfer", "system", "utilities"] },
  "com.apple.ibooksx": { name: "Books", tags: ["writing", "education", "entertainment"] },
  "com.apple.bootcampassistant": { name: "Boot Camp Assistant", tags: ["system", "utilities"] },
  "com.apple.calculator": { name: "Calculator", tags: ["utilities"] },
  "com.apple.ical": { name: "Calendar", tags: ["productivity"] },
  "com.apple.chess": { name: "Chess", tags: ["game", "entertainment"] },
  "com.apple.clock": { name: "Clock", tags: ["productivity", "utilities"] },
  "com.apple.colorsyncutility": { name: "ColorSync Utility", tags: ["design", "picture-photo", "system", "utilities"] },
  "com.apple.console": { name: "Console", tags: ["system", "utilities"] },
  "com.apple.addressbook": { name: "Contacts", tags: ["communication", "productivity"] },
  "com.apple.dictionary": { name: "Dictionary", tags: ["writing", "education", "utilities"] },
  "com.apple.digitalcolormeter": { name: "Digital Color Meter", tags: ["design", "picture-photo", "utilities"] },
  "com.apple.diskutility": { name: "Disk Utility", tags: ["system", "utilities"] },
  "com.apple.facetime": { name: "FaceTime", tags: ["communication", "video"] },
  "com.apple.findmy": { name: "Find My", tags: ["system", "utilities"] },
  "com.apple.fontbook": { name: "Font Book", tags: ["design", "utilities"] },
  "com.apple.freeform": { name: "Freeform", tags: ["productivity", "design"] },
  "com.apple.games": { name: "Games", tags: ["game", "entertainment"] },
  "com.apple.grapher": { name: "Grapher", tags: ["education", "utilities"] },
  "com.apple.home": { name: "Home", tags: ["system", "utilities"] },
  "com.apple.image_capture": { name: "Image Capture", tags: ["picture-photo", "transfer", "utilities"] },
  "com.apple.generativeplaygroundapp": { name: "Image Playground", tags: ["ai-tools", "picture-photo", "design"] },
  "com.apple.imovieapp": { name: "iMovie", tags: ["video", "media"] },
  "com.apple.screencontinuity": { name: "iPhone Mirroring", tags: ["system-enhancement", "utilities"] },
  "com.apple.journal": { name: "Journal", tags: ["writing", "productivity"] },
  "com.apple.iwork.keynote": { name: "Keynote", tags: ["productivity", "writing"] },
  "com.apple.magnifier": { name: "Magnifier", tags: ["system", "utilities"] },
  "com.apple.mail": { name: "Mail", tags: ["communication"] },
  "com.apple.maps": { name: "Maps", tags: ["productivity", "utilities"] },
  "com.apple.mobilesms": { name: "Messages", tags: ["communication"] },
  "com.apple.migrateassistant": { name: "Migration Assistant", tags: ["transfer", "system", "utilities"] },
  "com.apple.exposelauncher": { name: "Mission Control", tags: ["system", "system-enhancement"] },
  "com.apple.music": { name: "Music", tags: ["audio", "media", "entertainment"] },
  "com.apple.news": { name: "News", tags: ["media", "writing"] },
  "com.apple.notes": { name: "Notes", tags: ["writing", "productivity"] },
  "com.apple.iwork.numbers": { name: "Numbers", tags: ["productivity"] },
  "com.apple.iwork.pages": { name: "Pages", tags: ["writing"] },
  "com.apple.passwords": { name: "Passwords", tags: ["security", "utilities"] },
  "com.apple.mobilephone": { name: "Phone", tags: ["communication"] },
  "com.apple.photobooth": { name: "Photo Booth", tags: ["picture-photo", "media", "entertainment"] },
  "com.apple.photos": { name: "Photos", tags: ["picture-photo", "media"] },
  "com.apple.podcasts": { name: "Podcasts", tags: ["audio", "media", "entertainment"] },
  "com.apple.preview": { name: "Preview", tags: ["picture-photo", "writing", "utilities"] },
  "com.apple.printcenter": { name: "Print Center", tags: ["system", "utilities"] },
  "com.apple.quicktimeplayerx": { name: "QuickTime Player", tags: ["video", "audio", "media"] },
  "com.apple.reminders": { name: "Reminders", tags: ["productivity"] },
  "com.apple.safari": { name: "Safari", tags: ["browser"] },
  "com.apple.screensharing": { name: "Screen Sharing", tags: ["system", "communication", "utilities"] },
  "com.apple.screenshot.launcher": { name: "Screenshot", tags: ["picture-photo", "video", "utilities"] },
  "com.apple.scripteditor2": { name: "Script Editor", tags: ["development", "system-enhancement", "utilities"] },
  "com.apple.sfsymbols": { name: "SF Symbols", tags: ["design", "development"] },
  "com.apple.shortcuts": { name: "Shortcuts", tags: ["system-enhancement", "productivity", "utilities"] },
  "com.apple.siri.launcher": { name: "Siri", tags: ["system", "utilities"] },
  "com.apple.stickies": { name: "Stickies", tags: ["writing", "productivity"] },
  "com.apple.stocks": { name: "Stocks", tags: ["finance"] },
  "com.apple.systemprofiler": { name: "System Information", tags: ["system", "utilities"] },
  "com.apple.systempreferences": { name: "System Settings", tags: ["system", "utilities"] },
  "com.apple.terminal": { name: "Terminal", tags: ["development", "system", "utilities"] },
  "com.apple.textedit": { name: "TextEdit", tags: ["writing"] },
  "com.apple.backup.launcher": { name: "Time Machine", tags: ["system", "utilities"] },
  "com.apple.helpviewer": { name: "Tips", tags: ["education", "utilities"] },
  "com.apple.tv": { name: "TV", tags: ["video", "media", "entertainment"] },
  "com.apple.voicememos": { name: "Voice Memos", tags: ["audio", "media"] },
  "com.apple.voiceoverutility": { name: "VoiceOver Utility", tags: ["system", "utilities"] },
  "com.apple.weather": { name: "Weather", tags: ["utilities"] },
  "com.apple.dt.xcode": { name: "Xcode", tags: ["development"] },
const tagerToStableTags = new Map(Object.entries({
  "3d-cad": ["design"],
  "Automation": ["productivity", "system-enhancement"],
  "Font": ["design", "utilities"],
  "GTD": ["productivity"],
  "Meeting": ["communication", "productivity"],
  "Notes": ["productivity", "writing"],
  "PDF": ["writing", "utilities"],
  "ai-tools": ["ai-tools"],
  "api-tools": ["development"],
  "audio": ["media", "audio"],
  "browser": ["browser"],
  "communication": ["communication"],
  "database-tools": ["development"],
  "design": ["design"],
  "device-management": ["utilities", "system-enhancement"],
  "devops": ["development"],
  "diagramming": ["design"],
  "education": ["education"],
  "entertainment": ["entertainment"],
  "file-management": ["file-management", "utilities"],
  "finance": ["finance"],
  "game": ["entertainment", "game"],
  "ide": ["development"],
  "input-tools": ["utilities", "system-enhancement"],
  "media": ["media"],
  "network-tools": ["utilities"],
  "office": ["productivity", "writing"],
  "picture-photo": ["media", "picture-photo"],
  "runtime-sdk": ["development"],
  "security": ["security"],
  "system": ["system", "utilities"],
  "system-maintenance": ["utilities", "system-enhancement"],
  "terminal-tools": ["development"],
  "transfer": ["transfer", "utilities"],
  "ui-prototyping": ["design"],
  "utilities": ["utilities"],
  "video": ["media", "video"],
  "window-management": ["utilities", "system-enhancement"],
  "writing": ["writing"],
}));
function parseCSV(text) {
@@ -258,15 +203,19 @@
  return Array.from(String(value ?? "")).length;
}
function stripTerminalPunctuation(value) {
  return String(value ?? "")
    .replace(/\s+/g, " ")
    .trim()
    .replace(/[。.\.!!\??;;::,,、]+$/u, "")
    .trim();
}
function truncateNote(value) {
  const clean = String(value ?? "").replace(/\s+/g, " ").trim();
  const clean = stripTerminalPunctuation(value);
  if (charLength(clean) <= noteLimit) return clean;
  let sliced = Array.from(clean).slice(0, noteLimit).join("");
  sliced = sliced.replace(/[,、;:,.!!??]$/, "");
  if (!/[。.!!??]$/.test(sliced)) {
    sliced = `${Array.from(sliced).slice(0, noteLimit - 1).join("")}。`;
  }
  return sliced;
  return stripTerminalPunctuation(sliced);
}
function loadCategoryTranslations() {
@@ -291,11 +240,10 @@
  const labels = shownTags.map((tag) => categoryMap[tag] ?? tag);
  const joiner = ["ar", "ar-Najdi"].includes(code) ? "، " : ["zh-Hans", "zh-Hant", "ja"].includes(code) ? "、" : ", ";
  const separator = ["zh-Hans", "zh-Hant", "ja"].includes(code) ? ":" : ": ";
  const end = ["zh-Hans", "zh-Hant", "ja"].includes(code) ? "。" : ".";
  const full = `${name}${separator}${labels.join(joiner)}${end}`;
  const full = `${name}${separator}${labels.join(joiner)}`;
  if (charLength(full) <= noteLimit) return full;
  const compact = `${name}${separator}${labels[0] ?? "App"}${end}`;
  const compact = `${name}${separator}${labels[0] ?? "App"}`;
  if (charLength(compact) <= noteLimit) return compact;
  return truncateNote(compact);
@@ -391,197 +339,93 @@
  stats.mergedRows += 1;
}
function mapCuratedTags(value) {
  const tags = [];
  const unknownTokens = [];
  for (const token of splitTags(value)) {
    const mapped = tagerToStableTags.get(token);
    if (!mapped) {
      unknownTokens.push(token);
      continue;
    }
    tags.push(...mapped);
  }
  return {
    tags: orderedTags(tags.length > 0 ? tags : ["other"]),
    unknownTokens,
  };
}
function build() {
  fs.mkdirSync(outputDir, { recursive: true });
  const translations = loadCategoryTranslations();
  const appleNotes = parseAppleNotes();
  const entries = [];
  const byBundle = new Map();
  const byName = new Map();
  const ambiguous = [];
  const mergedGroups = [];
  const previousRuntimeEntries = fs.existsSync(outputs.runtimeJSON)
    ? new Map(
        (JSON.parse(fs.readFileSync(outputs.runtimeJSON, "utf8")).entries ?? []).map((entry) => [entry.normalizedName, entry]),
      )
    : new Map();
  const curatedRows = readCSVObjects(sources.curatedCatalog);
  const unknownTagerTokens = new Set();
  const stats = {
    sourceRows: {},
    mergedRows: 0,
    curatedRows: curatedRows.length,
    rowsWithSourceNotes: 0,
    appleNotesAttached: 0,
    appleNotesSwiftOnly: 0,
    tagChangedVsPrevious: 0,
    zhNoteChangedVsPrevious: 0,
    bundleChangedVsPrevious: 0,
    missingLocalizedNotes: 0,
  };
  const finalRows = curatedRows.map((row, index) => {
    const name = String(row.Name ?? "").trim();
    const normalizedName = String(row.normalizedName ?? "").trim() || normalizeName(name);
    const bundleIdentifier = normalizeBundle(row.bundleIdentifier);
    const legacyTags = orderedTags(splitTags(row.defaultTag));
    const mapping = mapCuratedTags(row.tager);
    for (const token of mapping.unknownTokens) unknownTagerTokens.add(token);
  function addEntry(incoming) {
    if (!incoming.name || incoming.tags.length === 0) return;
    const bundleKey = incoming.bundleIdentifier?.toLowerCase() ?? null;
    let existing = bundleKey ? byBundle.get(bundleKey) : null;
    if (!existing) {
      const normalizedExisting = byName.get(incoming.normalizedName);
      if (normalizedExisting) {
        const existingBundle = normalizedExisting.bundleIdentifier?.toLowerCase() ?? null;
        if (bundleKey && existingBundle && bundleKey !== existingBundle) {
          ambiguous.push({
            kind: "same_normalized_different_bundle",
            identity: incoming.normalizedName,
            names: [normalizedExisting.name, incoming.name].join(" | "),
            bundles: [normalizedExisting.bundleIdentifier, incoming.bundleIdentifier].join(" | "),
            sources: [[...normalizedExisting.sources].join("|"), incoming.source].join(" | "),
            decision: "kept_separate_for_review",
          });
        } else {
          existing = normalizedExisting;
        }
      }
    }
    if (!existing) {
      const entry = {
        name: incoming.name,
        normalizedName: incoming.normalizedName,
        bundleIdentifier: incoming.bundleIdentifier,
        tags: incoming.tags,
        noteZH: incoming.noteZH,
        rank: incoming.rank,
        sourceEvidence: new Set([...incoming.evidence, incoming.source].filter(Boolean)),
        sources: new Set([incoming.source]),
        namePriority: incoming.priority,
        notePriority: incoming.noteZH ? incoming.priority : -1,
      };
      entries.push(entry);
      byName.set(entry.normalizedName, entry);
      if (bundleKey) byBundle.set(bundleKey, entry);
      return;
    }
    const beforeSources = new Set(existing.sources);
    mergeInto(existing, incoming, stats);
    if (incoming.bundleIdentifier) byBundle.set(incoming.bundleIdentifier.toLowerCase(), existing);
    byName.set(existing.normalizedName, existing);
    mergedGroups.push({
      identity: bundleKey ? `bundle:${bundleKey}` : `name:${incoming.normalizedName}`,
      sourcesBefore: [...beforeSources].join("|"),
      mergedSource: incoming.source,
      finalName: existing.name,
    });
  }
  const masterRows = readCSVObjects(sources.master5000);
  stats.sourceRows.master5000 = masterRows.length;
  for (const row of masterRows) {
    addEntry(rawEntry({
      source: "master5000",
      name: row.name,
      normalizedName: row.normalizedName,
      bundleIdentifier: row.bundleIdentifier,
      tags: splitTags(row.defaultTagIDs),
      rank: row.rank,
      evidence: splitTags(row.sourceEvidence),
    }));
  }
  const topRows = readCSVObjects(sources.top1000);
  stats.sourceRows.top1000 = topRows.length;
  for (const row of topRows) {
    addEntry(rawEntry({
      source: "top1000",
      name: row.name,
      normalizedName: row.normalizedName,
      bundleIdentifier: row.bundleIdentifier,
      tags: splitTags(row.defaultTagIDs),
      rank: row.rank,
      evidence: splitTags(row.sourceEvidence),
    }));
  }
  const gameRows = readCSVObjects(sources.game);
  stats.sourceRows.game = gameRows.length;
  for (const row of gameRows) {
    addEntry(rawEntry({
      source: "game",
      name: row.name,
      normalizedName: normalizeName(row.name),
      bundleIdentifier: row.bundleIdentifier,
      tags: orderedTags([...splitTags(row.defaultTagIDs), "entertainment"]),
      rank: row.rank,
      evidence: ["game_seed"],
    }));
  }
  const personaRows = readCSVObjects(sources.persona);
  stats.sourceRows.persona = personaRows.length;
  for (const row of personaRows) {
    addEntry(rawEntry({
      source: "persona",
      name: row.appName,
      normalizedName: row.normalizedName,
      bundleIdentifier: row.bundleIdentifier,
      tags: splitTags(row.defaultTagIDs),
      noteZH: row.defaultNoteZH,
      rank: row.masterRank || row.bestPersonaRank,
      evidence: ["persona_top_apps", ...(row.personaIDs ? [`personas:${row.personaIDs}`] : [])],
    }));
  }
  stats.sourceRows.apple = appleNotes.byBundle.size;
  for (const [bundleIdentifier, note] of appleNotes.byBundle.entries()) {
    const metadata = appleCatalogMetadata.get(bundleIdentifier);
    if (!metadata) continue;
    addEntry(rawEntry({
      source: "apple",
      name: metadata.name,
      normalizedName: normalizeName(metadata.name),
      bundleIdentifier,
      tags: metadata.tags,
      noteZH: note,
      rank: 50,
      evidence: ["apple_default_notes"],
    }));
  }
  for (const entry of entries) {
    const bundleNote = entry.bundleIdentifier
      ? appleNotes.byBundle.get(entry.bundleIdentifier.toLowerCase())
      : null;
    const nameNote = appleNotes.byName.get(entry.name);
    const note = bundleNote ?? nameNote;
    if (note) {
      entry.noteZH = truncateNote(note);
      entry.notePriority = sourcePriority.apple;
      entry.sources.add("apple");
      entry.sourceEvidence.add("apple_default_notes");
    let noteZH = truncateNote(row["defaultNote-ZH"]);
    let usedAppleFallback = false;
    if (!noteZH) {
      const bundleNote = bundleIdentifier ? appleNotes.byBundle.get(bundleIdentifier.toLowerCase()) : null;
      const nameNote = appleNotes.byName.get(name);
      if (bundleNote || nameNote) {
        noteZH = truncateNote(bundleNote ?? nameNote);
        usedAppleFallback = true;
      stats.appleNotesAttached += 1;
    }
    } else {
      stats.rowsWithSourceNotes += 1;
  }
  for (const bundle of appleNotes.byBundle.keys()) {
    if (!byBundle.has(bundle)) stats.appleNotesSwiftOnly += 1;
    const notes = buildLocalizedNotes(name, mapping.tags, noteZH, translations);
    const previous = previousRuntimeEntries.get(normalizedName);
    if (previous) {
      if (JSON.stringify(previous.defaultTag ?? []) !== JSON.stringify(mapping.tags)) {
        stats.tagChangedVsPrevious += 1;
      }
      if (((previous.notes ?? {})["zh-Hans"] ?? "") !== (noteZH ?? "")) {
        stats.zhNoteChangedVsPrevious += 1;
      }
      if ((previous.bundleIdentifier ?? null) !== bundleIdentifier) {
        stats.bundleChangedVsPrevious += 1;
      }
  }
  const finalRows = entries
    .map((entry) => {
      entry.tags = orderedTags(entry.tags);
      if (entry.tags.length === 1 && entry.tags[0] === "other") {
        const overrideTags = tagOverridesByNormalizedName.get(entry.normalizedName);
        if (overrideTags) {
          entry.tags = orderedTags(overrideTags);
          entry.sourceEvidence.add("manual_other_reduction");
        }
      }
      const notes = buildLocalizedNotes(entry.name, entry.tags, entry.noteZH, translations);
      return {
        name: entry.name,
        normalizedName: entry.normalizedName || normalizeName(entry.name),
        bundleIdentifier: entry.bundleIdentifier,
        tags: entry.tags,
        noteZH: entry.noteZH,
      rank: index + 1,
      name,
      normalizedName,
      bundleIdentifier,
      tags: mapping.tags,
      legacyTags,
      noteZH,
        notes,
        rank: entry.rank,
        sourceEvidence: [...entry.sourceEvidence].sort(),
        sources: [...entry.sources].sort(),
      sourceEvidence: usedAppleFallback
        ? ["curated_tager_catalog", "apple_default_notes"]
        : ["curated_tager_catalog"],
      };
    })
    .sort((left, right) => {
      const rankDiff = left.rank - right.rank;
      if (rankDiff !== 0) return rankDiff;
      return left.name.localeCompare(right.name);
    });
  const invalidTagRows = finalRows.filter((row) => row.tags.some((tag) => !stableTags.has(tag)));
@@ -595,32 +439,35 @@
      const note = row.notes[code];
      if (!note) {
        localizedNoteIssues.push({ name: row.name, code, issue: "missing" });
        stats.missingLocalizedNotes += 1;
      } else if (charLength(note) > noteLimit) {
        localizedNoteIssues.push({ name: row.name, code, issue: `over_limit:${charLength(note)}` });
      } else if (stripTerminalPunctuation(note) != note) {
        localizedNoteIssues.push({ name: row.name, code, issue: "trailing_punctuation" });
      }
    }
  }
  const reviewHeader = ["Name", "normalizedName", "defaultTag", "bundleIdentifier", "defaultNote-ZH"];
  const reviewRows = [
    reviewHeader,
    ...finalRows.map((row) => [
      row.name,
      row.normalizedName,
      row.tags.join("|"),
      row.bundleIdentifier ?? "null",
      row.noteZH ?? "",
  const cleanedCuratedRows = [
    ["Name", "normalizedName", "defaultTag", "tager", "bundleIdentifier", "defaultNote-ZH"],
    ...curatedRows.map((row) => [
      String(row.Name ?? "").trim(),
      String(row.normalizedName ?? "").trim() || normalizeName(row.Name ?? ""),
      String(row.defaultTag ?? "").trim(),
      String(row.tager ?? "").trim(),
      normalizeBundle(row.bundleIdentifier) ?? "null",
      truncateNote(row["defaultNote-ZH"]),
    ]),
  ];
  fs.writeFileSync(outputs.reviewCSV, `${stringifyCSV(reviewRows)}\n`);
  fs.writeFileSync(outputs.reviewCSV, `${stringifyCSV(cleanedCuratedRows)}\n`);
  const runtime = {
    version: 2,
    generatedAt: new Date().toISOString(),
    noteLimit,
    supportedLanguages: [...translations.keys()].sort(),
    entries: finalRows.map((row, index) => ({
      rank: index + 1,
    entries: finalRows.map((row) => ({
      rank: row.rank,
      name: row.name,
      normalizedName: row.normalizedName,
      bundleIdentifier: row.bundleIdentifier,
@@ -632,14 +479,26 @@
  fs.writeFileSync(outputs.runtimeJSON, `${JSON.stringify(runtime, null, 2)}\n`);
  const duplicateRows = [
    ["kind", "identity", "names", "bundles", "sources", "decision"],
    ...ambiguous.map((row) => [row.kind, row.identity, row.names, row.bundles, row.sources, row.decision]),
    ["name", "normalizedName", "tager", "legacyDefaultTag", "runtimeDefaultTag", "bundleIdentifier", "defaultNote-ZH"],
    ...finalRows
      .filter((row) => row.legacyTags.join("|") !== row.tags.join("|"))
      .map((row) => [
        row.name,
        row.normalizedName,
        (curatedRows[row.rank - 1]?.tager ?? ""),
        row.legacyTags.join("|"),
        row.tags.join("|"),
        row.bundleIdentifier ?? "null",
        row.noteZH ?? "",
      ]),
  ];
  fs.writeFileSync(outputs.duplicateReview, `${stringifyCSV(duplicateRows)}\n`);
  const sourceLines = Object.entries(stats.sourceRows)
    .map(([source, count]) => `- ${source}: ${count}`)
    .join("\n");
  const sourceLines = [
    "- curated CSV: `Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.csv`",
    `- curated rows: ${stats.curatedRows}`,
    `- unknown tager tokens: ${unknownTagerTokens.size}`,
  ].join("\n");
  const topTagLines = stableTagOrder
    .map((tag) => [tag, finalRows.filter((row) => row.tags.includes(tag)).length])
    .filter(([, count]) => count > 0)
@@ -664,14 +523,16 @@
- Final rows: ${finalRows.length}
- Rows with Chinese default notes: ${noteRows.length}
- Merged source rows: ${stats.mergedRows}
- Ambiguous duplicate candidates: ${ambiguous.length}
- Rows with source Chinese notes from curated CSV: ${stats.rowsWithSourceNotes}
- Rows with Apple note fallback attached: ${stats.appleNotesAttached}
- Invalid tag rows: ${invalidTagRows.length}
- Empty normalizedName rows: ${emptyNormalizedRows.length}
- Exact \`other\` rows: ${exactOtherRows.length}
- Mixed \`other\` rows after cleanup: ${mixedOtherRows.length}
- Apple notes attached to catalog rows: ${stats.appleNotesAttached}
- Apple notes still handled by Swift-only fallback: ${stats.appleNotesSwiftOnly}
- Tag changes vs previous runtime JSON: ${stats.tagChangedVsPrevious}
- Chinese note changes vs previous runtime JSON: ${stats.zhNoteChangedVsPrevious}
- Bundle identifier changes vs previous runtime JSON: ${stats.bundleChangedVsPrevious}
- Unknown tager tokens: ${unknownTagerTokens.size}
- Localized note issues: ${localizedNoteIssues.length}
## Tag Distribution
@@ -680,7 +541,7 @@
## Notes
- \`other\` is removed whenever a row has at least one meaningful tag.
- Runtime tags are generated from the curated CSV \`tager\` column, not the legacy \`defaultTag\` column.
- Runtime notes are generated for all supported localization files when a Chinese source note exists.
- Non-Chinese localized notes currently use localized tag-summary copy generated from existing category translations; high-traffic languages can be manually polished later.
`;
@@ -707,12 +568,13 @@
  return {
    finalRows: finalRows.length,
    noteRows: noteRows.length,
    ambiguousDuplicates: ambiguous.length,
    changedVsPrevious: stats.tagChangedVsPrevious,
    invalidTagRows: invalidTagRows.length,
    emptyNormalizedRows: emptyNormalizedRows.length,
    exactOtherRows: exactOtherRows.length,
    mixedOtherRows: mixedOtherRows.length,
    localizedNoteIssues: localizedNoteIssues.length,
    unknownTagerTokens: [...unknownTagerTokens].sort(),
    outputDir,
  };
}
Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.csv
Diff too large
Research/SmartStart/UltimateDefaultCatalog/SmartStart_UltimateDefaultCatalog.json
Diff too large