Skip to content

feat(macos): automated DMG release workflow (#165 phase 2) - #167

Merged
jonocodes merged 3 commits into
mainfrom
feat/ticket-165-phases-1-2
Sep 23, 2026
Merged

jonocodes merged 3 commits into
mainfrom
feat/ticket-165-phases-1-2

Conversation

@jonocodes

Copy link
Copy Markdown
Owner

Summary

Implements Phase 2 of #165: build and release the macOS app bundle (deckd.app) as a DMG artifact attached to GitHub releases.

Automated release workflow (.github/workflows/release-macos.yml):

  • Builds on tag push (v*) or manual trigger
  • Runs on macos-14 (Apple Silicon only) with all dependencies
  • Verifies tag matches pyproject.toml version
  • Wraps the app in a DMG using the same recipe contributors run locally
  • Attaches the DMG to the GitHub release

Bundle configuration (daemon/deckd/macos_app.py):

  • Extracted bundle_info_plist() function: TCC-relevant keys are now unit-testable on Linux hosts
  • Adds NSAppleEventsUsageDescription so macOS shows the Automation prompt when the bundle uses System Events (fixes the silent errAEEventNotPermitted from macOS packaging & deployment: self-contained .app + DMG release #165)
  • Updated deckd.spec to import and use this function

Documentation and verification:

  • Documented the macOS app bundle path in docs/GUIDE.md including TCC behavior, the ad-hoc signing caveat, and the release process
  • Recorded hardware verification in docs/PLATFORM-PARITY.md: builds, launches as menu-bar app, serves surface on loopback
  • Added tests for bundle info plist generation and TCC string validation

Scope: arm64 (Apple Silicon) only; universal2/Intel is a documented follow-up.

🤖 Generated with Claude Code

Phases 1-2 of the self-contained deckd.app.

Phase 1 (on a Mac): the bundle builds, launches as a menu-bar app, seeds
layouts, and serves the surface on loopback — verified on macOS 26.6.2
(Apple Silicon). Adds NSAppleEventsUsageDescription to the bundle's
Info.plist, without which macOS refuses the System Events Apple Events
the daemon sends via osascript (errAEEventNotPermitted, -1743). The plist
moves into deckd.macos_app.bundle_info_plist so its TCC-relevant keys are
unit-tested on Linux, and the spec reads BUNDLE_ID from there too.

Phase 2: .github/workflows/release-macos.yml — on a v* tag (or a manual
dispatch) a macos-14 runner builds the client, freezes the app, wraps it
in a DMG via the same `just build-macos-dmg` recipe a contributor runs,
verifies it, and attaches it to the GitHub release. Adds `just version`
as the single source for the DMG name and the tag guard. arm64-only;
Intel/universal2 is a follow-up.

Docs: GUIDE gets the releasing flow and the ad-hoc re-grant caveat;
PLATFORM-PARITY points at it.

Refs #165
There's no versioning scheme yet, and a strict tag == pyproject version
check would force one. The tag is now just the release handle (any v*
string); the DMG filename still comes from pyproject's `version`, and
the two don't have to agree.

Refs #165
Rough CalVer: the tag (minus a leading `v`) becomes the bundle version,
so the DMG name, its volume, and CFBundleShortVersionString all match the
tag. `deckd.macos_app.bundle_version` resolves DECKD_VERSION → pyproject,
the spec reads it, and `just build-macos-dmg` names the artifact from it;
a local build with no tag still falls back to pyproject's version.

Verified on this Mac: DECKD_VERSION=2026.09.23 -> deckd-2026.09.23.dmg
with the matching Info.plist version; the no-env build stays 0.0.1.

Refs #165
@jonocodes
jonocodes merged commit ba74748 into main Sep 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant