1
0
Files
nex/openspec/specs/desktop-app/spec.md

23 KiB
Raw Blame History

桌面应用

Purpose

TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源打包为单一可执行文件

Requirements

Requirement: Desktop 启动失败报告

Desktop SHALL 将无法进入可用状态的启动失败统一转换为包含阶段、用户消息和底层原因的启动错误,并通过统一报告器提示用户。

Scenario: 配置加载或验证失败

  • WHEN desktop 启动时 ~/.nex/config.yaml 无法解析或配置验证失败
  • THEN 系统 SHALL 生成 config 阶段启动错误
  • THEN 错误提示 SHALL 包含配置文件路径和失败原因
  • THEN 应用 SHALL 退出

Scenario: 日志初始化失败

  • WHEN desktop 启动时完整 logger 初始化失败
  • THEN 系统 SHALL 生成 logger 阶段启动错误
  • THEN 错误提示 SHALL 描述日志初始化失败并包含可操作的路径或权限线索
  • THEN 应用 SHALL 退出

Scenario: 数据库初始化或迁移失败

  • WHEN desktop 启动时数据库打开、连接、初始化或迁移失败
  • THEN 系统 SHALL 生成 databasemigration 阶段启动错误
  • THEN 错误提示 SHALL 描述数据库初始化失败或迁移失败
  • THEN 错误提示 SHALL NOT 暴露 MySQL password、完整 DSN 或 API key 等敏感信息
  • THEN 应用 SHALL 退出

Scenario: 嵌入资源或内部组件初始化失败

  • WHEN desktop 启动时前端嵌入资源、协议 adapter 或其他内部组件初始化失败
  • THEN 系统 SHALL 生成对应启动阶段的启动错误
  • THEN 错误提示 SHALL 描述应用初始化失败并建议查看日志或重新安装
  • THEN 应用 SHALL 退出

Scenario: 启动失败提示降级链

  • WHEN desktop 生成 fatal 启动错误
  • THEN 系统 SHALL 先尝试平台系统通知
  • THEN 系统 SHALL 在通知不可用或返回失败时尝试平台模态弹窗
  • THEN 系统 SHALL 在模态弹窗不可用或返回失败时输出到 stderr 或可用启动日志
  • THEN 每一次提示通道失败 SHALL 被记录但 SHALL NOT 阻止后续 fallback

Scenario: 提示通道调用前可用性检查

  • WHEN desktop 启动失败报告器准备调用任一系统通知或模态弹窗通道
  • THEN 系统 SHALL 在调用前检查该通道对应命令、系统 API 或图形会话条件是否可用
  • THEN 系统 SHALL 在通道不可用时跳过该通道并进入下一 fallback
  • THEN 系统 SHALL NOT 因某个通知或弹窗工具缺失而中断后续降级链

Scenario: 浏览器打开失败为非 fatal

  • WHEN desktop 后端服务和托盘已启动但浏览器自动打开失败
  • THEN 系统 SHALL 记录 warning
  • THEN 系统 SHALL 尝试通过非 fatal 提示告知用户可手动访问 http://localhost:<server.port>
  • THEN 应用 SHALL 继续运行

Requirement: macOS 通知和对话框降级策略

系统 SHALL 在 macOS 上优先使用通知中心提示 desktop 启动错误,并在通知不可用时降级到 AppleScript 模态告警。

Scenario: macOS 通知可用

  • WHEN desktop 在 macOS 上生成启动错误且通知命令可用
  • THEN 系统 SHALL 使用 osascript display notification 发送系统通知
  • THEN 通知 SHALL 包含应用名称和错误描述文本

Scenario: macOS 通知失败

  • WHEN macOS 系统通知命令不可用或返回失败
  • THEN 系统 SHALL 使用 osascript display alert 显示模态告警
  • THEN 模态告警 SHALL 使用 critical 错误语义
  • THEN 告警 SHALL 包含应用名称和错误描述文本

Scenario: macOS UI 提示均失败

  • WHEN macOS 系统通知和模态告警均失败
  • THEN 系统 SHALL 降级输出到 stderr 或可用启动日志

Requirement: Linux 启动错误提示降级策略

系统 SHALL 在 Linux 上为 desktop 启动错误按优先级使用可用的通知或对话框工具,优先使用系统通知栏,再降级到模态弹窗,最后降级到标准错误输出。该策略 SHALL 不移除既有通用信息提示能力。

Scenario: notify-send 可用

  • WHEN desktop 在 Linux 上生成启动错误且 notify-send 命令可用并存在图形会话
  • THEN 使用 notify-send 显示系统通知
  • AND 错误通知使用 -u critical 参数
  • AND 通知 SHALL 包含应用名称和错误描述文本

Scenario: kdialog passive popup 可用

  • WHEN notify-send 不可用或返回失败且 kdialog 命令可用
  • THEN 系统 SHALL 优先尝试使用 kdialog --passivepopup 显示系统通知式提示

Scenario: zenity 可用

  • WHEN Linux 通知工具不可用或返回失败且系统检测到 zenity 命令可用
  • THEN 使用 zenity 显示 GTK 风格错误对话框
  • AND 错误对话框使用 zenity --error 命令

Scenario: kdialog 模态对话框可用

  • WHEN zenity 不可用或返回失败且 kdialog 命令可用
  • THEN 使用 kdialog 显示 KDE 风格错误对话框
  • AND 错误对话框使用 kdialog --error 命令

Scenario: xmessage 可用

  • WHEN zenitykdialog 模态对话框均不可用或返回失败且 xmessage 命令可用
  • THEN 使用 xmessage 显示基础 X11 对话框
  • AND 对话框居中显示(-center 参数)

Scenario: 无 UI 工具可用

  • WHEN 所有通知和对话框工具均不可用、图形会话不存在或所有 UI 提示均返回失败
  • THEN 降级到标准错误输出
  • AND 输出格式为 错误: <title>: <message>

Scenario: 工具检测缓存

  • WHEN desktop 在 Linux 上启动
  • THEN 系统检测一次可用通知和对话框工具
  • AND 检测结果缓存在包级变量
  • AND 后续提示调用直接使用缓存结果,不重复检测

Requirement: 桌面应用启动

系统 SHALL 支持作为桌面应用启动,将后端服务与前端静态资源打包为单一可执行文件,并使用启动配置中的 server.port 作为本次运行端口。

Scenario: 双击启动

  • WHEN 用户双击桌面应用可执行文件
  • THEN 系统从 ~/.nex/config.yaml 和默认值加载启动配置快照
  • AND 系统使用 gofrs/flock 尝试获取排他文件锁
  • AND 锁文件路径为系统临时目录下的 nex-gateway.lock
  • AND 系统使用启动配置中的 server.port 启动后端服务
  • AND 未配置 server.port 时默认端口为 9826
  • AND 系统托盘图标出现
  • AND 浏览器自动打开 http://localhost:<server.port> 显示管理界面

Scenario: 单实例检查

  • WHEN 用户尝试启动第二个实例
  • THEN 系统检测到已有实例持有文件锁
  • AND 显示错误提示"已有 Nex 实例运行"
  • AND 新实例退出

Scenario: 退出释放锁

  • WHEN 用户点击托盘菜单"退出"
  • THEN 系统释放文件锁
  • AND 应用进程退出

Requirement: 系统托盘

系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择,端口显示 SHALL 使用启动配置中的 server.port。托盘初始化失败 SHALL 被视为 desktop fatal 启动失败。

Scenario: 托盘图标显示

  • WHEN 桌面应用启动成功
  • THEN 系统根据平台加载正确的图标格式
  • AND 在 Windows 上加载 ICO 格式图标(assets/icon.ico
  • AND 在 macOS 和 Linux 上加载 PNG 格式图标(assets/icon.png
  • AND 托盘图标 tooltip 显示 Nex

Scenario: 托盘菜单显示

  • WHEN 用户点击托盘图标(左键或右键)
  • THEN 显示托盘菜单
  • AND 菜单包含"打开管理界面"选项
  • AND 菜单包含"状态: 运行中"选项(禁用状态)
  • AND 菜单包含"端口: <server.port>"选项(禁用状态)
  • AND 菜单包含"退出"选项

Scenario: 打开管理界面

  • WHEN 用户点击托盘菜单"打开管理界面"
  • THEN 系统在浏览器中打开 http://localhost:<server.port>

Scenario: 托盘 ready 后自动打开浏览器

  • WHEN desktop 启动并完成托盘图标、菜单和点击事件初始化
  • THEN 系统 SHALL 将托盘标记为 ready
  • THEN 系统 SHALL 在托盘 ready 后自动打开 http://localhost:<server.port>
  • THEN 系统 SHALL NOT 在托盘初始化失败时自动打开浏览器

Scenario: 浏览器打开失败

  • WHEN 系统无法打开浏览器(浏览器未安装等)
  • THEN 托盘菜单仍可正常使用
  • AND 用户可手动访问 http://localhost:<server.port>
  • AND 系统 SHALL 尝试显示非 fatal 提示,告知用户手动访问地址

Scenario: 退出应用

  • WHEN 用户点击托盘菜单"退出"
  • THEN 系统优雅关闭后端服务
  • AND 托盘图标消失
  • AND 应用进程退出

Scenario: 托盘初始化失败

  • WHEN desktop 启动时托盘未在限定时间内 ready、托盘图标资源无法加载或托盘菜单无法完成初始化
  • THEN 系统 SHALL 生成 tray 阶段启动错误
  • THEN 系统 SHALL 通过启动失败提示降级链提示用户
  • THEN 应用 SHALL 退出

Requirement: 静态文件服务

系统 SHALL 通过 Gin 同时服务 API、协议代理和前端静态资源。

Scenario: API 请求路由

  • WHEN 请求路径以 /api//health 开头
  • THEN 请求由现有业务 handler 处理或返回 API 风格 404

Scenario: 版本接口路由

  • WHEN desktop 模式收到 GET /api/version 请求
  • THEN 请求 SHALL 由版本信息 handler 处理
  • THEN 响应 SHALL 为 API JSON 响应
  • THEN 请求 SHALL NOT 返回前端 index.html

Scenario: 协议代理请求路由

  • WHEN 请求路径以 /openai//anthropic/ 开头
  • THEN 请求 SHALL 被视为协议代理请求或返回 API 风格 404
  • THEN 请求 SHALL NOT 返回前端 index.html

Scenario: OpenAI 代理路由

  • WHEN desktop 模式收到 /openai/v1/chat/completions 请求
  • THEN 请求 SHALL 进入 ProxyHandler
  • THEN ProxyHandler SHALL 获取 clientProtocol 为 openai

Scenario: Anthropic 代理路由

  • WHEN desktop 模式收到 /anthropic/v1/messages 请求
  • THEN 请求 SHALL 进入 ProxyHandler
  • THEN ProxyHandler SHALL 获取 clientProtocol 为 anthropic

Scenario: 静态资源路由

  • WHEN 请求路径为 /assets/*
  • THEN 返回嵌入的前端静态资源文件
  • THEN 请求 SHALL NOT 被协议代理路由处理

Scenario: PNG Favicon 路由

  • WHEN 请求路径为 /icon.png
  • THEN 返回来源于统一应用图标的 PNG favicon 资源
  • THEN 请求 SHALL NOT 被协议代理路由处理

Scenario: SPA 路由回退

  • WHEN 请求路径不匹配任何 API、协议代理或静态资源路由
  • THEN 返回 index.html(支持前端 SPA 路由)

Requirement: 端口冲突检测

系统 SHALL 在启动时获取启动配置中的 server.port 对应监听端口,并使用同一个 listener 启动后端服务,避免端口预检测与实际监听之间的竞态。

Scenario: 配置端口可用

  • WHEN 启动配置中的 server.port 未被占用
  • THEN 系统 SHALL 成功创建该端口的 listener
  • THEN 后端服务 SHALL 使用该 listener 正常启动

Scenario: 配置端口被占用

  • WHEN 启动配置中的 server.port 已被其他程序占用
  • THEN 系统 SHALL 生成 port 阶段启动错误
  • THEN 错误提示 SHALL 包含"端口 <server.port> 已被占用"
  • AND 应用退出

Scenario: 单实例优先于端口监听

  • WHEN 用户尝试启动第二个 desktop 实例
  • THEN 系统 SHALL 优先检测到已有实例持有文件锁
  • THEN 系统 SHALL 显示"已有 Nex 实例运行"相关错误提示
  • THEN 系统 SHALL NOT 将该场景误报为端口占用

Requirement: 桌面配置源隔离和启动快照

desktop SHALL 仅在启动时从默认配置文件 ~/.nex/config.yaml 和默认值加载运行时配置,并将加载结果作为本次运行的 immutable runtime snapshot。

Scenario: Desktop 仅使用默认配置文件

  • WHEN desktop 启动
  • THEN SHALL 从 ~/.nex/config.yaml 加载配置
  • THEN SHALL 在配置文件不存在时使用默认值
  • THEN SHALL 使用默认值补齐配置文件未设置的配置项

Scenario: Desktop 不支持 CLI 配置源

  • WHEN desktop 启动时传入 --server-port 9000--database-path /tmp/test.db--config /tmp/custom.yaml
  • THEN SHALL 忽略这些参数
  • THEN SHALL NOT 将这些参数应用到运行时配置
  • THEN SHALL NOT 使用 --config 指定的配置文件路径

Scenario: Desktop 不支持环境变量配置源

  • WHEN desktop 启动环境中存在 NEX_SERVER_PORTNEX_DATABASE_PATHNEX_LOG_LEVEL 或其他 NEX_* 环境变量
  • THEN SHALL NOT 将这些环境变量应用到运行时配置
  • THEN SHALL 使用默认配置文件和默认值确定运行时配置

Scenario: Desktop 忽略未知启动参数

  • WHEN desktop 启动时传入未知命令行参数
  • THEN SHALL NOT 因未知参数导致配置加载失败
  • THEN SHALL 继续使用默认配置文件和默认值加载配置

Scenario: 配置文件修改仅下次启动生效

  • WHEN desktop 已启动并正在处理请求
  • AND 用户修改 ~/.nex/config.yaml 中的 server.portdatabase.*log.* 或 timeout 配置
  • THEN 当前运行中的 desktop SHALL NOT 重新加载配置文件
  • THEN 当前运行中的 HTTP server、数据库连接、日志器和请求处理 SHALL NOT 因配置文件修改而重建或中断
  • THEN 修改后的配置 SHALL 在下一次 desktop 启动时生效

Scenario: 配置文件无效

  • WHEN desktop 启动时 ~/.nex/config.yaml 存在但内容无法解析或验证失败
  • THEN SHALL 显示包含配置文件路径和失败原因的错误提示
  • THEN SHALL 退出应用
  • THEN SHALL NOT 静默回退默认配置继续启动

Requirement: Desktop 前端同源 API 访问

desktop 内嵌前端 SHALL 使用同源相对路径访问后端 API不主动发现、缓存或覆盖 desktop 端口。

Scenario: 同源 API 请求

  • WHEN desktop 浏览器页面打开在 http://localhost:<server.port>
  • THEN 前端 SHALL 使用 /api/*/openai/*/anthropic/* 等相对路径访问同一 origin
  • THEN 前端 SHALL NOT 硬编码 desktop 端口

Scenario: 重启后新端口访问

  • WHEN 用户修改配置文件中的 server.port 并重启 desktop
  • THEN desktop SHALL 按新端口启动并打开 http://localhost:<new-port>
  • THEN 前端 SHALL 继续使用当前 origin 的相对路径访问 API
  • THEN 前端 SHALL NOT 扫描端口、读取本地缓存端口或请求端口发现接口

Requirement: 跨平台构建

系统 SHALL 支持跨平台构建和打包。构建 target SHALL 按平台和架构分离或参数化,中间构建产物文件名 SHALL 保持可区分目标平台和架构,最终桌面发布资产文件名 SHALL 包含统一版本号、组件、平台、架构和格式信息。

Scenario: macOS 构建

  • WHEN 执行 macOS desktop 构建命令且当前版本为 1.2.3
  • THEN 系统 SHALL 生成 macOS arm64 和 amd64 桌面可执行文件
  • AND 系统 SHALL 使用 lipo 生成 macOS universal 桌面可执行文件
  • AND 系统 SHALL 生成可打包为 .app bundle 的 macOS desktop 产物
  • AND 最终 macOS desktop 发布资产文件名 SHALL 包含 1.2.3macosuniversal

Scenario: Windows 构建

  • WHEN 执行 Windows desktop 构建命令且当前版本为 1.2.3
  • THEN 系统 SHALL 生成 Windows amd64 desktop 可执行文件
  • AND Windows desktop 构建 SHALL 使用 -H=windowsgui linker flag 隐藏控制台窗口
  • AND 最终 Windows desktop 发布资产文件名 SHALL 包含 1.2.3windowsamd64

Scenario: Linux 构建

  • WHEN 执行 Linux desktop 构建命令且当前版本为 1.2.3
  • THEN 系统 SHALL 生成 Linux amd64 和 arm64 desktop 可执行文件
  • AND Linux desktop 构建 SHALL 使用 CGO 和 GTK/AppIndicator 构建依赖
  • AND 最终 Linux desktop 发布资产文件名 SHALL 包含 1.2.3linux 和对应架构标识

Requirement: Linux 桌面发布安装包

系统 SHALL 为 Linux desktop amd64 和 arm64 生成 tar.gz、AppImage、deb 和 rpm 发布安装包,并 SHALL 在安装包中包含标准桌面集成元数据。

Scenario: Linux desktop tar.gz 裸包

  • WHEN 构建 Linux desktop 发布资产
  • THEN 系统 SHALL 为 amd64 生成 nex-desktop_<version>_linux_amd64.tar.gz
  • AND 系统 SHALL 为 arm64 生成 nex-desktop_<version>_linux_arm64.tar.gz

Scenario: Linux desktop AppImage 包

  • WHEN 构建 Linux desktop AppImage 发布资产
  • THEN 系统 SHALL 为 amd64 生成 nex-desktop_<version>_linux_amd64.AppImage
  • AND 系统 SHALL 为 arm64 生成 nex-desktop_<version>_linux_arm64.AppImage
  • AND AppImage SHALL 包含 desktop entry、应用图标和 desktop 可执行文件
  • AND AppImage SHALL 依赖目标系统提供 GTK3、Ayatana AppIndicator 和运行 AppImage 所需的 runtime/FUSE 能力

Scenario: Linux desktop deb 包

  • WHEN 构建 Linux desktop deb 发布资产
  • THEN 系统 SHALL 为 amd64 生成 nex-desktop_<version>_linux_amd64.deb
  • AND 系统 SHALL 为 arm64 生成 nex-desktop_<version>_linux_arm64.deb
  • AND deb 包 SHALL 将可执行文件安装到 /usr/bin/nex
  • AND deb 包 SHALL 将 desktop entry 安装到 /usr/share/applications/nex.desktop
  • AND deb 包 SHALL 将 hicolor 图标安装到 /usr/share/icons/hicolor/.../apps/nex.png
  • AND deb 包 SHALL 声明 libgtk-3-0libayatana-appindicator3-1xdg-utils 运行时依赖
  • AND deb 包 metadata 的架构字段 SHALL 使用 amd64arm64

Scenario: Linux desktop rpm 包

  • WHEN 构建 Linux desktop rpm 发布资产
  • THEN 系统 SHALL 为 amd64 生成 nex-desktop_<version>_linux_amd64.rpm
  • AND 系统 SHALL 为 arm64 生成 nex-desktop_<version>_linux_arm64.rpm
  • AND rpm 包 SHALL 将可执行文件安装到 /usr/bin/nex
  • AND rpm 包 SHALL 将 desktop entry 安装到 /usr/share/applications/nex.desktop
  • AND rpm 包 SHALL 将 hicolor 图标安装到 /usr/share/icons/hicolor/.../apps/nex.png
  • AND rpm 包 SHALL 声明 gtk3libayatana-appindicator-gtk3xdg-utils 运行时依赖
  • AND rpm 包 metadata 的架构字段 SHALL 使用 x86_64aarch64

Requirement: macOS DMG 打包

系统 SHALL 为 macOS desktop universal .app 生成 unsigned DMG 安装包,并 SHALL 保留 universal zip 发布资产。

Scenario: macOS universal zip 包

  • WHEN 构建 macOS desktop 发布资产且当前版本为 1.2.3
  • THEN 系统 SHALL 生成 nex-desktop_1.2.3_macos_universal.zip
  • AND zip 包 SHALL 包含 Nex.app

Scenario: macOS universal DMG 包

  • WHEN 构建 macOS desktop DMG 发布资产且当前版本为 1.2.3
  • THEN 系统 SHALL 生成 nex-desktop_1.2.3_macos_universal.dmg
  • AND DMG SHALL 包含 Nex.app
  • AND DMG SHALL 包含指向 /Applications 的快捷方式
  • AND DMG SHALL NOT 要求 macOS 签名或 notarization 才能完成构建

Scenario: macOS universal 架构校验

  • WHEN macOS desktop universal 可执行文件生成完成
  • THEN 系统 SHALL 验证该可执行文件包含 amd64 和 arm64 架构

Requirement: macOS .app 打包

系统 SHALL 支持打包为 macOS .app bundle并使 bundle 元数据中的版本字段来源于统一版本号而非硬编码值。

Scenario: .app 结构

  • WHEN 执行 macOS 桌面打包脚本
  • THEN 生成 Nex.app 目录结构
  • AND 包含 Contents/Info.plist 元数据
  • AND 包含 Contents/MacOS/nex 可执行文件
  • AND 包含 Contents/Resources/icon.icns 图标
  • AND Info.plistLSUIElementtrue(不显示 Dock 图标)

Scenario: bundle 版本元数据同步

  • WHEN 当前统一版本号为 1.2.3
  • THEN Info.plistCFBundleShortVersionString SHALL 为 1.2.3
  • AND Info.plistCFBundleVersion SHALL 为 1.2.3
  • AND 打包流程 SHALL NOT 使用硬编码版本值

Requirement: Windows 原生对话框

系统 SHALL 在 Windows 上优先使用系统通知提示启动错误,并在通知不可用或失败时使用 user32.dllMessageBoxW API 显示错误对话框,替代 msg * 命令。

Scenario: Windows 通知优先

  • WHEN 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等)
  • THEN 系统 SHALL 优先尝试发送 Windows 系统通知
  • THEN 通知 SHALL 包含应用名称和错误描述文本

Scenario: 错误提示对话框

  • WHEN Windows 系统通知不可用或返回失败
  • THEN 使用 MessageBoxW 显示模态对话框
  • AND 对话框标题栏显示应用名称
  • AND 对话框包含错误描述文本
  • AND 对话框显示错误图标MB_ICONERROR
  • AND 对话框 SHALL 尽量置于前台或顶层显示

Scenario: Windows UI 提示均失败

  • WHEN Windows 系统通知和 MessageBoxW 均失败
  • THEN 系统 SHALL 记录提示失败原因
  • THEN 系统 SHALL 降级输出到可用启动日志

Scenario: 非 Windows 平台不受影响

  • WHEN 应用运行在 macOS 或 Linux 上
  • THEN 错误提示 SHALL 使用对应平台的通知和弹窗降级策略

Requirement: macOS AppleScript 字符转义

系统 SHALL 对 AppleScript 通知、模态告警和对话框中的特殊字符进行转义,确保脚本正确执行。

Scenario: 转义反斜杠

  • WHEN AppleScript 提示文本包含反斜杠字符 \
  • THEN 转义为 \\

Scenario: 转义双引号

  • WHEN AppleScript 提示文本包含双引号字符 "
  • THEN 转义为 \"

Scenario: 多行文本处理

  • WHEN AppleScript 提示文本包含换行符 \n
  • THEN AppleScript 通知或模态告警 SHALL 正确显示多行文本或保留可读换行语义

Requirement: 桌面应用打包迁移资源

桌面应用 SHALL 在打包安装后仍能访问数据库迁移资源,并 SHALL 在首次启动时完成数据库初始化和迁移。

Scenario: 打包安装后首次启动执行迁移

  • WHEN 用户从 macOS DMG 安装并首次启动 Nex.app
  • THEN 系统 SHALL 初始化默认配置和数据库
  • THEN 系统 SHALL 使用打包在应用内的迁移资源执行 SQLite 迁移
  • THEN 系统 SHALL NOT 尝试访问构建机源码路径或仓库源码路径
  • THEN 系统 SHALL 成功启动后端服务、托盘和管理界面

Scenario: .app 包含运行时必需迁移资源

  • WHEN 执行 macOS 桌面打包脚本
  • THEN Nex.app SHALL 包含启动后端服务所需的数据库迁移资源
  • THEN 迁移资源 SHALL 随应用移动到任意安装位置后仍可用
  • THEN .app SHALL NOT 依赖构建目录、源码目录或 GitHub Actions runner 路径

Scenario: DMG 安装后运行时资源完整

  • WHEN 用户从 DMG 将 Nex.app 拖入 /Applications 并启动
  • THEN 应用 SHALL 能访问数据库迁移资源
  • THEN 应用 SHALL NOT 因 migrations/sqlitemigrations/mysql 文件系统目录不存在而启动失败