Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 0 additions & 88 deletions .github/workflows/macos-compatibility.yml

This file was deleted.

10 changes: 2 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,8 @@ jobs:
CODE_SIGN_IDENTITY="Developer ID Application" \
OTHER_CODE_SIGN_FLAGS="--timestamp" \
MARKETING_VERSION="${RELEASE_VERSION}" \
CURRENT_PROJECT_VERSION="${BUILD_NUMBER}"
CURRENT_PROJECT_VERSION="${BUILD_NUMBER}" \
archive

- name: Verify build number in archive
env:
Expand All @@ -336,13 +337,6 @@ jobs:
fi
echo "Verified CFBundleVersion: ${actual}"

- name: Verify macOS 14 compatibility metadata
run: |
python3 scripts/verify_macos_compatibility.py \
"build/${APP_NAME}.xcarchive/Products/Applications/${APP_NAME}.app" \
--minimum 14.0 \
--require-universal

- name: Re-sign nested code for notarization
run: |
set -euo pipefail
Expand Down
22 changes: 12 additions & 10 deletions Cotabby.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

41 changes: 0 additions & 41 deletions Cotabby/App/Core/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

private let activationIndicatorController: ActivationIndicatorController
private let focusDebugOverlayController: FocusDebugOverlayController?
/// AppKit owns the menu-bar lifecycle so launch never depends on SwiftUI scene construction.
/// Lazy creation lets callbacks capture this delegate only after `super.init()` has completed.
private lazy var menuBarController = MenuBarController(
permissionManager: permissionManager,
runtimeModel: runtimeModel,
modelDownloadManager: modelDownloadManager,
focusModel: focusModel,
permissionGuidanceController: permissionGuidanceController,
suggestionSettings: suggestionSettings,
foundationModelAvailabilityService: foundationModelAvailabilityService,
powerSourceMonitor: powerSourceMonitor,
suggestionCoordinator: suggestionCoordinator,
appUpdateManager: appUpdateManager,
onOpenSettings: { [weak self] in
self?.settingsCoordinator.showSettings()
},
onReportFeedback: {
guard let baseURL = URL(string: "https://www.cotabby.app/feedback") else { return }
let url = DeviceInfo.snapshot().appending(to: baseURL)
NSWorkspace.shared.open(url)
}
)
/// Retained for the app's lifetime because the environment owns its own `cancellables` (the only
/// subscriptions wiring the focus-poll-interval setting and the global-toggle hotkey rebind to the
/// runtime). If the environment deallocated when `init` returned, those subscriptions would be
Expand Down Expand Up @@ -153,23 +131,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Smoke Argument Runs Forever

The -cotabby-compatibility-smoke-test argument is no longer checked before normal startup. A compatibility runner that launches the built app with that flag now enters full app startup and stays open instead of constructing the menu graph and exiting cleanly, so the launch check times out rather than reporting success or failure.

Fix in Codex Fix in Claude Code

/// Starts runtime and polling services once AppKit reports that app launch finished.
func applicationDidFinishLaunching(_ notification: Notification) {
if Self.isRunningCompatibilitySmokeTest {
menuBarController.prepareForCompatibilitySmokeTest()
CotabbyLogger.app.info("macOS compatibility launch smoke test passed")
// Leave the launch callback before terminating so AppKit finishes its notification
// bookkeeping; this produces a normal zero exit status instead of an artificial kill.
DispatchQueue.main.async {
NSApplication.shared.terminate(nil)
}
return
}

guard !Self.isRunningUnderXCTest else {
CotabbyLogger.app.info("Unit test host detected; skipping production service startup")
return
}

menuBarController.start()
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "?"
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "?"
CotabbyLogger.app.info("Cotabby \(version) (build \(build)) launching on macOS \(ProcessInfo.processInfo.operatingSystemVersionString)")
Expand Down Expand Up @@ -262,7 +228,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
focusDebugOverlayController?.hide()
suggestionCoordinator.stop()
inlineCommandCoordinator.stop()
menuBarController.stop()
inputMonitor.stop()
focusModel.stop()

Expand Down Expand Up @@ -320,10 +285,4 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private static var isRunningUnderXCTest: Bool {
ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

/// CI launches the built application itself on older macOS runners. This argument exercises
/// AppKit startup plus the complete hosted menu graph without touching global input or TCC.
private static var isRunningCompatibilitySmokeTest: Bool {
ProcessInfo.processInfo.arguments.contains("-cotabby-compatibility-smoke-test")
}
}
74 changes: 57 additions & 17 deletions Cotabby/App/Core/CotabbyApp.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,64 @@
import AppKit
import SwiftUI

/// File overview:
/// Declares Cotabby's AppKit entry point. `AppDelegate` owns the long-lived dependency graph and
/// `MenuBarController` owns the status item/popover, so launch never has to instantiate SwiftUI's
/// `App`/`Scene` graph. That boundary matters for macOS 14 and early macOS 15 releases, where an
/// Xcode 26-built `MenuBarExtra(.window)` can abort inside AttributeGraph before AppKit delivers a
/// lifecycle callback (issue #767).
/// Declares the SwiftUI app entry point and hosts the optional menu-bar scene that renders
/// Cotabby's compact status UI. Shared services are injected through `AppDelegate`.
///
/// The enum has no instances. Its one static delegate reference keeps the weak `NSApplication`
/// delegate alive until the run loop exits.
/// `@main` marks the single process entry point for a Swift app.
@main
enum CotabbyApp {
private static var appDelegate: AppDelegate?
struct CotabbyApp: App {
/// Bridges old-style AppKit lifecycle callbacks into a SwiftUI app.
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
/// Scene declarations cannot directly observe the delegate's nested settings object. This
/// projection watches the same durable key so SwiftUI re-evaluates status-item insertion as
/// soon as Settings changes it; the settings model remains the app-facing preference API.
@AppStorage(SuggestionSettingsStore.menuBarIconVisibleDefaultsKey)
private var isMenuBarIconVisible = true

@MainActor
static func main() {
let application = NSApplication.shared
let delegate = AppDelegate()
appDelegate = delegate
application.delegate = delegate
application.run()
/// Defines the menu bar extra that surfaces Cotabby's runtime, focus, and suggestion state.
var body: some Scene {
MenuBarExtra(isInserted: menuBarIconVisibilityBinding) {
MenuBarView(
permissionManager: appDelegate.permissionManager,
runtimeModel: appDelegate.runtimeModel,
modelDownloadManager: appDelegate.modelDownloadManager,
focusModel: appDelegate.focusModel,
permissionGuidanceController: appDelegate.permissionGuidanceController,
suggestionSettings: appDelegate.suggestionSettings,
foundationModelAvailabilityService: appDelegate.foundationModelAvailabilityService,
powerSourceMonitor: appDelegate.powerSourceMonitor,
appUpdateManager: appDelegate.appUpdateManager,
onOpenSettings: {
appDelegate.settingsCoordinator.showSettings()
},
onReportFeedback: {
guard let baseURL = URL(string: "https://www.cotabby.app/feedback") else {
return
}
// Attach host details so the landing form can pre-fill the Environment block
// (Cotabby + macOS + hardware) and the user only has to write the actual report.
let url = DeviceInfo.snapshot().appending(to: baseURL)
NSWorkspace.shared.open(url)
}
)
} label: {
MenuBarStatusLabelView(
suggestionCoordinator: appDelegate.suggestionCoordinator,
suggestionSettings: appDelegate.suggestionSettings
)
}
.menuBarExtraStyle(.window)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Menu Scene Crashes Baseline Launch

The app still targets macOS 14.0, but this restores the MenuBarExtra(.window) scene as the main launch path. The reverted AppKit path existed because an Xcode 26-built .window menu extra can abort inside AttributeGraph on macOS 14 and early macOS 15 before applicationDidFinishLaunching runs, so supported users can crash on launch with no recovery window or status item.

Fix in Codex Fix in Claude Code


/// SwiftUI owns insertion/removal of the status item, while the settings model remains the
/// single durable source of truth. The setter writes only through the model; `@AppStorage`
/// observes the same `UserDefaults` key, so the getter and the scene re-evaluate off that one
/// write instead of persisting the value twice (which could drift if model-side validation is
/// ever added).
private var menuBarIconVisibilityBinding: Binding<Bool> {
Binding(
get: { isMenuBarIconVisible },
set: { appDelegate.suggestionSettings.setMenuBarIconVisible($0) }
)
}
}
4 changes: 2 additions & 2 deletions Cotabby/Models/SuggestionSettingsData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ struct SuggestionSettingsData: Equatable {
/// green preview while typing, disable it, or use both behaviors together.
var automaticallyFixTypos: Bool
var isPerformanceTrackingEnabled: Bool
/// Controls whether AppKit inserts Cotabby's status item into the system menu bar. The app keeps
/// running when this is false; reopening Cotabby provides the recovery path to Settings.
/// Controls whether SwiftUI inserts Cotabby's `MenuBarExtra` into the system menu bar. The app
/// keeps running when this is false; reopening Cotabby provides the recovery path to Settings.
var isMenuBarIconVisible: Bool
var isMenuBarWordCountVisible: Bool
var mirrorPreference: MirrorPreference
Expand Down
Loading
Loading