* Add hover background to split action buttons
Split buttons (terminal, browser, split right/down) now show a subtle
rounded-rect background highlight on hover. Matches standard macOS
toolbar button behavior.
* Prevent fade overlays from bleeding into bottom separator
* Render bottom separator above fade overlays to prevent bleed
* Exclude drop zone from scroll fade threshold
* Revert button hover, fix fade threshold with 32pt buffer
* Fix right fade threshold: subtract drop zone, 4pt tolerance
* Add leading padding to split buttons, fix fade threshold
* Rework tab bar: full-width scroll with floating split buttons
* Use ultraThinMaterial blur for floating split buttons
* Make split buttons group full height
* Full-height split button blur background
* Inset split buttons from bottom separator
* Fix blur overlapping separator
* Use matching tab bar bg for split buttons, clear separator
* Use regularMaterial blur for split buttons
* Try thickMaterial for split buttons
* Fade gradient + solid barFill for floating split buttons
* Use extended right fade as split buttons backdrop
* Add 5 debug styles for split button background
* Add Split Button Style debug window to Debug menu
* Clean up: no-bg floating buttons, alphabetical debug menu
- Split buttons float with no background (tabBarBackground covers the
area, scroll padding prevents tabs from appearing behind buttons)
- Default splitButtonsWidth to 120 so first render has correct padding
- Remove split button style debug window and debug styles
- Alphabetize Debug Windows menu entries, remove dividers
* Revert to HStack sibling layout, add debug menu docs to CLAUDE.md
- Split buttons are HStack siblings of the ScrollView, not overlays.
Single .background() on parent, no compositing mismatch.
- Alphabetize Debug Windows menu, remove dividers.
- Document Debug menu in CLAUDE.md.
* Add Split Button Layout debug window with 5 switchable approaches
* Fix fade gradient color to match tab bar background
* Add fade color debug window with 6 color options
* Use mask for scroll fades, fixes color mismatch
* Hide scroll fades in minimal mode unless hovering
* Remove split buttons from layout when hidden in minimal mode
* Always show fades, overlay buttons to prevent scroll jump
* Add hover-only mask fade behind split buttons
* Reduce button mask area to 90pt
* Animate button mask smoothly
* Fade entire button group together via opacity
* Add blur behind buttons with fade mask
* Use theme barBackground for button backdrop
* Blur + theme tint for button backdrop
* More tint (0.85), less blur
* Tint 0.2, clear bottom border
* Use terminal bg color, add scroll trailing padding for buttons
* Less blur, paneBackground at 0.75 opacity
* paneBackground at 0.9 opacity
* 0.97 opacity for button backdrop
* Test: fully opaque paneBackground
* Test: solid red backdrop
* Gradient + solid paneBackground backdrop, no mask
* Force opaque paneBackground for button backdrop
* Use barBackground for button backdrop
* Use terminal bg (paneBackground forced opaque)
* Pre-composite backdrop color for exact match
* Add 6 switchable backdrop styles in debug window
* Mask-based button area hiding, no backdrop color needed
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* reload.sh: default to build-only, add --launch flag to open app
By default, reload.sh now builds and prints the app path without
launching. Pass --launch to get the previous behavior (kill existing
instance and open). This lets agents build without stealing focus,
and the user can cmd-click the printed path to launch when ready.
* CLAUDE.md: use reload.sh output for app path instead of hardcoded home dir
The templates hardcoded /Users/lawrencechen/ which broke cmd-click
on machines with a different home directory. Agents now read the
actual path from reload.sh's "App path:" output.
* CLAUDE.md: add concrete example for app path URL format
Uses a fictional /Users/jane/ to make it clear the path comes from
reload.sh output, not a hardcoded value.
* CLAUDE.md: clearer step-by-step instructions for app path URL
Explicit 3-step recipe (grab path, prepend file://, format as link)
with example showing the reload.sh output and the expected result.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add typing hot path timing diagnostics
* Add stress workspace debug menu item
* Restore stress workspace preload debug path
* Reduce typing lag from sidebar re-evaluation and hitTest overhead
hitTest: gate divider/sidebar/drag routing to pointer events only,
avoiding two full view-tree walks per non-pointer event.
forceRefresh: replace per-keystroke ISO8601DateFormatter + FileHandle
I/O with dlog() in DEBUG builds.
TabItemView: replace @EnvironmentObject subscriptions with plain refs
and precomputed parameters, add Equatable conformance to skip body
re-evaluation when parent rebuilds with unchanged values. @self changed
re-evaluations dropped from 668 to 1 during rapid typing.
* Add typing-latency guardrail comments and CLAUDE.md pitfalls
Strategic comments on hitTest, TabItemView, and forceRefresh to prevent
future regressions. Adds typing-latency-sensitive paths to CLAUDE.md
pitfalls section so agents know the constraints before editing.
* Add workspace palette actions and fix release autosave typing guard
Add Move Up/Down/Top, Close Other/Above/Below, Mark Read/Unread to
Cmd+Shift+P command palette and a Workspace submenu in the menu bar.
Fix recordTypingActivity() being gated behind #if DEBUG, which prevented
release builds from honoring the typing quiet period in autosave.
Two-commit structure for bug fix PRs: first commit adds the failing
test (CI red), second commit adds the fix (CI green). Proves in the
GitHub PR UI that the test genuinely catches the bug.
* Return browser screenshot image URL
* Make screenshot path/url best effort
* cli: omit screenshot png_base64 from json output
* browser wait: fail fast on js errors and include screenshot in help
* browser wait: avoid main-actor default world warning
* tests: scope contentWorld regression check to function signature
* browser screenshot: clean up output handling and tests
* browser wait: resolve snapshot refs in selector waits
Agents were following CLAUDE.md instructions to run bare xcodebuild
without -derivedDataPath, producing untagged cmux DEV.app that shares
the default debug socket and steals window focus.
Replace bare xcodebuild instruction with reload.sh --tag. Replace
E2E/Basic tests sections with a unified testing policy that forbids
local test runs and requires tagged builds.
* Add Language setting to Settings for per-app locale override
Uses UserDefaults AppleLanguages to override locale without changing
macOS system language. Picker shows System/English/Japanese with a
restart prompt when the selection changes.
* Address review feedback: guard relaunch and reset behavior
- Guard relaunchApp() against Process launch failure (don't terminate
if the new instance didn't start)
- Prevent restart dialog from firing during Reset All Settings
* Add localization requirement to CLAUDE.md
All user-facing strings must use String(localized:) with keys in
Localizable.xcstrings and translations for all supported languages.
* Fix relaunch: use detached shell so open survives app exit
The previous approach spawned open as a child process that could get
killed when the parent terminated. Now spawns a shell with sleep+open
that outlives the current process.
* Fix shell injection risk and SwiftUI state batching in language setting
- Pass bundle path via environment variable instead of interpolating
into shell command string
- Defer isResettingSettings=false to next run loop tick so onChange
handler reliably sees the guard during Reset All Settings
* 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
Tests run via SSH and aren't cmux descendants, so cmuxOnly mode
rejects the connection. Also fix socket path from /tmp/cmux.sock
to /tmp/cmux-debug.sock for debug builds.
* Fix sidebar drag-and-drop broken by FileDropOverlayView
The FileDropOverlayView (added in 9fd3cc2) sits on the window's theme
frame above the content view. Its hitTest returned self for all events,
causing AppKit to route drag sessions to the overlay instead of the
content view where SwiftUI lives. AppKit walks UP the superview chain
from the hit-tested view, never checking siblings — so SwiftUI's
.onDrop handlers for sidebar tab reordering were never reached.
Three changes fix this:
1. Smart hitTest: check NSPasteboard(name: .drag) for .fileURL and only
return self during Finder file drags. Return nil otherwise so mouse
events and internal drags pass through to the content view.
2. Custom UTType for sidebar drags: replace the fragile UTType.plainText
hack with a proper com.cmux.sidebar-tab-reorder type registered in
Info.plist. Uses visibility: .ownProcess since it's internal-only.
3. Narrow overlay registration: only register for .fileURL instead of
.fileURL + .URL + .string. The broad .string type collided with
text-based drag payloads.
* Add custom UTType Info.plist pitfall to CLAUDE.md
* Fix terminal keys (arrows, Ctrl+N/P) swallowed after opening browser
After a browser panel is shown, SwiftUI's internal focus system activates
and its _NSHostingView starts consuming arrow keys and other non-Command
key events via performKeyEquivalent, preventing them from reaching the
terminal's keyDown handler.
Fix: In the NSWindow performKeyEquivalent swizzle, when GhosttyNSView is
the first responder and the event has no Command modifier, route directly
to the terminal's performKeyEquivalent — bypassing SwiftUI's view hierarchy
walk entirely.
Also clear stale browserAddressBarFocusedPanelId when a terminal surface
has focus, preventing Cmd+N from being eaten by omnibar selection logic
after focus transitions away from a browser.
Adds DEBUG-only keyboard event ring buffer (KeyDebugLog) that dumps to
/tmp/cmux-key-debug.log for diagnosing future key routing issues.
* Fix split focus and Cmd+Shift+N swallowed after opening browser
Split focus: capture the source terminal's hostedView before bonsplit
mutates focusedPaneId, so focusPanel moves focus FROM the old pane
instead of from the new pane to itself. Also retry ensureFocus when the
new terminal's view has no window yet (matching the existing retry
pattern for isVisibleInUI).
Cmd+Shift+N: after WKWebView has been in the responder chain, SwiftUI's
internal focus system can intercept Command-key events in the content
view hierarchy (returning true) without firing the CommandGroup action
closure. Fix by dispatching Command-key events directly to NSApp.mainMenu
when the terminal is first responder, bypassing the broken SwiftUI path.
Also add Cmd+Shift+N to handleCustomShortcut so it's customizable and
doesn't depend on SwiftUI menu dispatch at all.
* Unified debug event log: merge key/mouse/focus into /tmp/cmux-debug.log
- Delete KeyDebugLog, MouseDebugLog, klog(), mlog() from AppDelegate
- Replace all klog/mlog calls with dlog() (provided by bonsplit)
- Remove debugLogCallback wiring from Workspace
- Add focus change logging: focus.panel, focus.firstResponder,
split.created, focus.moveFocus
- Add import Bonsplit where needed for dlog access
- Fix stale drag state on cancelled tab drags (bonsplit submodule)
* Fix split focus stolen by re-entrant becomeFirstResponder during reparenting
During programmatic splits (Cmd+D / Cmd+Shift+D), SwiftUI reparents the old
terminal view, which fires becomeFirstResponder → onFocus → focusPanel for the
OLD panel, stealing focus from the newly created pane.
Add programmaticFocusTargetPanelId guard to suppress re-entrant focusPanel
calls for non-target panels during split creation.
Also document the unified debug event log in CLAUDE.md.
* Clear stale title/favicon when browser navigation fails
When a page fails to load (e.g. connection refused), the tab was still
showing the previous page's title and favicon. Now didFailProvisionalNavigation
resets pageTitle to the failed URL and clears faviconPNGData.
* Fix Cmd+N swallowed by browser omnibar and improve split focus suppression
- Only Ctrl+N/P trigger omnibar navigation, not Cmd+N/P (Cmd+N should
always create new workspace regardless of address bar focus)
- Move split focus suppression from workspace-level guard to source:
suppress becomeFirstResponder side-effects (onFocus + ghostty_surface_set_focus)
directly on the old GhosttyNSView during reparenting, preventing both
model-level and libghostty-level focus divergence
- Remove programmaticFocusTargetPanelId from Workspace.focusPanel
* Fix omnibar hang, WebView white flash, drag-over-browser, and idle CPU spin
- Omnibar: first click selects all without entering NSTextView tracking loop;
subsequent clicks have 3s synthetic mouseUp safety net to prevent hang
- WebView: set underPageBackgroundColor to match window so new browsers don't
flash white before content loads
- Drag/drop: register custom UTType (com.splittabbar.tabtransfer) in Info.plist
so WKWebView doesn't intercept tab drags; override registerForDraggedTypes
on CmuxWebView as belt-and-suspenders
- CPU: fix infinite makeFirstResponder loop in controlTextDidEndEditing by
checking both the text field and its field editor (the actual first responder)
Fix blank terminal on macOS 26 and macOS 15:
- Add macOS 26 guard to two additional code paths that set window non-opaque
- Fix NSVisualEffectView z-order: add to themeFrame instead of contentView
- Align sidebarBlendMode defaults between @AppStorage and UserDefaults
- Add read_screen socket command and blank screen regression test
- Add reloads.sh staging script
- Restore real ZDOTDIR early in zsh startup so HISTFILE uses user history\n- Preserve user ZDOTDIR without treating Ghostty injected ZDOTDIR as user\n- Add regression tests and terminfo overlay for bright colors
Key changes:
- Fix keyboard input handling for control characters in GhosttyTerminalView
- Set consumed_mods correctly (exclude Ctrl/Cmd from consumed mods)
- Send unmodified character text for Ctrl+key combinations
- Add unshifted_codepoint support for proper key encoding
- Add TerminalController with Unix socket API (/tmp/ghosttytabs.sock)
- Commands: send, send_key, list_tabs, new_tab, close_tab, select_tab
- Supports ctrl-c, ctrl-d, ctrl-z, enter, tab, escape, etc.
- Add Python test client and automated test suite
- tests/ghosttytabs.py - Python client library
- tests/test_ctrl_socket.py - Main Ctrl+C/D test suite (4 tests)
- tests/test_signals_auto.py - Standalone PTY signal tests
- Update CLAUDE.md with socket API documentation and testing guide
- Update .gitignore for Python cache files
This fixes Ctrl+C/D not working in apps like claude-code, btop, opencode
while continuing to work in simpler apps like htop.