Repository navigation
Consolidate debug extractions into CmuxFeedback (no new packages) #6224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6288209
Consolidate debug extractions into CmuxFeedback + CmuxAppKitSupportUI…
azooz2003-bit 849e41c
Fix package-conventions-lint: scope ComposerDictationTextMerge onto S…
azooz2003-bit 25eec4f
AboutTitlebarDebugStore: split config snapshot from pasteboard write
azooz2003-bit 7e7a14b
Merge remote-tracking branch 'origin/main' into feat-consolidate-debug
azooz2003-bit 9d382af
Merge remote-tracking branch 'origin/main' into feat-consolidate-debug
azooz2003-bit c02aad3
Merge remote-tracking branch 'origin/main' into feat-consolidate-debug
azooz2003-bit 8441595
Merge remote-tracking branch 'origin/main' into feat-consolidate-debug
azooz2003-bit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
.../Sources/CmuxAppKitSupportUI/AboutTitlebarDebug/Coordinator/DebugWindowsCoordinator.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| #if canImport(AppKit) | ||
|
|
||
| import AppKit | ||
| public import Observation | ||
|
|
||
| /// Owns and sequences the About Titlebar Debug subsystem on behalf of the app. | ||
| /// | ||
| /// The app composition root constructs one coordinator, injecting the | ||
| /// ``WindowDecorating`` seam, and forwards its existing call sites (the Debug | ||
| /// menu, the `About`/`Acknowledgments` window controllers, and "open all debug | ||
| /// windows") into this type. The coordinator owns the ``AboutTitlebarDebugStore`` | ||
| /// and lazily owns the editor window controller, so the app target no longer | ||
| /// declares the underlying types. | ||
| @MainActor | ||
| @Observable | ||
| public final class DebugWindowsCoordinator { | ||
| /// The store backing the About Titlebar Debug options. Exposed so the app's | ||
| /// `About`/`Acknowledgments` window controllers can apply current options to | ||
| /// their windows as they build them. | ||
| public let aboutTitlebarStore: AboutTitlebarDebugStore | ||
|
|
||
| @ObservationIgnored | ||
| private weak var decorator: (any WindowDecorating)? | ||
|
|
||
| @ObservationIgnored | ||
| private var aboutTitlebarController: AboutTitlebarDebugWindowController? | ||
|
|
||
| /// Creates the coordinator. | ||
| /// | ||
| /// - Parameter decorator: The window-decoration seam. Held weakly because the | ||
| /// app-side conformer (`AppDelegate`) is a singleton that also owns this | ||
| /// coordinator. | ||
| public init(decorator: (any WindowDecorating)?) { | ||
| self.decorator = decorator | ||
| self.aboutTitlebarStore = AboutTitlebarDebugStore(decorator: decorator) | ||
| } | ||
|
|
||
| /// Presents the About Titlebar Debug editor, creating its window on first use. | ||
| public func showAboutTitlebarDebugWindow() { | ||
| let controller = aboutTitlebarController ?? AboutTitlebarDebugWindowController( | ||
| store: aboutTitlebarStore, | ||
| decorator: decorator | ||
| ) | ||
| aboutTitlebarController = controller | ||
| controller.show() | ||
| } | ||
| } | ||
|
|
||
| #endif |
21 changes: 21 additions & 0 deletions
21
...pKitSupportUI/Sources/CmuxAppKitSupportUI/AboutTitlebarDebug/Seams/WindowDecorating.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| #if canImport(AppKit) | ||
|
|
||
| public import AppKit | ||
|
|
||
| /// Applies the app's standard window chrome (background, blur, traffic-light | ||
| /// placement, and related decorations) to a freshly built or reconfigured | ||
| /// `NSWindow`. | ||
| /// | ||
| /// This inverts the `AboutTitlebarDebug*` types' previous reach into | ||
| /// `AppDelegate.shared`: the app target's `AppDelegate` conforms and is injected | ||
| /// into ``DebugWindowsCoordinator`` at the composition root, so this package owns | ||
| /// no reference to the application delegate. | ||
| @MainActor | ||
| public protocol WindowDecorating: AnyObject { | ||
| /// Applies the standard cmux window decorations to `window`. | ||
| /// | ||
| /// - Parameter window: The window whose chrome should be normalized. | ||
| func applyWindowDecorations(to window: NSWindow) | ||
| } | ||
|
|
||
| #endif |
166 changes: 166 additions & 0 deletions
166
...ortUI/Sources/CmuxAppKitSupportUI/AboutTitlebarDebug/Stores/AboutTitlebarDebugStore.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| #if canImport(AppKit) | ||
|
|
||
| public import AppKit | ||
| public import Observation | ||
|
|
||
| /// Holds the live ``AboutTitlebarDebugOptions`` for each ``AboutWindowKind`` and | ||
| /// applies them to matching open windows. | ||
| /// | ||
| /// This is the single writer of the debug options. Editing ``aboutOptions`` | ||
| /// (directly or via ``update(_:for:)``) immediately reapplies the new treatment | ||
| /// to any open window with the matching identifier, preserving the original | ||
| /// `didSet`-driven behavior. Window decoration is delegated to an injected | ||
| /// ``WindowDecorating`` seam rather than reaching into the app delegate. | ||
| @MainActor | ||
| @Observable | ||
| public final class AboutTitlebarDebugStore { | ||
| /// The current options for the About window. Setting this reapplies them to | ||
| /// every open About window. | ||
| public var aboutOptions = AboutTitlebarDebugOptions.defaults(for: .about) { | ||
| didSet { applyToOpenWindows(for: .about) } | ||
| } | ||
|
|
||
| @ObservationIgnored | ||
| private weak var decorator: (any WindowDecorating)? | ||
|
|
||
| /// Creates a store. | ||
| /// | ||
| /// - Parameter decorator: The seam used to apply standard window chrome after | ||
| /// a debug option change. Held weakly because the app-side conformer | ||
| /// (`AppDelegate`) is a singleton that also owns this store, so a strong | ||
| /// reference would form a retain cycle. | ||
| public init(decorator: (any WindowDecorating)?) { | ||
| self.decorator = decorator | ||
| } | ||
|
|
||
| /// Returns the current options for a window kind. | ||
| public func options(for kind: AboutWindowKind) -> AboutTitlebarDebugOptions { | ||
| switch kind { | ||
| case .about: | ||
| return aboutOptions | ||
| } | ||
| } | ||
|
|
||
| /// Replaces the current options for a window kind. | ||
| public func update(_ newValue: AboutTitlebarDebugOptions, for kind: AboutWindowKind) { | ||
| switch kind { | ||
| case .about: | ||
| aboutOptions = newValue | ||
| } | ||
| } | ||
|
|
||
| /// Resets a window kind to its non-overriding defaults. | ||
| public func reset(_ kind: AboutWindowKind) { | ||
| update(AboutTitlebarDebugOptions.defaults(for: kind), for: kind) | ||
| } | ||
|
|
||
| /// Reapplies the current options to every open window of the given kind. | ||
| /// | ||
| /// A nil `NSApp` (no running application, e.g. a headless unit-test process) | ||
| /// is a no-op; in the running app `NSApp` is always present, so this is | ||
| /// behavior-preserving. | ||
| public func applyToOpenWindows(for kind: AboutWindowKind) { | ||
| guard let app = NSApp else { return } | ||
| for window in app.windows where window.identifier?.rawValue == kind.windowIdentifier { | ||
| apply(options(for: kind), to: window, for: kind) | ||
| } | ||
| } | ||
|
|
||
| /// Reapplies the current options to every open About window. | ||
| public func applyToOpenWindows() { | ||
| applyToOpenWindows(for: .about) | ||
| } | ||
|
|
||
| /// Applies the current options for `kind` to a specific window. Used by the | ||
| /// About/Acknowledgments window controllers as they build their windows. | ||
| public func applyCurrentOptions(to window: NSWindow, for kind: AboutWindowKind) { | ||
| apply(options(for: kind), to: window, for: kind) | ||
| } | ||
|
|
||
| /// Builds the human-readable snapshot of the current About options. Pure (no | ||
| /// pasteboard side effect) so it is unit-testable without mutating the | ||
| /// process clipboard; `copyConfigToPasteboard()` is the side-effecting wrapper. | ||
| public func configSnapshot() -> String { | ||
| let about = options(for: .about) | ||
| return """ | ||
| # About Titlebar Debug | ||
| about.overridesEnabled=\(about.overridesEnabled) | ||
| about.title=\(about.windowTitle) | ||
| about.titleVisibility=\(about.titleVisibility.rawValue) | ||
| about.titlebarAppearsTransparent=\(about.titlebarAppearsTransparent) | ||
| about.movableByWindowBackground=\(about.movableByWindowBackground) | ||
| about.titled=\(about.titled) | ||
| about.closable=\(about.closable) | ||
| about.miniaturizable=\(about.miniaturizable) | ||
| about.resizable=\(about.resizable) | ||
| about.fullSizeContentView=\(about.fullSizeContentView) | ||
| about.showToolbar=\(about.showToolbar) | ||
| about.toolbarStyle=\(about.toolbarStyle.rawValue) | ||
| """ | ||
| } | ||
|
|
||
| /// Copies a human-readable snapshot of the current About options to the | ||
| /// general pasteboard. | ||
| public func copyConfigToPasteboard() { | ||
| let pasteboard = NSPasteboard.general | ||
| pasteboard.clearContents() | ||
| pasteboard.setString(configSnapshot(), forType: .string) | ||
| } | ||
|
|
||
| private func apply(_ options: AboutTitlebarDebugOptions, to window: NSWindow, for kind: AboutWindowKind) { | ||
| let effective = options.overridesEnabled ? options : AboutTitlebarDebugOptions.defaults(for: kind) | ||
| let resolvedTitle = effective.windowTitle.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| window.title = resolvedTitle.isEmpty ? kind.fallbackTitle : resolvedTitle | ||
| window.titleVisibility = effective.titleVisibility.windowValue | ||
| window.titlebarAppearsTransparent = effective.titlebarAppearsTransparent | ||
| window.isMovableByWindowBackground = effective.movableByWindowBackground | ||
| window.toolbarStyle = effective.toolbarStyle.windowValue | ||
|
|
||
| if effective.showToolbar { | ||
| ensureToolbar(on: window, kind: kind) | ||
| } else if window.toolbar != nil { | ||
| window.toolbar = nil | ||
| } | ||
|
|
||
| var styleMask = window.styleMask | ||
| setStyleMaskBit(&styleMask, .titled, enabled: effective.titled) | ||
| setStyleMaskBit(&styleMask, .closable, enabled: effective.closable) | ||
| setStyleMaskBit(&styleMask, .miniaturizable, enabled: effective.miniaturizable) | ||
| setStyleMaskBit(&styleMask, .resizable, enabled: effective.resizable) | ||
| setStyleMaskBit(&styleMask, .fullSizeContentView, enabled: effective.fullSizeContentView) | ||
| window.styleMask = styleMask | ||
|
|
||
| let maxSize = effective.resizable ? NSSize(width: 8192, height: 8192) : kind.minimumSize | ||
| window.minSize = kind.minimumSize | ||
| window.maxSize = maxSize | ||
| window.contentMinSize = kind.minimumSize | ||
| window.contentMaxSize = maxSize | ||
| window.invalidateShadow() | ||
| decorator?.applyWindowDecorations(to: window) | ||
| } | ||
|
|
||
| private func ensureToolbar(on window: NSWindow, kind: AboutWindowKind) { | ||
| guard window.toolbar == nil else { return } | ||
| let identifier = NSToolbar.Identifier("cmux.debug.titlebar.\(kind.rawValue)") | ||
| let toolbar = NSToolbar(identifier: identifier) | ||
| toolbar.allowsUserCustomization = false | ||
| toolbar.autosavesConfiguration = false | ||
| toolbar.displayMode = .iconOnly | ||
| toolbar.showsBaselineSeparator = false | ||
| window.toolbar = toolbar | ||
| } | ||
|
|
||
| private func setStyleMaskBit( | ||
| _ styleMask: inout NSWindow.StyleMask, | ||
| _ bit: NSWindow.StyleMask, | ||
| enabled: Bool | ||
| ) { | ||
| if enabled { | ||
| styleMask.insert(bit) | ||
| } else { | ||
| styleMask.remove(bit) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #endif |
90 changes: 90 additions & 0 deletions
90
...tUI/Sources/CmuxAppKitSupportUI/AboutTitlebarDebug/Values/AboutTitlebarDebugOptions.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| #if canImport(AppKit) | ||
|
|
||
| public import AppKit | ||
|
|
||
| /// A complete, editable description of the titlebar treatment applied to an | ||
| /// About-family window by the About Titlebar Debug subsystem. | ||
| /// | ||
| /// When ``overridesEnabled`` is `false`, the store falls back to ``defaults(for:)`` | ||
| /// so the window keeps its normal appearance; the remaining fields only take | ||
| /// effect once overrides are enabled. | ||
| public struct AboutTitlebarDebugOptions: Equatable, Sendable { | ||
| /// Whether the debug overrides in this value are applied at all. | ||
| public var overridesEnabled: Bool | ||
| /// The window title text (trimmed; empty falls back to the kind's title). | ||
| public var windowTitle: String | ||
| /// Whether the title text is shown or hidden. | ||
| public var titleVisibility: TitlebarVisibilityOption | ||
| /// Whether the titlebar background is transparent. | ||
| public var titlebarAppearsTransparent: Bool | ||
| /// Whether the window is draggable by its background. | ||
| public var movableByWindowBackground: Bool | ||
| /// Whether the `.titled` style-mask bit is set. | ||
| public var titled: Bool | ||
| /// Whether the `.closable` style-mask bit is set. | ||
| public var closable: Bool | ||
| /// Whether the `.miniaturizable` style-mask bit is set. | ||
| public var miniaturizable: Bool | ||
| /// Whether the `.resizable` style-mask bit is set. | ||
| public var resizable: Bool | ||
| /// Whether the `.fullSizeContentView` style-mask bit is set. | ||
| public var fullSizeContentView: Bool | ||
| /// Whether a toolbar is attached to the window. | ||
| public var showToolbar: Bool | ||
| /// The toolbar style applied when ``showToolbar`` is `true`. | ||
| public var toolbarStyle: TitlebarToolbarStyleOption | ||
|
|
||
| /// Creates an options value with every field specified. | ||
| public init( | ||
| overridesEnabled: Bool, | ||
| windowTitle: String, | ||
| titleVisibility: TitlebarVisibilityOption, | ||
| titlebarAppearsTransparent: Bool, | ||
| movableByWindowBackground: Bool, | ||
| titled: Bool, | ||
| closable: Bool, | ||
| miniaturizable: Bool, | ||
| resizable: Bool, | ||
| fullSizeContentView: Bool, | ||
| showToolbar: Bool, | ||
| toolbarStyle: TitlebarToolbarStyleOption | ||
| ) { | ||
| self.overridesEnabled = overridesEnabled | ||
| self.windowTitle = windowTitle | ||
| self.titleVisibility = titleVisibility | ||
| self.titlebarAppearsTransparent = titlebarAppearsTransparent | ||
| self.movableByWindowBackground = movableByWindowBackground | ||
| self.titled = titled | ||
| self.closable = closable | ||
| self.miniaturizable = miniaturizable | ||
| self.resizable = resizable | ||
| self.fullSizeContentView = fullSizeContentView | ||
| self.showToolbar = showToolbar | ||
| self.toolbarStyle = toolbarStyle | ||
| } | ||
|
|
||
| /// The default, non-overriding options for a given window kind. This matches | ||
| /// the window's normal appearance, so applying it with overrides disabled is | ||
| /// a no-op relative to the system defaults. | ||
| public static func defaults(for kind: AboutWindowKind) -> AboutTitlebarDebugOptions { | ||
| switch kind { | ||
| case .about: | ||
| return AboutTitlebarDebugOptions( | ||
| overridesEnabled: false, | ||
| windowTitle: "About cmux", | ||
| titleVisibility: .hidden, | ||
| titlebarAppearsTransparent: true, | ||
| movableByWindowBackground: false, | ||
| titled: true, | ||
| closable: true, | ||
| miniaturizable: true, | ||
| resizable: false, | ||
| fullSizeContentView: false, | ||
| showToolbar: false, | ||
| toolbarStyle: .automatic | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Localize the default window title.
The
windowTitledefault value indefaults(for:)uses a bare English string"About cmux"that becomes the displayed window title. This should useString(localized:defaultValue:)to match the localization requirement. Consider reusing the same key asAboutWindowKind.fallbackTitlefor consistency.🌐 Proposed localization fix
case .about: return AboutTitlebarDebugOptions( overridesEnabled: false, - windowTitle: "About cmux", + windowTitle: String(localized: "window.about.title", defaultValue: "About cmux"), titleVisibility: .hidden,🤖 Prompt for AI Agents
Source: Coding guidelines