Skip to content

对齐 macOS installer:结果页、WebSetup handoff 与跨平台页面模型 #678

Description

@kxn

背景

当前 macOS packaged installer 已经有比 Windows 更完整的 GUI 框架,但它的页面设计、按钮语义和最终 handoff 方式,和刚收敛出来的 Windows installer 新模型并不一致。

当前已确认的偏移包括:

  • 成功路径下会自动打开 WebSetup 或 Admin UI,而不是在最终结果页显式 handoff
  • 结果页的主按钮语义仍然偏向“立即打开后续入口”,而不是严格区分 Continue WebSetupFinish
  • 成功页里把 日志路径 / 管理页 / WebSetup 直接塞进 detail 文本,而不是和结果页动作区形成统一模型
  • 当前用户可见文案与页面结构没有按跨平台统一结果页模型收敛

随着 #677 已经把 Windows installer 的结果页状态模型、WebSetup handoff 语义与多语言方向收口,macOS installer 也应对齐到同一套产品模型,避免两个平台在“安装完成后到底发生什么”上继续分叉。

关联 issue:

目标

让 macOS packaged installer 的页面设计与行为语义,对齐到 #677 中确定的统一 installer 结果页模型。

范围

统一结果页模型

macOS installer 需要对齐以下结果页状态:

  1. fresh install + setupRequired
  2. fresh install + no setupRequired
  3. reinstall / repair success
  4. failure

WebSetup handoff 语义对齐

  • fresh install 阶段 installer 只负责 binary 安装与最基础设置。
  • 如果安装结果要求继续 WebSetup,不应在安装过程中或安装完成瞬间自动打开浏览器。
  • 应在最终结果页提供主动作按钮 Continue WebSetup
  • 用户点击后,installer 任务视为完成,再 handoff 给浏览器中的 WebSetup。
  • reinstall / repair 默认不启动 WebSetup。

页面动作模型对齐

  • fresh install + setupRequired
    • 主按钮:Continue WebSetup
    • 辅助动作:Open Logs
    • 不显示 Open Admin UI
  • fresh install + no setupRequired
    • 主按钮:Finish
    • 辅助动作:Open Admin UIOpen Logs
  • reinstall / repair success
    • 主按钮:Finish
    • 辅助动作:Open Admin UIOpen Logs
  • failure
    • 主按钮:Finish
    • 辅助动作:Open Logs

页面设计对齐

  • 结果信息不再主要靠 detail 文本堆叠 URL/path
  • 页面中需要明确区分:
    • 状态摘要区
    • 动作区
    • 辅助信息区
  • 按钮、链接、结果摘要的组织方式要与 Windows 新模型一致,避免跨平台用户心智分裂

非目标

  • 不在本 issue 中重新设计整套 macOS installer 的 Welcome / Install Location / Installing 阶段信息架构
  • 不在本 issue 中改变 macOS payload 选择、probe、install location 编辑等平台特有实现边界
  • 不要求 macOS 在技术实现上硬复制 NSIS;目标是产品语义与页面模型对齐,而不是实现技术栈一致

完成标准

  1. macOS installer 成功页不再自动打开 WebSetup 或 Admin UI。
  2. 只有在 fresh install + setupRequired 时才显示 Continue WebSetup
  3. reinstall / repair 成功后不显示 Continue WebSetup,也不会自动启动 WebSetup。
  4. Open Admin UIContinue WebSetup 不会同时出现。
  5. failure 仍在 installer 主窗口内完成收尾,不跳出额外割裂式交互。
  6. 页面设计与动作分区能够和 #677 约定的统一模型对齐。
  7. 对应 macOS installer 的验证路径同步更新,至少能覆盖结果页与 handoff 行为。

调研结论

1. 当前成功/失败页行为与 #677 已收敛模型不一致

  • InstallerApp.beginInstall() 在拿到成功结果后,会在进入成功页前直接打开 setupURL
  • InstallerApp.primaryAction() 在成功页里仍然直接打开 setupURLadminURL,而不是落在统一的结果页动作模型里。
  • failure 页主按钮当前是 重新检查,本质仍是重新 probe,而不是用统一的结果页收尾语义完成 handoff。

2. 结果页状态模型存在结构性缺口,不只是文案漂移

  • ScreenState.success 目前只携带 PackagedInstallResultValue
  • PackagedInstallResultValue 不包含 sameVersion,因此一旦安装执行结束,结果页就丢失了“同版本重装修复”和“升级修复”的判别上下文。
  • 当前 macOS 只有 pre-install 的 InstallerLaunchPlan,没有一个由 probe + install result 共同派生出来的结果页 view model。
  • 这意味着 issue 范围里要求的四种结果页状态,当前代码并不能稳定、清晰地表达。

3. 页面结构本身还没有进入统一的“摘要 / 动作 / 辅助信息”模型

  • success 页标题固定为“安装完成”,摘要文案仍然围绕“会自动打开”。
  • successDetail(...) 直接把日志路径、管理页 URL、WebSetup URL 堆进 detail 文本。
  • 成功页和失败页都继续保留原始滚动日志区,没有明确的 Open Logs 动作模型。
  • 这不是单点 copy 问题,而是结果页信息架构仍然沿用旧语义。

4. 当前验证面不足以覆盖完成标准 7

  • installer-smoke.yml 里的 macOS 路径只负责打包 DMG、检查文件存在和 checksum,不覆盖 installer 运行态和 handoff 行为。
  • 当前 macOS InstallerApp 是通过 swiftc 直接编译源码,没有现成的 Swift test target 或结果页逻辑 harness。
  • 如果不先把结果页派生逻辑收口到可测试边界,后续即使把 UI 改对,也很难稳定验证“不自动 handoff / 结果页动作互斥 / 四种状态映射”这些行为。

计划结论

本 issue 采用“先收口结果页模型,再改 UI,再补验证”的方案,不走最小补丁。

核心实现判断如下:

  1. 不在 InstallerApp.swift 里继续散落 success/failure 特判。
  2. 新增一层 Foundation-only 的结果页派生模型,由 probe + result 决定:
    • 页面状态
    • 标题/摘要
    • 主动作类型
    • 辅助动作集合
    • 辅助信息区内容
  3. InstallerApp.swift 只负责消费该模型和渲染 AppKit 页面,不再自己决定业务语义。
  4. 自动化验证优先落在这个纯模型层,再用现有 DMG build 证明 app 仍可编译打包;本 issue 不引入重型 GUI 点击自动化。

建议范围

阶段 A:结果页状态模型收口

目标:把当前 success / failure 的分散判定,收口为一层可测试的单一结果页模型。

建议文件边界:

  • 新增 deploy/macos/InstallerApp/Sources/InstallerResultPageModel.swift
    • 保持 Foundation 依赖,不引入 Cocoa
    • 定义:
      • 结果页状态 kind
      • 主动作 kind
      • 辅助动作 kind
      • 结果页 view model builder
  • 修改 deploy/macos/InstallerApp/Sources/InstallerModels.swift
    • ScreenState 从当前 .success(...) / .failure(...) 过渡到统一结果页状态承载
    • 如需要,保留最小安装中状态模型,但结果页不再直接持有裸 PackagedInstallResultValue
  • 修改 deploy/macos/InstallerApp/Sources/InstallerApp.swift
    • 安装完成后,立即用 plan.probe + summary.result 派生结果页模型
    • 去掉安装完成瞬间的 openURL(...)
    • primaryAction() 改为只执行模型描述的主动作

收口要求:

  • fresh install + setupRequired -> Continue WebSetup
  • fresh install + no setupRequired -> Finish + Open Admin UI + Open Logs
  • repair same-version -> Finish + Open Admin UI + Open Logs
  • repair upgrade -> Finish + Open Admin UI + Open Logs
  • failure -> Finish + Open Logs

阶段 B:结果页 UI 与 handoff 语义收口

目标:让 AppKit 结果页真正对齐统一的“摘要区 / 动作区 / 辅助信息区”模型。

建议修改:

  • deploy/macos/InstallerApp/Sources/InstallerApp.swift
    • 成功/失败页都改为消费统一结果页模型
    • 安装中页继续保留滚动日志区
    • 结果页不再展示原始滚动日志区作为主体内容
    • 结果页辅助区仅保留模型要求的信息,例如:
      • 已安装版本
      • 当前启动方式
      • 日志路径
    • Open Admin UIOpen Logs 改成明确独立动作,不再埋在 detail 文本里
  • InstallerApp.primaryAction()
    • 只有 Continue WebSetup 才会打开浏览器并 handoff
    • 其他成功态主按钮统一为 Finish
  • InstallerApp.secondaryAction()
    • 保持关闭/完成语义,不承担业务 handoff

语义要求:

  • installer 只在用户点击 Continue WebSetup 后 handoff
  • repair / upgrade 路径没有 WebSetup handoff
  • Open Admin UIContinue WebSetup 互斥
  • failure 结果页在主窗口内完成收尾

阶段 C:验证路径收口

目标:给 #678 补一条稳定、可重复、无需 GUI 猜测的自动化语义验证路径。

建议文件边界:

  • 新增 scripts/check/smoke-macos-installer-result-model.sh
    • 在 mac runner 上用 swiftc 编译一个临时 harness
    • harness 只链接:
      • deploy/macos/InstallerApp/Sources/InstallerModels.swift
      • deploy/macos/InstallerApp/Sources/InstallerResultPageModel.swift
      • 临时生成的 test main
    • 断言结果页派生语义,而不是做桌面点击自动化
  • 修改 .github/workflows/installer-smoke.yml
    • 新增 macOS installer 结果页语义 smoke job 或 step
    • 与现有 DMG build job 并存:
      • 现有 job 继续验证“能打包”
      • 新 smoke 验证“语义没偏”
  • 如需要,同步 docs/general/install-deploy-design.md
    • 把 macOS 结果页验证要求写成与 Windows 同级的明确语义要求

最小自动化覆盖:

  • fresh install + setupRequired
  • fresh install + no setupRequired
  • repair same-version
  • failure

自动化断言内容:

  • 是否出现 Continue WebSetup
  • 是否错误出现 Open Admin UI
  • failure 是否只保留 Finish + Open Logs
  • 结果页是否不再依赖“自动打开 URL”完成 handoff

执行决策

  • 是否拆分:不拆。
  • 当前执行单元:#678 作为未拆分直做单元。
  • 不拆原因:状态模型、AppKit 结果页语义、以及验证路径共享同一个结果页 contract;拆开会把验收面割裂。
  • verifier:需要。该 issue 会修改最终用户直接看到的 installer 结果页与 handoff 语义,完成后默认做独立 verifier pass。
  • 当前产品决策:采用“先收口结果页模型,再改 UI,再补验证”的方案,已经过确认。

执行快照

  • 当前阶段:closeout。
  • 当前执行点:closeout 尾项,正在准备运行 close-plan,随后执行 finish --close
  • 已完成:
    • 审核当前 macOS installer 成功/失败页行为
    • 核对 #677 已确定的 Windows 结果页与 handoff 基线
    • 审核当前 macOS 构建与 CI 覆盖面
    • 确认采用“模型先行”方案
    • 新增 InstallerResultPageModel.swift,由 probe + result 派生结果页状态、主动作、辅助动作和辅助信息
    • ScreenState 已切到统一的 .result(InstallerResultPageModel) 承载
    • 安装完成瞬间自动打开 setupURL 的旧逻辑已移除
    • 新增 scripts/check/smoke-macos-installer-result-model.sh:Linux 下做结构 smoke,macOS 下做 Swift 语义 harness
    • 结果页不再把滚动日志作为主体内容,Open Admin UI / Open Logs 已切成显式辅助动作
    • InstallerBridge 已补 openFilePath(...),可打开日志路径
    • installer-smoke workflow 已接入新的 macOS result-model smoke
    • docs/general/install-deploy-design.md 已同步 macOS 结果页与验证语义
    • 三个阶段提交已推送到 masterb574e721eceda75199bb1010
    • 远端 Installer Smoke run 26715070829 已成功完成,含 build-macos-packaged-installer job 与 Smoke macOS installer result model step
    • 独立 verifier 已通过:issuecomment-4587004131
  • 下一步:
    1. 运行 close-plan
    2. 执行 finish --close
  • 当前阻塞:无
  • 最近变化的假设:无
  • 最后一致状态:
    • 工作区干净,HEADorigin/master 一致
    • 最新提交:99bb1010 (test: cover mac installer result model smoke)
    • 最新远端验证:Installer Smoke run 26715070829 全绿
  • 未完成尾项:close-planfinish --close
  • 恢复步骤:
    1. 运行 bash .codex/skills/issue-workflow-guardrail/scripts/issuectl.sh close-plan --issue 678
    2. 运行 bash .codex/skills/issue-workflow-guardrail/scripts/issuectl.sh finish --issue 678 --close

相关文档

  • docs/general/install-deploy-design.md
  • docs/implemented/non-linux-user-autostart-design.md

涉及文件

  • deploy/macos/InstallerApp/Sources/InstallerApp.swift
  • deploy/macos/InstallerApp/Sources/InstallerBridge.swift
  • deploy/macos/InstallerApp/Sources/InstallerModels.swift
  • deploy/macos/InstallerApp/Sources/InstallerResultPageModel.swift(新增)
  • internal/app/install/packaged_install.go
  • internal/app/install/packaged_install_probe.go
  • scripts/release/build-macos-installer-app.sh
  • scripts/check/smoke-macos-installer-result-model.sh(新增)
  • .github/workflows/installer-smoke.yml
  • macOS installer 相关测试与构建脚本

实现参考

  • deploy/macos/InstallerApp/Sources/InstallerApp.swift
    • primaryAction()
    • beginInstall()
    • render()
    • 当前 success/failure 渲染分支
  • deploy/macos/InstallerApp/Sources/InstallerModels.swift
    • InstallerProbeResult
    • PackagedInstallResultValue
    • ScreenState
  • deploy/macos/InstallerApp/Sources/InstallerBridge.swift
    • probe()
    • runInstall(...)
    • parseResultFile(...)
  • scripts/release/build-macos-installer-app.sh
    • 当前 swiftc 直接编译所有 Swift sources 的事实边界
  • .github/workflows/installer-smoke.yml
    • 现有 macOS packaged installer 构建链
  • docs/general/install-deploy-design.md
    • 当前跨平台 installer 结果页 / handoff 基线

检查参考

  • fresh install + setupRequired 只在结果页显示 Continue WebSetup,安装完成瞬间不自动开浏览器。
  • Continue WebSetupOpen Admin UI 不同时出现。
  • repair / upgrade success 不触发 WebSetup。
  • failure 保持在 installer 结果页完成收尾。
  • 结果页结构能够区分结果摘要区、动作区、辅助信息区。
  • 新增的 macOS installer smoke 能稳定断言结果页状态映射与 handoff 语义。
  • 现有 macOS packaged installer build 仍能在 CI 成功产出 DMG。

收尾参考

  • 如实现改动了统一 installer 产品语义或验证要求,同步 docs/general/install-deploy-design.md
  • 实现完成后补 independent verifier pass。
  • close 前重新核对 #677 / #678 的平台对齐结论是否仍一致。

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:releaseGitHub release workflow, versioning, milestones, and release automationarea:runtimeRuntime manager, process lifecycle, and persisted stateenhancementNew feature or requeststatus:implementable-nowReady to implement with current issue body and context

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions