* Hide new-tab browser toggles and align dark-mode button style
* Switch forced dark mode from dimming overlay to dark theme
* Add tri-state browser theme mode for embedded web view
* Hide browser theme menu chevron in toolbar
* Use outline icons for browser theme toggle
* Align browser theme icon tint with DevTools button
* Force monochrome rendering for browser toolbar icons
* Reduce browser theme icon weight for visual parity
* Tune browser theme icon stroke for perceptual color match
* Force flat SF Symbol color rendering for toolbar icons
* Use button popover for browser theme selector
* Address PR 242 follow-ups for titlebar and browser background
* Restore titlebar border per follow-up scope
* Refresh browser under-page color with Ghostty opacity
* Browser: theme blank page fallback for about:blank
* Browser: keep new tabs webview-less until first nav
* Remove border below titlebar
Remove the 1px separator line overlay at the bottom of the custom
titlebar and its associated fakeTitlebarSeparatorColor computed property.
* Remove tab hover background in bonsplit
Update bonsplit submodule to remove the background fill on hovered
non-selected tabs.
* Restore titlebar border with system separator color, hover bg on all tabs, browser theme bg
- Add back 1px bottom border on titlebar using NSColor.separatorColor
(matches bonsplit tab separator color)
- Tab hover background now applies to all tabs including the selected one
- Browser address bar and under-page background now use Ghostty theme
background color instead of window background
Handle multi-button mouse events in the browser panel's WKWebView:
- Mouse back button (button 3) triggers goBack(), forward button
(button 4) triggers goForward(), enabling side-button navigation
on mice like Logitech
- Middle-click (button 2) on a link opens it in a new browser tab
by hit-testing the click position via JavaScript and routing through
the existing openLinkInNewTab mechanism
The browser omnibar's updateNSView and controlTextDidEndEditing
were both dispatching makeFirstResponder calls without any guard
against re-dispatch. Each makeFirstResponder triggers SwiftUI's
FirstResponderObserver, which re-evaluates the view graph, which
calls updateNSView again, creating an infinite loop via the main
dispatch queue.
Fix: Add a pendingFocusRequest flag on the coordinator to prevent
re-dispatching while a focus/blur request is already in flight.
Also add nsView.currentEditor() != nil to the isFirstResponder
check so the field is recognized as focused during the transition
when the field editor (not the field itself) is first responder.
* Add "+" menu button to horizontal tab bar for new terminal/browser tabs
Adds a "+" button to the tab bar (next to split buttons) that shows a
dropdown menu with "New Terminal ⌘T" and "New Browser ⌘⇧L" options.
- Uses native NSButton + NSMenu so the icon matches the split buttons
- Menu appears below the button
- Routes tab creation through new didRequestNewTab delegate method
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* works
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix multi-workspace drag/drop, WebView click focus, and add regression tests
- Wire bonsplit isInteractive to workspace active state so inactive
workspace NSViews are hidden from AppKit event routing
- Add CmuxWebView.mouseDown notification for browser panel focus
tracking (AppKit delivers clicks to WKWebView, not SwiftUI overlays)
- Add multi-workspace focus regression test covering isHidden fix,
rapid workspace switching, and browser panel focus routing
* Bump version to 1.36.0
The 3-second safety net that posts a synthetic mouseUp to break out of
NSTextView's stuck tracking loop was dispatched on the main queue. Since
super.mouseDown blocks the main thread in the tracking loop, the timeout
could never fire. Use a background queue instead (NSApp.postEvent is
thread-safe). Use DispatchWorkItem.isCancelled for atomic cancellation.
* 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)