# Desktop App Skill

> 创建桌面 GUI 应用的通用规范约束, 与 GUI 框架无关. 当 agent 新建桌面应用项目, 或为现有桌面应用补充托盘图标, 窗口显隐, 版本展示, 日志与隔离调试, 终端退出, 自动更新等基础设施时使用.

- Skill: `azazo1/desktop-app-skill` (Agent Skill)
- Install (CLI): `npx skillmds@latest add azazo1/desktop-app-skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/azazo1/desktop-app-skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: azazo1 (https://skillmd.com/u/azazo1)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/azazo1/desktop-app-skill

---


# 桌面 GUI 应用通用规范

## 适用范围

- 适用于任意技术栈的桌面 GUI 应用 (egui, Tauri, Electron, Qt, Avalonia, Flutter 等), 包括新建项目与为已有应用补充托盘, 窗口管理, 日志, 更新等基础设施.
- 下文每一条都是验收标准, 以行为语义为准; 任务完成后逐条自查.
- 文中出现的具体 API 仅为常见技术栈的示例, 与所选框架不一致时, 按其原生方式复现相同语义即可.

## 托盘图标

- 左键单击托盘图标必须显示主窗口; 主窗口已经处于非隐藏状态时, 聚焦并置前它. 不允许实现成 "显示/隐藏" 切换, 也不允许关闭后重开窗口.
- 托盘组件需区分左键行为与右键菜单: 左键走显示窗口逻辑, 右键弹出菜单 (大多数托盘库可分别配置这两种行为).
- 右键菜单必须包含 "退出" 项, 点击后走统一的优雅退出路径: 停止后台服务, 刷盘日志, 关闭进程. 可以加二次确认弹窗, 但托盘 "退出" 项本身必须存在.
- 菜单首部用禁用项显示应用名和版本号, 例如 `<App Name> v0.13.0`.
- tooltip (hover hint) 必须显示当前应用名, 且放在第一行.
- 菜单与图标事件统一转成命令或消息发给 UI 线程处理, 不在事件回调里直接操作业务状态.

## 版本号展示

- 主界面固定位置 (如底部状态栏或 About 区域) 展示当前版本; 有新版本时该位置替换为 "新版本 X 可用" 链接, 点击打开更新窗口.
- 运行时版本号的显示格式与编译期注入机制遵循 create-github-release-flow skill 的运行时版本号约定; 桌面应用属于其二进制/应用分发类型, About 对话框等版本展示位置同样按其要求落实.

## 窗口显隐与后台驻留

- 后台持续服务类应用: 点击窗口关闭按钮 = 隐藏到托盘并继续运行, 不退出进程. 在窗口关闭事件中取消默认关闭行为, 改为隐藏窗口, macOS 同时隐藏 dock 图标.
- 必须提供 "启动时隐藏主窗口" 设置项并持久化; 开启后应用启动仅存在于托盘.
- macOS: dock 图标跟随主窗口显隐, 且是否跟随隐藏提供设置开关, 非 macOS 平台应隐藏该设置项. 实现上通过激活策略或 dock 显隐 API 切换 (如 AppKit 的 Regular/Accessory 策略, Electron 的 `app.dock.hide()`/`show()`), 平台判断封装在守卫函数里, 不散布在调用点; 显示窗口时先恢复 dock, 再显示并聚焦窗口. 同时处理 reopen/activate 事件 (点击 dock 或 Finder 再次打开), 唤起隐藏中的窗口; 注意窗口隐藏后 macOS 可能立刻补发 activate 事件, 需要在隐藏后的短暂窗口期内忽略该事件, 否则窗口会被再次唤起. Electron 的 `app.dock.show()` 返回 Promise, 需捕获失败并记日志.
- 全应用只有一个优雅退出入口 (停服务, 刷日志, 关窗口), 托盘退出, 确认弹窗与信号退出都汇入它.

## 单实例

- 同一数据目录下应用只允许一个实例运行: 二次启动时, 新进程把启动参数转发给已有实例后自行退出, 已有实例收到通知后显示并聚焦主窗口.
- 主窗口必须全局唯一: 所有唤起入口 (托盘点击, activate/reopen, 快捷键, 二次启动参数转发) 统一走同一个 "打开主窗口" 函数, 该函数在主窗口已存在时恢复 (若最小化), 显示并聚焦已有窗口, 不创建第二个主窗口.
- 锁机制按所选栈的惯用方式实现 (如 Electron 的 `requestSingleInstanceLock` 与 `second-instance` 事件, 其他栈用命名 socket, 命名互斥量或带 pid 存活检测的锁文件), 持锁实例退出时必须释放.
- 单实例锁的标识必须与数据目录绑定: `just debug` 的隔离实例使用不同数据目录, 应能与正式实例并行运行, 不被锁挡住.
- macOS 在系统层面天然单实例, 二次打开走 reopen 事件, 复用 dock reopen 的唤起逻辑; windows 与 linux 需要应用自行实现锁.

## 终端 Ctrl+C 优雅退出

- 从终端启动的实例收到 Ctrl+C 必须优雅退出: 注册 SIGINT 处理, 转成退出请求发给 UI 线程, 由统一退出入口收尾; 不允许进程被信号直接杀死而遗留后台服务或丢失日志.
- Windows: release 构建声明为 GUI 子系统 (链接器 SUBSYSTEM:WINDOWS 或等价设置), 避免发布版闪出控制台黑框; debug 构建保留控制台子系统, 保证终端里 Ctrl+C 可用且日志可观察.

## just debug 与日志

- 提供 `just debug` recipe, 以隔离配置启动调试实例: 数据目录与日志文件都重定向到项目中的 `target/<app>-debug/` 下 (该目录跟随你使用的编程语言和框架而定), 不触碰日常数据; 应用自身日志级别提升到最详细档. 示例 (Rust, 其他栈替换运行命令与日志级别变量):

```justfile
# 启动隔离数据目录的调试实例, 完整日志写入隔离目录.
debug:
    APP_DATA_DIR=target/app-debug APP_LOG_FILE=target/app-debug/app.log RUST_LOG=app=trace cargo run
```

- 应用支持环境变量覆盖数据目录与主日志文件路径; 未设置时日志写入数据目录, 按每日与单文件大小轮转并限制留存文件数.
- 日志基础设施: 使用语言生态的成熟日志设施, 而非 print, console.log 等临时输出; 默认 info 级别, 支持运行时切换更详细级别; panic/未捕获异常消息转写日志; 终端保留 info 输出便于观察.
- 日志内容优先覆盖: 用户操作与关键外部输入 (托盘点击, 菜单命令, 设置变更, 窗口显隐等), 便于审计和复现; 主要执行阶段与阶段耗时 (服务启停, 更新检查与下载等), 便于判断进程是否卡住; 关键数据变化和统计指标; 错误, 异常分支与必要上下文, 便于定位根因; 运行环境和资源信息.
- 层级按重要性区分: 关键流程 info, 细节诊断 debug 或 trace, 错误和异常分支包含足够上下文. 计算密集型短流程减少高频日志, 避免拖慢运行; 长时间后台任务 (更新下载, 同步, 批处理等) 必须阶段性输出进度, 防止卡死却无从判断.
- 日志文案自然, 简洁, 明了, 不堆叠过多字段, 不使用含义不清或过于生僻的缩写.

## 自动更新

- 安装版与便携版都必须带自动更新: 启动静默检查与手动检查, 下载带 SHA256 校验, 按平台差异安装, 完成后可重启生效;
- 发布侧的产物命名, SHA256SUMS 生成与版本 tag 流程由 create-github-release-flow skill 保证; 应用内 (客户端) 侧的机制设计见 `references/auto-update.md`, 实现前先通读.

## fake-dist 测试构建

- 提供 `just fake-dist` recipe, 复用 `just dist` 的平台打包路径, 产出专用于自动更新测试的 fake 构建.
- fake 构建不改包名: bundle id, package id 与应用名和正式应用保持一致, 仅把版本号注入为 `v0.0.0`, 保证任何正式 release 都比它新.
- 数据目录独立, 不与正式应用共享任何数据; 自动检查开关, 跳过版本等设置的读写隔离在 fake 自己的数据目录内. 单实例锁按规范与数据目录绑定, fake 构建因此可与正式实例并行运行.
- 本地产物命名在标准命名末尾追加 `-fake` (`<app>-v0.0.0-<platform>-<arch>-fake.<ext>`) 用于区分, release 不上传 fake 变体.
- fake 构建使用与正式应用完全相同的更新检测逻辑: 匹配标准资产, 下载并安装正式产物, 用于端到端验证更新流程.

