diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4177d753..5e01dc65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,6 +44,7 @@ jobs: run: | brew update brew install zig + npm install --global create-dmg - name: Build GhosttyKit.xcframework run: | @@ -137,6 +138,7 @@ jobs: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: | if [ -z "$APPLE_ID" ] || [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] || [ -z "$APPLE_TEAM_ID" ]; then echo "Missing notarization secrets (APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID)" >&2 @@ -158,13 +160,12 @@ jobs: xcrun stapler validate "$APP_PATH" spctl -a -vv --type execute "$APP_PATH" rm -f "$ZIP_SUBMIT" - STAGING_DIR="$(mktemp -d)" - cp -R "$APP_PATH" "$STAGING_DIR/cmuxterm.app" - ln -s /Applications "$STAGING_DIR/Applications" - # hdiutil defaults to APFS for -srcfolder images (volume name becomes "Untitled" on mount). - # Force HFS+ so the mounted volume name is stable and Finder shows the contents reliably. - hdiutil create -fs HFS+ -volname "cmuxterm" -srcfolder "$STAGING_DIR" -ov -format UDZO "$DMG_RELEASE" - rm -rf "$STAGING_DIR" + # create-dmg generates a styled drag-to-install DMG (matches Ghostty's approach) + create-dmg \ + --identity="$APPLE_SIGNING_IDENTITY" \ + "$APP_PATH" \ + ./ + mv ./cmuxterm*.dmg "$DMG_RELEASE" DMG_SUBMIT_JSON="$(xcrun notarytool submit "$DMG_RELEASE" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)" DMG_SUBMIT_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$DMG_SUBMIT_JSON")" DMG_STATUS="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' <<<"$DMG_SUBMIT_JSON")" diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e483ea..53da6295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,20 @@ All notable changes to cmuxterm are documented here. +## [1.19.1] - 2026-02-08 + +### Fixed +- Blank window on macOS 26 when background glass effect is enabled +- "Copy Update Logs" showing empty logs in production builds + +### Changed +- Clearer error when app needs to be moved to Applications before updating +- DMG installer now shows drag-to-install window with Applications shortcut + ## [1.19.0] - 2026-02-08 ### Fixed - Blank window on macOS 26 caused by NSGlassEffectView wrapper -- "Copy Update Logs" showing empty logs in production builds ## [1.18.0] - 2026-02-06 diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index 4ab69381..0bf0db05 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -538,7 +538,7 @@ CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 26; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -554,7 +554,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.19.0; + MARKETING_VERSION = 1.19.1; OTHER_LDFLAGS = ( "-lc++", "-framework", @@ -583,7 +583,7 @@ CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 26; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -599,7 +599,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.19.0; + MARKETING_VERSION = 1.19.1; OTHER_LDFLAGS = ( "-lc++", "-framework", @@ -652,10 +652,10 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 26; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.19.0; + MARKETING_VERSION = 1.19.1; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.appuitests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -669,10 +669,10 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 26; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.19.0; + MARKETING_VERSION = 1.19.1; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.cmuxterm.appuitests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 1764e767..56a2c62b 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -23,9 +23,8 @@ enum WindowGlassEffect { let bounds = contentView.bounds - // macOS 26+: Prefer NSGlassEffectView. Avoid re-parenting the SwiftUI NSHostingView via - // constraints (it can result in a blank content area). Keep the original content view - // on autoresizing masks and just insert it into the glass view. + // macOS 26+: Insert NSGlassEffectView as a background subview (never replace + // window.contentView — reparenting the SwiftUI hosting view causes blank content). if let glassClass = NSClassFromString("NSGlassEffectView") as? NSVisualEffectView.Type { let glassView = glassClass.init(frame: bounds) glassView.wantsLayer = true @@ -39,13 +38,7 @@ enum WindowGlassEffect { } } - window.contentView = glassView - - contentView.wantsLayer = true - contentView.layer?.backgroundColor = NSColor.clear.cgColor - contentView.frame = glassView.bounds - contentView.autoresizingMask = [.width, .height] - glassView.addSubview(contentView) + contentView.addSubview(glassView, positioned: .below, relativeTo: contentView.subviews.first) objc_setAssociatedObject(window, &glassViewKey, glassView, .OBJC_ASSOCIATION_RETAIN) return diff --git a/Sources/Update/UpdatePopoverView.swift b/Sources/Update/UpdatePopoverView.swift index afdad3de..d4d3ce4d 100644 --- a/Sources/Update/UpdatePopoverView.swift +++ b/Sources/Update/UpdatePopoverView.swift @@ -337,6 +337,11 @@ fileprivate struct UpdateErrorView: View { let error: UpdateState.Error let dismiss: DismissAction + private var isLocationError: Bool { + let nsError = error.error as NSError + return nsError.domain == SUSparkleErrorDomain && (nsError.code == 1003 || nsError.code == 1005) + } + var body: some View { let title = UpdateViewModel.userFacingErrorTitle(for: error.error) let message = UpdateViewModel.userFacingErrorMessage(for: error.error) @@ -349,8 +354,8 @@ fileprivate struct UpdateErrorView: View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { HStack(spacing: 8) { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.orange) + Image(systemName: isLocationError ? "arrow.right.doc.on.clipboard" : "exclamationmark.triangle.fill") + .foregroundColor(isLocationError ? .accentColor : .orange) .font(.system(size: 13)) Text(title) .font(.system(size: 13, weight: .semibold)) @@ -362,39 +367,57 @@ fileprivate struct UpdateErrorView: View { .fixedSize(horizontal: false, vertical: true) } - VStack(alignment: .leading, spacing: 6) { - Text("Details") - .font(.system(size: 11, weight: .semibold)) - Text(details) - .font(.system(size: 10, design: .monospaced)) - .foregroundColor(.secondary) - .fixedSize(horizontal: false, vertical: true) - .textSelection(.enabled) - } + if isLocationError { + HStack(spacing: 8) { + Button("Open Applications Folder") { + NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: "/Applications") + } + .controlSize(.small) - HStack(spacing: 8) { - Button("Copy Details") { - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(details, forType: .string) + Spacer() + + Button("OK") { + error.dismiss() + dismiss() + } + .keyboardShortcut(.defaultAction) + .controlSize(.small) } - .controlSize(.small) - - Button("OK") { - error.dismiss() - dismiss() + } else { + VStack(alignment: .leading, spacing: 6) { + Text("Details") + .font(.system(size: 11, weight: .semibold)) + Text(details) + .font(.system(size: 10, design: .monospaced)) + .foregroundColor(.secondary) + .fixedSize(horizontal: false, vertical: true) + .textSelection(.enabled) } - .keyboardShortcut(.cancelAction) - .controlSize(.small) - Spacer() + HStack(spacing: 8) { + Button("Copy Details") { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(details, forType: .string) + } + .controlSize(.small) - Button("Retry") { - error.retry() - dismiss() + Button("OK") { + error.dismiss() + dismiss() + } + .keyboardShortcut(.cancelAction) + .controlSize(.small) + + Spacer() + + Button("Retry") { + error.retry() + dismiss() + } + .keyboardShortcut(.defaultAction) + .controlSize(.small) } - .keyboardShortcut(.defaultAction) - .controlSize(.small) } } .padding(16) diff --git a/Sources/Update/UpdateViewModel.swift b/Sources/Update/UpdateViewModel.swift index a31e0321..68a867ed 100644 --- a/Sources/Update/UpdateViewModel.swift +++ b/Sources/Update/UpdateViewModel.swift @@ -205,7 +205,7 @@ class UpdateViewModel: ObservableObject { case 1, 2, 3001, 3002: return "Update Signature Error" case 1003, 1005: - return "App Location Issue" + return "Move to Applications" default: break } diff --git a/docs-site/content/docs/changelog.mdx b/docs-site/content/docs/changelog.mdx index ad4bffe9..49eb436c 100644 --- a/docs-site/content/docs/changelog.mdx +++ b/docs-site/content/docs/changelog.mdx @@ -5,11 +5,20 @@ description: Release notes and version history for cmuxterm All notable changes to cmuxterm are documented here. +## [1.19.1] - 2026-02-08 + +### Fixed +- Blank window on macOS 26 when background glass effect is enabled +- "Copy Update Logs" showing empty logs in production builds + +### Changed +- Clearer error when app needs to be moved to Applications before updating +- DMG installer now shows drag-to-install window with Applications shortcut + ## [1.19.0] - 2026-02-08 ### Fixed - Blank window on macOS 26 caused by NSGlassEffectView wrapper -- "Copy Update Logs" showing empty logs in production builds ## [1.18.0] - 2026-02-06