项目实践

SpotiBind

SpotiBind 是一个 macOS 菜单栏 companion,将标准 play/pause、next、previous media keys 路由到选定的播放器。它的 owner-facing surface 是 MenuBarExtra(.

  • macOS app
  • macOS Launch Services window handoff
  • Native macOS visual evidence
  • Quality gates
  • PR label release

目录

技术栈

运行与交付

CI 工作流
  • label-gate.yml
  • main.yml
  • pr.yml
  • release-completion.yml
  • release-intent-defaults.yml
  • release-preparation.yml
  • release.yml
能力标记
  • github_actions
JSON

Purpose

SpotiBind 是一个 macOS 菜单栏 companion,将标准 play/pause、next、previous media keys 路由到选定的播放器。它的 owner-facing surface 是 MenuBarExtra(.window) popover 和保留的 settings window;播放器自身的播放状态、私有 MediaRemote API 和无关桌面窗口不在项目边界内(docs/specs/spotibind/SPEC.md)。

Taxonomy

  • Tags: macOS app, macOS Launch Services window handoff, Native macOS visual evidence, Quality gates, PR label release
  • Why / evidence: 这些 tags 由 Package.swift、Sources/SpotiBind/SpotiBindApp.swift、Sources/SpotiBind/UIDemoScenario.swift、scripts/macos/ 和 .github/workflows/ 支撑。Native macOS visual evidence 只描述严格的目标窗口证据,不替代 macOS bundle 或 release 生命周期。

Repository map

  • Sources/SpotiBind/SpotiBindApp.swift 定义菜单栏应用与 .window 风格的 popover;Sources/SpotiBind/ApplicationDelegate.swift 负责 Demo 启动、应用生命周期和 Launch Services reopen handoff。
  • Sources/SpotiBind/UIDemoScenario.swift 提供固定的 UI Demo 状态;AppState.swift 将偏好限制在 Demo 可控的 UserDefaults 范围。
  • scripts/macos/build.sh、capture-theme-ui.sh、capture-settings-window.sh、popover-window-probe.swift 与 window-probe.swift 组成构建、身份探针和窗口捕获入口。
  • docs/specs/spotibind/SPEC.md 记录两个目标 surface、四个 Light/Dark 健康资产和错误场景的 smoke-only 边界。

Runtime and delivery

本地 UI evidence 流程先用 scripts/macos/build.sh 从当前源码构建真实 .app,再由项目脚本启动隔离 Demo。settings window 路径从同一 PID 验证 bundle identity、AX role/title 语义和唯一 WindowServer ID,随后才允许执行 screencapture -x -l;popover probe 目前只验证 PID、bundle、WindowServer ID 和几何约束,缺少 AX role/title,因此只能作为迁移输入,不能进入严格正向矩阵。窗口缺失、歧义、不可成像或 PNG 无效都会失败。真实桌面捕获只在受控本机会话中逐次授权,CI 不承担该权限边界。

Architecture notes

应用使用 SwiftUI 菜单栏壳层,媒体键转发逻辑与播放器 adapter 保持可测试边界。UIDemoScenario.swift 让 popover/settings 在不连接真实播放器、网络或硬件的情况下呈现稳定状态;UISnapshot.swift 只用于真实 status-bar host 的窗口验证,不使用 NSPanel、cacheDisplay 或 PDF/bitmap 合成窗口。settings window probe 把应用 PID、bundle identity、Accessibility 语义和 WindowServer ID 绑定为一个证据链;popover probe 尚未绑定 AX role/title,必须在补齐前保持迁移状态。

Workflow and quality gates

scripts/macos/test-ui-capture-contract.sh 静态检查捕获脚本的 screencapture -x -l、status-bar host 和 probe 入口,并拒绝旧的合成窗口路径。.github/workflows/pr.yml 与 scripts/macos/test.sh 运行静态合同、Swift 单元测试和构建检查,不在 CI 申请桌面权限。发布侧由 .github/workflows/release-preparation.yml、release-completion.yml 和 release.yml 维护版本、签名与 release 证据;这些属于 macOS app / delivery 主题,不改变截图合同。

Reusable decisions

  • 将 target-window 证据拆成 build、fixture、probe、capture 四个可审查步骤,任何身份不确定性都 fail closed。
  • 通过 LSMultipleInstancesProhibited 和 applicationShouldHandleReopen 把重复 Launch Services 请求交给已有进程,再复用 retained settings presenter/window;不创建第二进程、第二窗口或 custom IPC。该模式的完整上下文见 docs/solutions/macos/duplicate-launch-settings-window.md。
  • 用固定 UIDemoScenario 和隔离的用户状态替代真实播放器、网络、Keychain 或硬件,使截图矩阵可复现。
  • 将 Light/Dark settings window 作为当前严格健康矩阵;popover 在补齐 AX role/title probe 前只保留为迁移/smoke 输入。accessibility-required、no-supported-player、path-unavailable 和 dispatch-failure 保留为 smoke-only。
  • 项目负责实际 PNG 与 PR 索引,Playbook 只复用合同和 checklist,不把应用截图复制到 Topic package。

Risks and open questions

  • macOS WindowServer、Accessibility 语义和 SwiftUI host 的行为受系统版本影响,项目需要在支持的系统范围内重新验证 probe。
  • 真实应用捕获仍需要目标项目逐次本机授权;本 snapshot 不代表已在本 Playbook PR 中运行应用或生成新的 PNG。
  • 该仓库样本为 private;对外导出的 Playbook 内容只保留相对证据路径和通用合同,不公开来源、checkout 或运行日志。

Evidence index

  • docs/specs/spotibind/SPEC.md
  • Sources/SpotiBind/SpotiBindApp.swift
  • Sources/SpotiBind/ApplicationDelegate.swift
  • Sources/SpotiBind/AppState.swift
  • Sources/SpotiBind/UIDemoScenario.swift
  • Sources/SpotiBind/UISnapshot.swift
  • scripts/macos/build.sh
  • scripts/macos/capture-theme-ui.sh
  • scripts/macos/capture-settings-window.sh
  • scripts/macos/popover-window-probe.swift
  • scripts/macos/window-probe.swift
  • scripts/macos/test-ui-capture-contract.sh
  • .github/workflows/pr.yml
  • docs/solutions/macos/duplicate-launch-settings-window.md
  • Sources/SpotiBind/ApplicationDelegate.swift
  • Sources/SpotiBind/SettingsWindowController.swift
  • Sources/SpotiBind/ApplicationPresentationController.swift
  • packaging/macos/Info.plist
  • Tests/SpotiBindAppTests/ApplicationDelegateTests.swift
  • Tests/SpotiBindAppTests/ApplicationPresentationTests.swift