Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
35e1f06
Add customizable workspace actions: inline layouts, save-from-workspa…
austinywang Jul 4, 2026
c982b3b
Save agent session CLI via executableName, not provider rawValue
austinywang Jul 5, 2026
3b1caf1
Split workspace-action code into dedicated files for the length budget
austinywang Jul 5, 2026
006853f
Merge remote-tracking branch 'origin/main' into cmux-actions
austinywang Jul 5, 2026
17fba95
Add failing test: inline workspace surface-tab-bar button click is a …
austinywang Jul 5, 2026
a5f7766
Fix three codex-review findings on inline workspace actions
austinywang Jul 5, 2026
c0729dd
Extract new-workspace context menu from AppDelegate for the length bu…
austinywang Jul 5, 2026
d77afa0
Open Customize Actions in cmux's own editor, not the OS default app
austinywang Jul 6, 2026
df74429
Fix three codex-review findings: trust disclosure, index coalescing, …
austinywang Jul 6, 2026
aa728f3
Capture live terminal commands when saving workspace actions
austinywang Jul 6, 2026
46f8db4
Quote captured executables and disclose saved commands in the save di…
austinywang Jul 6, 2026
8bb5b60
Disclose saved URLs/env keys and sanitize the trust dialog title
austinywang Jul 6, 2026
8374daf
Preserve the invocation form of captured foreground commands
austinywang Jul 6, 2026
c830812
Harden saved-config permissions and disclose env in the trust prompt
austinywang Jul 6, 2026
94004a4
Use the shared agent sanitizer for capture; stop persisting workspace…
austinywang Jul 6, 2026
728dc26
Merge remote-tracking branch 'origin/main' into cmux-actions
austinywang Jul 6, 2026
9636d82
Cover agent aliases in capture, disclose cwd, align saved workspace name
austinywang Jul 6, 2026
2d1fcd9
Sanitize registry-owned agent executables (pi, grok, antigravity)
austinywang Jul 6, 2026
45d9ec7
Join capture on owned tty devices; validate auto-appended menu actions
austinywang Jul 6, 2026
325de05
Fail closed when foreground argv can't be captured
austinywang Jul 6, 2026
af9976e
Write saved actions through symlinked cmux.json to the real target
austinywang Jul 6, 2026
043807a
Disclose surface URLs in trust prompt; write config owner-only from b…
austinywang Jul 6, 2026
87fdc1e
Disclose setup's real cwd; refuse to overwrite a non-object actions b…
austinywang Jul 6, 2026
3b9eea2
Add action deletion from the plus menu; fail closed on unparseable co…
austinywang Jul 6, 2026
457c4f2
Keep String indices within one string when splicing the separator comma
austinywang Jul 6, 2026
605b640
Fix warning-budget guard: removeStart is a constant after the splice …
austinywang Jul 6, 2026
e3b3c15
Localize the trust-disclosure cwd/url labels
austinywang Jul 6, 2026
6f66015
Merge remote-tracking branch 'origin/main' into cmux-actions
austinywang Jul 6, 2026
4922394
Rename workspace actions to workspace layouts; add default layout for…
austinywang Jul 6, 2026
62e898d
Default-layout submenu: list only workspace layouts, restore row icons
austinywang Jul 6, 2026
c2c09a7
Split default-layout tests into CmuxConfigNewWorkspaceDefaultLayoutTests
austinywang Jul 6, 2026
dc8822a
Clear the new-workspace default when its layout is deleted
austinywang Jul 6, 2026
b73fe1f
Clarify the save menu item: Save Workspace as Layout…
austinywang Jul 6, 2026
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
548 changes: 546 additions & 2 deletions Resources/Localizable.xcstrings

Large diffs are not rendered by default.

113 changes: 113 additions & 0 deletions Sources/AppDelegate+NewWorkspaceContextMenu.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import AppKit
import Foundation

// MARK: - New-workspace plus-button context menu (verbatim move from AppDelegate.swift
// for the Swift file length budget; behavior unchanged)

@MainActor
private final class NewWorkspaceContextMenuActionBox: NSObject {
let windowId: UUID
let action: CmuxResolvedConfigAction

init(windowId: UUID, action: CmuxResolvedConfigAction) {
self.windowId = windowId
self.action = action
}
}

extension AppDelegate {

@discardableResult
func showNewWorkspaceContextMenu(
anchorView: NSView,
event: NSEvent,
debugSource: String = "titlebar.newWorkspace.contextMenu"
) -> Bool {
let context = contextForMainWindow(anchorView.window)
?? mainWindowContext(forShortcutEvent: event, debugSource: debugSource)
?? preferredMainWindowContextForWorkspaceCreation(event: event, debugSource: debugSource)
guard let context,
let cmuxConfigStore = context.cmuxConfigStore else {
return false
}

let configuredItems = cmuxConfigStore.newWorkspaceContextMenuItems

let menu = NSMenu()
for configuredItem in configuredItems {
switch configuredItem {
case .separator:
if !menu.items.isEmpty, menu.items.last?.isSeparatorItem == false {
menu.addItem(.separator())
}
case .action(let menuAction):
let item = NSMenuItem(
title: menuAction.title,
action: #selector(performNewWorkspaceContextMenuItem(_:)),
keyEquivalent: ""
)
item.target = self
item.representedObject = NewWorkspaceContextMenuActionBox(
windowId: context.windowId,
action: menuAction.action
)
item.toolTip = menuAction.tooltip
item.image = menuAction.icon?.contextMenuImage(
configSourcePath: menuAction.iconSourcePath,
globalConfigPath: cmuxConfigStore.globalConfigPath
)
menu.addItem(item)
// Hold ⌥ to turn a deletable saved action into its delete
// affordance, native alternate-item style.
if isDeletableGlobalAction(menuAction.action, cmuxConfigStore: cmuxConfigStore) {
let deleteFormat = String(
localized: "menu.newWorkspace.deleteLayoutAlternate",
defaultValue: "Delete “%@”"
)
let alternate = NSMenuItem(
title: String(format: deleteFormat, menuAction.action.title),
action: #selector(deleteWorkspaceConfigActionMenuItem(_:)),
keyEquivalent: ""
)
alternate.target = self
alternate.isAlternate = true
alternate.keyEquivalentModifierMask = [.option]
alternate.representedObject = WorkspaceActionDeleteBox(
windowId: context.windowId,
actionID: menuAction.action.id,
actionTitle: menuAction.action.title
)
menu.addItem(alternate)
}
}
}

while menu.items.last?.isSeparatorItem == true {
menu.removeItem(at: menu.items.count - 1)
}

appendSavedLayoutMenuItems(to: menu, windowId: context.windowId)

appendWorkspaceActionAffordances(
to: menu,
windowId: context.windowId,
cmuxConfigStore: cmuxConfigStore
)

NSMenu.popUpContextMenu(menu, with: event, for: anchorView)
return true
}

@objc private func performNewWorkspaceContextMenuItem(_ sender: NSMenuItem) {
guard let box = sender.representedObject as? NewWorkspaceContextMenuActionBox,
let context = mainWindowContexts.values.first(where: { $0.windowId == box.windowId }),
let window = resolvedWindow(for: context) else {
NSSound.beep()
return
}
guard executeConfiguredCmuxAction(box.action, context: context, preferredWindow: window) else {
NSSound.beep()
return
}
}
}
2 changes: 1 addition & 1 deletion Sources/AppDelegate+SavedLayoutMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extension AppDelegate {
}

let parent = NSMenuItem(
title: String(localized: "menu.savedLayout.newWorkspaceFromLayout", defaultValue: "New Workspace from Layout"),
title: String(localized: "menu.savedLayout.newWorkspaceFromLayout", defaultValue: "New Workspace from Template"),
action: nil,
keyEquivalent: ""
)
Expand Down
Loading
Loading