* Add keyboard copy mode for terminal scrollback
* Show vim copy mode indicator in terminal
* Fix vi copy-mode symbol keys and pending yank handling
* Refine copy-mode badge wording and font
* Rename keyboard copy-mode badge to VI MODE
* Address PR feedback for copy-mode routing and keyup handling
* Refresh copy-mode viewport row after scrolling
* Clear sidebar notification when user submits prompt in Claude Code
Add UserPromptSubmit hook to the Claude Code wrapper that calls
`cmux claude-hook prompt-submit`. This clears the workspace notification
and sets status back to "Running" when the user addresses Claude's question,
so the "waiting for input" preview in the sidebar goes away.
Also adds --tab support to clear_notifications socket command and
--workspace support to the clear-notifications CLI command for
per-workspace notification clearing.
Closes https://github.com/manaflow-ai/cmux/issues/799
* Address review feedback: stricter error handling
- clear-notifications CLI: error on explicit --workspace failure instead of
falling back to global clear. Env var still gracefully degrades.
- prompt-submit hook: propagate sendV1Command errors instead of swallowing
with try?.
- clear_notifications socket: validate --tab flag is present before resolving,
reject malformed args instead of falling back to selected tab.
* Gate env workspace fallback on windowId == nil in clear-notifications
Matches the pattern used by other CLI commands to avoid using
CMUX_WORKSPACE_ID from the caller shell when --window targets
a different window.
* Move UNUserNotificationCenter remove calls off main thread
removeDeliveredNotifications(withIdentifiers:) and
removePendingNotificationRequests(withIdentifiers:) perform synchronous XPC
to usernoted. When usernoted is slow or overwhelmed, this blocks the main
thread indefinitely, freezing the entire UI (confirmed via sample showing
__NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ for the full sample
duration on both cmux and cmux NIGHTLY).
Add extension methods on UNUserNotificationCenter that dispatch these calls
to a background queue. All 13 call sites in TerminalNotificationStore are
fire-and-forget (void, no data flows back), so moving them off-main is safe.
The @Published property mutations and dock badge updates remain on @MainActor.
* Use dedicated serial queue for notification removal dispatch
Replaces DispatchQueue.global(qos: .utility) with a private static serial
queue. If usernoted stalls, concurrent dispatches to the global pool could
exhaust threads. A dedicated queue caps concurrency at 1.
* Add debug logging for cmd+click link handling
Logs every decision point in the link opening pipeline:
- mouseDown/mouseUp: modifier keys, click count, position
- resolveTerminalOpenURLTarget: input URL, classification (external/embedded/fallback)
- OPEN_URL action handler: routing decision (cmuxBrowser disabled, external, whitelist miss, embedded browser pane)
All logs are #if DEBUG only via dlog().
* Update tagged build link format for Claude Code cmd+clickability
Claude Code gets a markdown link with the real derived-data path
(file:///tmp/cmux-<tag>/Build/Products/Debug/cmux%20DEV%20<tag>.app)
which is cmd+clickable in cmux. Codex keeps the original format.
* Add equal sign separators to Claude Code build link format
* Fix cmd+click URL buffer overread in OPEN_URL handler
cmux was using String(cString:) to decode the URL from
GHOSTTY_ACTION_OPEN_URL, which reads until a null terminator.
The C struct provides both a pointer and a length field (url + len),
and the pointer is not guaranteed to be null-terminated. This caused
cmux to read past the URL bytes into adjacent memory, producing
corrupted URLs like "https://example.comcom" and multi-URL
concatenations.
Fix: use Data(bytes:count:) with the length field, matching how
vanilla Ghostty decodes the same struct in Ghostty.Action.swift.
* Address review feedback: extract debugModifierString helper
* Add setting to hide Cmd-hold shortcut hints
* Bump bonsplit for Cmd-hold pane hint toggle
* Document tagged app link format in agent notes
* Disable Ctrl pane hints when hold-hints toggle is off
When sibling.hitTest() triggers a SwiftUI layout pass during the
drag handle's sibling walk, AppKit can call back into
windowDragHandleShouldCaptureHit before the outer invocation
finishes. This re-entry accesses SwiftUI view state that is already
held exclusively, causing a Swift runtime SIGABRT.
Add a module-level re-entrancy guard that bails out (returns false)
on nested calls to the sibling walk. Since hitTest is always called
on the main thread, a simple Bool flag is sufficient.
Crash was reproduced on macOS Sequoia 15.1.1 (24B91) in a UTM VM.
The crash stack: DraggableView.hitTest -> windowDragHandleShouldCaptureHit
-> sibling.hitTest -> SwiftUI body evaluation -> hitTest (re-entry)
-> exclusive-access violation -> SIGABRT.
* Add workspace-churn typing lag regression and fix
* Fix CI build for debug stress split calls
* Stabilize lag regression gate for low baseline latency
* Support pasting clipboard images as file paths in terminal
When the macOS clipboard contains only image data (e.g. from
Cmd+Ctrl+Shift+4 screenshot) and no text, Cmd+V now saves the image
as a temporary PNG file and pastes the file path into the terminal.
This allows CLI tools like Claude Code to receive pasted images.
The pasteboard heuristic only intercepts when there is image data
(TIFF/PNG) and no text/HTML/RTF, so normal text paste is unaffected.
Images over 10 MB are skipped and fall through to default behavior.
Closes#457
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix clipboard image paste: collision-free filenames and pasteAsPlainText validation
- Add UUID suffix to temp filenames to prevent overwrites when pasting
images multiple times in the same second
- Only enable Paste menu (not Paste as Plain Text) for image-only clipboard,
since pasteAsPlainText has no image-path handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Shell-escape pasted image path before sending to terminal
Use escapeDropForShell on the clipboard image temp path, consistent
with how drag/drop paths are escaped, to avoid issues with
shell-special characters in the path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add docstrings to paste and validation functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Jose Masri <ae_jmsalame@contractor.indeed.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Always open programmatic navigation (window.open, target=_blank) in a new tab.
Fix insecure HTTP path to treat nil targetFrame as new-tab intent.
Closes#606
Co-authored-by: lark <lark1115caster@gmail.com>
Toggling isHidden during a display cycle calls _setHidden:setNeedsDisplay:,
which posts another window-needs-display and pushes the pass count past
AppKit's per-cycle limit, causing an NSException crash near resize handles.
Remove the isHidden toggle from debugTopHitViewForCurrentEvent(); the hit
test now returns the overlay itself when it is the topmost view, which is
acceptable for debug logging purposes.
Add early return for non-leftMouseDown events in DraggableView.hitTest
to prevent re-entering SwiftUI view state during mouseMoved layout
passes, which caused fatal exclusive-access violations.
Set isTemplate = true on the menu bar icon image so macOS automatically
renders it black in light mode and white in dark mode. Changed the glyph
fill from white to black per template image convention.
Closes https://github.com/manaflow-ai/cmux/issues/737
* Add dark mode app icon variant for macOS Sequoia
Adds dark appearance entries to the AppIcon asset catalog so macOS 15+
automatically shows a dark-background icon when the system is in dark
mode. The chevron gradient and glow are preserved by recompositing the
foreground over a dark background (#1C1C1E).
Includes a generation script (scripts/generate_dark_icon.py) that
derives the dark PNGs from the light originals.
* Add icon picker in Settings and fix dark icon quality
Use the Figma chevron layer (design/cmux-icon-chevron.png) composited
over a dark background for pixel-perfect results, no white halo or
darkened gradient. Falls back to mathematical recomposition if the Figma
layer is missing.
Add an "App Icon" picker to Settings (under Theme) with three visual
options: Automatic (follows system appearance via asset catalog dark
variants on macOS 15+), Light, and Dark. The selection persists via
UserDefaults and is applied on launch in AppDelegate.ensureApplicationIcon.
* Fix dark icon chevron scale to match light icon
The Figma export was ~25% larger than the repo icon. Scale the Figma
chevron layer by 0.80x before compositing so the chevron size matches
exactly between light and dark variants.
* Use enhanced glow for dark icon
Add a soft blue bloom around the chevron on the dark background using
two Gaussian blur passes (wide at r=25 and tight at r=12) composited
at reduced opacity beneath the sharp chevron. Makes the icon pop more
against the dark squircle.
* Honor Ghostty background-opacity across all cmux chrome
Parse background-opacity from Ghostty config and propagate it through
the entire chrome pipeline: bonsplit tab bar (via RRGGBBAA hex),
browser panel/omnibar, titlebar, empty panel, and window background.
Decouple glass effect from sidebar blend mode — bgGlassEnabled now
defaults to false so opacity works independently. Add
GhosttyBackgroundTheme helper for consistent color+opacity resolution
across all UI surfaces.
Fixes https://github.com/manaflow-ai/cmux/issues/263
* Titlebar and chrome opacity matches terminal background-opacity
Use CALayer-level opacity for the titlebar background instead of SwiftUI
Color alpha, matching the terminal's Metal compositing path. Account for
the double alpha stacking in the terminal area (Bonsplit container bg +
Ghostty renderer) so the titlebar visually matches.
Also fix opacity-only config changes not triggering titlebar refresh on
Cmd+Shift+, reload.
The root ContentView body had `.frame(minWidth: 800, minHeight: 600)`
hardcoded since the initial commit, preventing users from resizing the
window narrower than 800px even when the sidebar is hidden and a narrow
terminal layout is perfectly usable.
Replace the magic numbers with the existing SessionPersistencePolicy
constants (minimumWindowWidth = 300, minimumWindowHeight = 200), which
were already defined and used for session-restore frame validation.
This gives those constants a second job as the canonical size floor and
makes it easy to tune the minimum in one place.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add `cmux <path>` to open directories and Homebrew binary stanza
CLI: `cmux .` or `cmux /path/to/dir` opens a new workspace at the
given directory. If the app isn't running, it launches first and waits
for the socket. Also adds `--cwd` flag to `new-workspace`.
Server: `workspace.create` now accepts an optional `cwd` parameter,
passed through to `TabManager.addWorkspace(workingDirectory:)`.
Homebrew: adds `binary` stanza to the cask so `cmux` CLI is globally
available after `brew install --cask cmux`. Updated both the cask file,
the CI workflow template, and the manual release script so automated
version bumps preserve the stanza.
* Address review: validate cwd type, fix socket detection, propagate errors
- looksLikePath now also matches paths containing `/` (e.g. `foo/bar`)
- openPath uses socket connection attempt instead of fileExists to detect
whether the app is running (Unix sockets may not appear on filesystem)
- launchApp/activateApp now throw instead of swallowing errors with try?
- Server validates that cwd param is a string, returns invalid_params error
if wrong type is passed
* Route local HTML open targets to cmux browser
* Keep file:// omnibar navigation inside cmux browser
* Load local file URLs via WKWebView file API
* Add browser regression test for local file URL loads
* Address PR feedback on local HTML and file URL handling
* Set cmux TestAction to Debug for UI tests
* Broaden XCTest detection for debug launch gate
* Fix AutomationSocketUITests launch hang in CI
* Stabilize CI Swift package resolution for test jobs
* Stabilize Xcode Cloud UI test focus and socket handling
* Add Xcode Cloud pre-xcodebuild submodule bootstrap
* Harden Xcode Cloud bonsplit bootstrap fallback
Sidebar body was calling sidebarOrderedPanelIds() multiple times per
render for branches, directories, and pull requests. Now computes it
once and passes through. Reduces redundant work during scroll frames.
Closes https://github.com/manaflow-ai/cmux/issues/655
- Notification/focus flash uses workspace customColor (fallback: accent)
- Selection bar/indicator uses workspace customColor when set
- Flash color propagated through Panel.triggerFlash(color:) API
- Browser panel flash overlay uses workspace color
- Regression tests for flash color resolution
Fixes https://github.com/manaflow-ai/cmux/issues/557
* Add "Open Folder…" command to open a workspace at a chosen directory
Adds a native folder picker (NSOpenPanel) accessible from:
- Command Palette (⌘⇧P → "Open Folder…")
- File menu with ⌘O shortcut
Selecting a folder opens a new workspace at that path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename openRepository → openFolder, add customizable shortcut
- Rename command ID from palette.openRepository to palette.openFolder
- Register openFolder in KeyboardShortcutSettings (default: Cmd+O)
- Wire menu bar shortcut through settings instead of hardcoding
- Add commandPaletteShortcutAction mapping for shortcut hint display
* Dismiss command palette before showing Open Folder panel
The NSOpenPanel.runModal() call blocked the main thread, keeping the
command palette visible behind the file picker. Wrapping in
DispatchQueue.main.async lets the palette dismiss first.
* Trigger GitHub PR refresh
---------
Co-authored-by: michalstrnadel <michal.strnadel@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Registers palette.triggerFlash wired to triggerFocusFlash() (same as
Cmd+Shift+H). Shortcut hint reads from KeyboardShortcutSettings so it
stays in sync with custom bindings.
Closes https://github.com/manaflow-ai/cmux/issues/633
Adds CmuxCLIPathInstaller with symlink management at /usr/local/bin/cmux,
exposed via Cmd+Shift+P command palette (VS Code style). Install entry
shown when CLI is not in PATH, uninstall entry shown when it is.
Falls back to osascript admin privilege escalation when /usr/local/bin
is not user-writable. Uses attributesOfItem instead of fileExists to
correctly handle dangling symlinks from relocated app bundles.
Closes https://github.com/manaflow-ai/cmux/issues/618
Cmd+Shift+Enter toggles zoom on the focused pane, expanding it to fill
the workspace. Splitting or creating new tabs auto-unzooms. Zoom state
shown as icon in sidebar tab. Includes bonsplit zoom toggle support.
Closes https://github.com/manaflow-ai/cmux/issues/136
Add nil guard in forceRefresh() to prevent dereferencing freed surface
pointer. Split else-if chains in Workspace.swift so
requestBackgroundSurfaceStartIfNeeded() runs if surface is freed during
the refresh call. Add regression test exercising the crash path.
- Wrap eval scripts in async IIFE that detects and awaits thenables,
using callAsyncJavaScript when available (macOS 11+) (#603)
- Register console/error telemetry hooks as WKUserScript at document
start so they survive navigation and are active before page JS (#604)
- Return typed envelope {__cmux_t, __cmux_v} from eval to distinguish
undefined from no return value; CLI prints "undefined" (#605)
- Keep dialog hooks as lazy injection only (not document-start) to
avoid suppressing WKUIDelegate native dialogs
- Add regression tests for async wrapper and undefined CLI rendering
Add window-identity check to windowDragHandleShouldCaptureHit so stale
leftMouseDown events from other apps (Finder, Dock) during launch don't
trigger the SwiftUI hierarchy walk while initial layout is mutating.
Add NSLock to breadcrumb limiter for thread safety. Update existing
tests to pass eventWindow for window-attached drag handles.
All four switch statements (case list, commandPaletteTitle,
commandPaletteKeywords, applicationBundlePathCandidates) are now
in consistent alphabetical order.