Commit graph

1905 commits

Author SHA1 Message Date
Austin Wang
35cb42fbc8
Add copy-on-select preference (#2282) 2026-03-29 18:16:05 -07:00
Austin Wang
94cc865e83
Fix sidebar live refresh for branch and PR state (#2331)
* Add regression coverage for sidebar live refresh

* Refresh sidebar git metadata on active workspaces
2026-03-29 18:15:57 -07:00
Austin Wang
e419fd9164
Fix remote proxy notification spam with cooldown, backoff, and SSH keepalive (#2325) (#2330)
- Add 5-minute per-host cooldown for remote error notifications
- Add exponential backoff (capped at 60s) to proxy broker and session controller retries
- Add default SSH ConnectTimeout/ServerAliveInterval/ServerAliveCountMax to detect dead connections faster
- Fix error status clearing to only reset on actual .connected state

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 18:07:39 -07:00
Austin Wang
9e75355525
Fix sidebar layout loop in workspace list (#2328) 2026-03-29 17:57:17 -07:00
Austin Wang
d95158e69d
Fix #2210: coalesce portal sync to latest geometry (#2214) 2026-03-29 17:55:40 -07:00
Austin Wang
45090d23df
Bump version to 0.63.1 (#2310) 2026-03-28 15:05:03 -07:00
Austin Wang
386f5abf67
Fix macOS compatibility: versioned geometry persistence and re-entrant layout crash (#2308)
- Version the persisted window geometry schema (v1 → v2) and clean up
  legacy UserDefaults keys so stale payloads from older releases don't
  cause crashes on startup.
- Defer layout follow-up flush via asyncAfter(0) and track an attempt
  version counter to invalidate stale retries, preventing re-entrant
  displayIfNeeded crashes triggered by SwiftUI geometry change callbacks.
- Replace fixed RunLoop delays in tests with polling waitUntil helpers
  and increase socket wait timeout for CI reliability.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:17:16 -07:00
Austin Wang
f1be3978ab
Fix stale session geometry crash after 0.63.0 upgrade (#2306)
* Add regression coverage for stale window geometry migration

* Discard stale persisted window geometry on launch
2026-03-28 13:52:48 -07:00
Kyle James Walker (he/him)
97c2bc92d4
Defer layout follow-up flush to avoid re-entrant displayIfNeeded crash (#2305)
* fix(workspace): defer layout follow-up flush to avoid re-entrant displayIfNeeded crash

beginEventDrivenLayoutFollowUp() ended with a synchronous call to
attemptEventDrivenLayoutFollowUp(), which calls flushWorkspaceWindowLayouts()
→ window.contentView?.displayIfNeeded(). This is fine when invoked from
user-event handlers, but splitTabBar(_:didChangeGeometry:) fires from inside
SwiftUI's .onChange(of: geometry) during an active AppKit display/layout pass.

Calling displayIfNeeded() re-entrantly during that pass caused AppKit to
increment the per-window Update Constraints pass counter on every display
cycle. Once the counter exceeded the view-count limit AppKit threw an
NSGenericException and crashed:

 'The window has been marked as needing another Update Constraints in Window
 pass, but it has already had more Update Constraints in Window passes than
 there are views in the window.'

Fix: replace the direct attemptEventDrivenLayoutFollowUp() call with
scheduleLayoutFollowUpAttempt(), which defers via asyncAfter(.now() + 0).
When layoutFollowUpStalledAttemptCount == 0 the backoff delay is zero, so
there is no meaningful latency increase — the flush simply runs at the start
of the next run loop iteration, after the current layout pass has fully
unwound. The NSWindow.didUpdateNotification observer and the existing timeout
still drive retries, so convergence is unaffected.

Made-with: Bunny

* fix(workspace): supersede stale layout follow-up retry on reset

scheduleLayoutFollowUpAttempt() is a no-op when
layoutFollowUpAttemptScheduled is true, so a pending retry with a
long backoff delay would survive a beginEventDrivenLayoutFollowUp()
call even though that call resets layoutFollowUpStalledAttemptCount
to 0. The stale closure would then fire after its original delay
rather than immediately.

Adds a layoutFollowUpAttemptVersion counter. beginEventDrivenLayoutFollowUp()
increments the version and clears layoutFollowUpAttemptScheduled,
allowing a fresh asyncAfter(0) attempt to be enqueued. Pending
closures capture the version at scheduling time and exit early if it
no longer matches. clearLayoutFollowUp() also increments the version
to cancel any in-flight closure during teardown.

Made-with: Bunny
2026-03-28 13:21:02 -07:00
Austin Wang
e4aeed8dc9
Prepare 0.63.0 release (#2297) 2026-03-28 09:24:18 -07:00
Austin Wang
f049195556
Build universal binary (arm64 + x86_64) in stable release workflow (#2287)
Match the nightly workflow's universal build approach so stable releases
support both Apple Silicon and Intel Macs. Adds -destination, ARCHS,
ONLY_ACTIVE_ARCH=NO flags and a post-build architecture verification step.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 09:03:58 -07:00
Lawrence Chen
550d98ca4f
Add Match Terminal Background sidebar setting (#2293)
* Add "Match Terminal Background" sidebar setting

Adds a toggle in Settings > Sidebar Appearance that makes the sidebar
use the same background color and transparency as the terminal area.

Uses layer-level opacity on a fully opaque background color (the same
technique as TitlebarLayerBackground) with effective opacity formula
`1 - (1-alpha)^2` to account for the terminal's two stacked
semi-transparent layers (Bonsplit chrome + Ghostty Metal surface).

Also adds a 1px trailing border derived from the terminal chrome color,
matching the bonsplit tab bar separator logic.

* Fix sidebar border color not updating on theme change

Add @State + .onReceive(.ghosttyDefaultBackgroundDidChange) to
SidebarTrailingBorder so the separator color recomputes when the
Ghostty theme changes, matching the pattern used in SidebarBackdrop.

* Address review comments: localize debug toggle, fix separator refresh

- Localize the debug panel toggle label (Codex P1)
- Add .onAppear to SidebarTrailingBorder for initial color (Cubic P2)
- Fix stale doc comment on SidebarTerminalBackgroundView (Cubic P3)

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-28 04:36:20 -07:00
Lawrence Chen
e9afc22353
Skip quit confirmation for tagged DEV builds (#2288)
* Skip quit confirmation for tagged DEV builds

Tagged DEV builds are ephemeral dev iterations, so the "Quit cmux?"
dialog just adds friction. Check SocketControlSettings.launchTag() in
both applicationShouldTerminate and handleQuitShortcutWarning to bypass
the confirmation when a tag is present.

Closes https://github.com/manaflow-ai/cmux/issues/2286

* Use bundle ID instead of env var for tagged DEV detection

CMUX_TAG env var is only set when reload.sh --launch opens the app.
When the user cmd-clicks the app path, it launches via Finder without
the env var, so launchTag() returns nil and the quit dialog still shows.

Switch to checking the bundle identifier (com.cmuxterm.app.debug.<tag>)
which is baked into the built app and available regardless of how it was
launched. Add SocketControlSettings.isTaggedDevBuild() helper.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-28 03:57:25 -07:00
Austin Wang
c4bc18d906
Fix ARC workspace inheritance crash and native Zig helper builds (#2283)
* Fix ARC workspace inheritance crash and native Zig helper builds

* Fix Nightly Cmd+N workspace creation crash

* Restore safe terminal config snapshots for Intel Nightly
2026-03-28 03:05:00 -07:00
Austin Wang
f0c3ccc314
Fix cmux omo bootstrap with yanked deps (#2280) 2026-03-28 01:08:35 -07:00
Austin Wang
97fee253b5
fix: honor CJK-capable font-family before fallback injection (#2241) 2026-03-27 23:45:30 -07:00
Lawrence Chen
27fa3873be
Add claude-teams, omo, and __tmux-compat to Go relay CLI for SSH sessions (#2238)
* Add claude-teams, omo, and __tmux-compat to Go relay CLI

These commands previously only existed in the Swift CLI which uses Unix
domain sockets and can't connect over TCP relay. The Go relay CLI already
handles TCP connections, so adding the commands here makes them work
inside `cmux ssh` sessions.

- `cmux claude-teams`: creates tmux shim scripts, configures environment
  (fake TMUX/TMUX_PANE, socket path, workspace/surface IDs), and execs
  into `claude --teammate-mode auto`
- `cmux omo`: same pattern for OpenCode with terminal-notifier shim
- `cmux __tmux-compat`: translates tmux commands (split-window,
  send-keys, capture-pane, display-message, list-panes, etc.) into cmux
  JSON-RPC calls over the relay socket. Includes main-vertical layout
  tracking, wait-for signaling, and format string rendering.

* Fix: search original PATH before environment modification

findExecutable was called after configureAgentEnvironment prepended the
shim directory to PATH. The Swift CLI searches the original PATH before
modification. Renamed to findExecutableInPath with explicit PATH arg and
moved the search before configureAgentEnvironment.

* Fix cmux omo hang and port oh-my-opencode plugin setup

Root cause: socketRoundTripV2 had no read timeout. When connecting to a
stale relay port (accepted TCP but never responded), the read blocked
forever. This caused getFocusedContext to hang, blocking agent launch.

Fixes:
- Add 15s read deadline to socketRoundTripV2 (affects all v2 RPC calls)
- Add 5s timeout to getFocusedContext so agent launch proceeds even if
  system.identify is slow
- Port omoEnsurePlugin from Swift: creates shadow config dir, adds
  oh-my-opencode to plugin list, symlinks node_modules/package.json,
  installs plugin via bun/npm if missing, configures tmux settings
  (enabled=true, lower min widths), sets OPENCODE_CONFIG_DIR

* Fix: use bun as runtime for node-script opencode when node is missing

opencode is installed via bun as a #!/usr/bin/env node script, but on
some systems (like the macmini) bun is installed without a standalone
node binary. Detect node scripts and fall back to bun as the runtime
since bun is node-compatible.

* Fix subagent pane theme: preserve COLORTERM, keep TERM_PROGRAM

The cmux ssh bootstrap exports COLORTERM=truecolor and TERM_PROGRAM=ghostty.
Our configureAgentEnvironment was unsetting TERM_PROGRAM and not setting
COLORTERM, causing subagent panes (created via split-window) to lose
truecolor detection and render with wrong theme colors.

* Restore TERM_PROGRAM unset, keep COLORTERM=truecolor

* Force dark colorScheme in opencode shadow config for SSH

* Remove hardcoded dark colorScheme, let opencode detect naturally

* Detect system color scheme for opencode over SSH

* Remove color scheme detection workaround, let opencode handle natively

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-27 21:04:24 -07:00
Jun
2d51c14ba1
perf: coalesce scrollbar updates during bulk output (#2116)
* perf: coalesce high-frequency scrollbar updates to reduce main-thread pressure

During bulk terminal output (e.g. `seq 1 100000`), GHOSTTY_ACTION_SCROLLBAR
fires thousands of times per second.  Previously each callback enqueued a
separate DispatchQueue.main.async block that updated the scrollbar property
and posted a NotificationCenter notification, causing the main thread to
process thousands of redundant scroll-geometry recalculations.

This change adds a lightweight coalescing layer: the action callback stores
the latest scrollbar value behind an NSLock and schedules at most one async
flush.  The flush picks up whichever value is current at execution time,
collapsing N callbacks into a single synchronizeScrollView() pass.

Measured improvement on `time seq 1 10000`:
- Before: ~0.052s (26% CPU — seq blocked on PTY backpressure)
- After:  expected ~0.025-0.030s (reduced main-thread contention)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: use defer for NSLock release in scrollbar coalescing

Address review feedback: wrap unlock() in defer blocks in both
enqueueScrollbarUpdate and flushPendingScrollbar to guarantee
lock release on any future early-return or exception path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* perf: coalesce wakeup→tick dispatches to eliminate main-thread queue flooding

During bulk terminal output, Ghostty's I/O thread fires wakeup_cb thousands
of times per second.  Previously each wakeup enqueued a separate
DispatchQueue.main.async { tick() } block, flooding the main queue and
starving the run loop.  The main thread spent all its time draining tick
blocks, creating PTY backpressure that blocked the writing process.

Add a lightweight coalescing gate: scheduleTick() only enqueues a single
async block; subsequent wakeups while the block is pending are no-ops.
The pending tick picks up all accumulated state in one ghostty_app_tick()
call, collapsing N wakeups into 1 main-thread dispatch.

Combined with the earlier scrollbar coalescing, measured improvement:

  time seq 1 10000:
  - Ghostty standalone: 0.019s (82% CPU)
  - cmux before:        0.052s (26% CPU)  ← main-thread saturated
  - cmux after:         0.016s (62% CPU)  ← faster than standalone

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: release scrollbar lock before posting notification

Move NotificationCenter.post outside the _scrollbarLock critical section
in flushPendingScrollbar(). Holding the lock through observer dispatch
would block the I/O thread's enqueueScrollbarUpdate() calls behind
main-thread observer work, recreating the backpressure this change
aims to eliminate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:55:27 -07:00
Lawrence Chen
c51f0f15c4
Fix minimal mode toggle not updating titlebar state (#2218)
* Fix minimal mode toggle not properly updating titlebar state

UpdateTitlebarAccessoryController only re-evaluated titlebar accessories
on window focus events (didBecomeKey/didBecomeMain), not when the
presentation mode actually changed. This caused:

1. Switching to minimal: accessories weren't immediately removed
2. Switching back to standard: accessories were never re-attached
   (removeAccessoryIfPresent cleared attachedWindows, but no event
   triggered re-attachment)
3. Repeated toggling left the window in inconsistent states

Add a UserDefaults observer that detects presentation mode changes and
re-evaluates all windows. When switching to minimal, accessories are
removed; when switching to standard, fresh accessories are created and
attached. Also handle the fullscreen edge case where re-attached
accessories must be hidden to avoid doubling with SwiftUI overlay
controls.

* Hide window toolbar in minimal mode to eliminate titlebar gap

The NSToolbar (attached by WindowToolbarController) creates a non-zero
titlebar area even in minimal mode, leaving an empty gap above the
Bonsplit tab bar. In minimal mode there's no need for the toolbar (it
shows a "Cmd:" text that's hidden anyway with titleVisibility=.hidden).

Hide the toolbar when switching to minimal mode and restore it when
switching back to standard. Also set initial visibility on attachment
based on the current mode.

* Apply ignoresSafeArea to contentAndSidebarLayout in minimal mode

The titlebar gap persisted because intermediate SwiftUI views still
respected the window's safe area even though MainWindowHostingView
zeroes safeAreaInsets. Apply .ignoresSafeArea(.container, edges: .top)
directly to the contentAndSidebarLayout when in minimal mode so the
entire content (sidebar + terminal) extends into the titlebar area.

* Remove toolbar entirely in minimal mode instead of just hiding

toolbar.isVisible=false still reserves titlebar space. Remove the
toolbar entirely (window.toolbar=nil) when switching to minimal mode
and re-attach it when switching back to standard. Skip toolbar
attachment entirely when launching in minimal mode.

* Add BonsplitTabDragUITests.swift to cmuxUITests target

The test file existed on disk but was missing from the Xcode project,
causing all BonsplitTabDrag UI tests (including minimal mode tests) to
silently report 0 tests on CI.

* Use negative titlebar padding in minimal mode to extend content into titlebar

The native titlebar area (28.5pt for traffic lights) persists even
without a toolbar and despite .ignoresSafeArea() modifiers. Use
negative padding (-titlebarPadding) in minimal mode to pull the
terminal content up into the titlebar area. The sidebar's internal
trafficLightPadding spacer keeps sidebar content properly offset
below the traffic lights.

* Add window drag handle to Bonsplit top strip in minimal mode

In minimal mode, the custom titlebar (which provides the window drag
handle) is hidden. Add a WindowDragHandleView to the top strip overlay
so users can drag-to-move the window from the Bonsplit tab bar area.
The TitlebarDoubleClickMonitorView is kept as a background for
double-click-to-zoom.

* Use native titlebar drag in minimal mode instead of WindowDragHandleView

WindowDragHandleView defers to interactive siblings (Bonsplit tab bar),
so it never captures hits. Instead, set window.isMovable=true in
minimal mode so the native titlebar area handles drag-to-move and
double-click-to-zoom. Remove the non-functional overlay from
WorkspaceContentView.

* Enable isMovableByWindowBackground in minimal mode for window dragging

window.isMovable alone doesn't work because the Bonsplit tab bar
captures all hits before the native titlebar drag engages. Use
isMovableByWindowBackground=true so any area that doesn't handle
mouse events becomes a drag handle. Also capture
workspacePresentationMode in the WindowAccessor closure so the
window properties update when toggling modes.

* Add debug logging for minimal mode window drag diagnosis

* Intercept double-click in minimal mode tab bar to zoom instead of new tab

Bonsplit's EmptyTabBarDoubleClickMonitorView creates a new tab on
double-click in the tab bar empty space. In minimal mode, intercept
these double-clicks with a higher-priority local event monitor and
perform the standard macOS titlebar action (zoom/minimize based on
System Settings) instead. Only intercepts in the top 30pt strip and
only when minimal mode is active.

* Fix double-click monitor ordering and coordinate calculation

NSEvent local monitors are called LIFO (last installed first). Install
the minimal-mode double-click interceptor with a 0.5s delay so it's
added after Bonsplit's EmptyTabBarDoubleClickMonitorView monitors,
ensuring it runs first and can consume the event. Also fix the
distance-from-top calculation to use window frame height instead of
contentLayoutRect height, since the tab bar is in the titlebar area.

* Remove unnecessary delay from double-click monitor installation

* Show split buttons on hover only in minimal mode, fix sidebar controls re-attachment

Two fixes:
1. Add splitButtonsOnHover to BonsplitConfiguration.Appearance. In
   minimal mode, the Bonsplit split buttons (terminal, browser, split
   right/down) fade in only when hovering the tab bar. Revert to
   always-visible when switching back to standard mode.
2. Delay titlebar accessory re-attachment when switching to standard
   mode so the toolbar is re-added first. Without this, the accessory
   attaches before the toolbar exists, causing the sidebar controls
   to not appear in the titlebar.

* Fix splitButtonsOnHover via onChange instead of body eval, add debug logs

* Update bonsplit submodule for splitButtonsOnHover

* Remove debug logs, verified splitButtonsOnHover and accessory re-attachment on macmini

* Read presentationMode directly in TabBarView via @AppStorage

The @Observable configuration propagation wasn't reliably triggering
re-renders in TabBarView. Read the workspacePresentationMode directly
via @AppStorage in TabBarView instead, which SwiftUI reactively
updates when UserDefaults changes. Remove the syncSplitButtonsOnHover
workaround from WorkspaceContentView.

* Fix tab drag, double-click zone, and sidebar controls re-attachment

- Revert isMovableByWindowBackground to false; it breaks Bonsplit tab
  reordering. Keep isMovable=true in minimal mode so the sidebar area
  (which has WindowDragHandleView) is draggable.
- Increase double-click intercept zone from 30pt to 40pt to cover the
  full tab bar height (33pt).
- Use asyncAfter(0.1s) for titlebar accessory re-attachment when
  switching to standard mode, giving the toolbar time to re-attach.

* Add debug logging for titlebar accessory re-attachment diagnosis

* Fix crash and sidebar controls re-attachment

Remove debug logging that crashed when accessing window properties
during iteration. Increase deferred re-attachment delay to 0.3s to
give the WindowAccessor callback time to set the window identifier
and toolbar before attachIfNeeded checks isMainTerminalWindow.

* Keep titlebar accessories attached in minimal mode instead of removing

The remove/re-add cycle was fragile: re-attachment depended on window
identifiers being set, toolbar being re-added, and timing delays.
Instead, keep TitlebarControlsAccessoryViewController always attached
and let its own UserDefaults observer handle visibility. It already
hides itself (view.isHidden=true, preferredContentSize=.zero) in
minimal mode and shows itself in standard mode. No timing hacks needed.

* Force titlebar accessory layout after toolbar re-addition

* Use both self.isHidden and view.alphaValue/isHidden for accessory visibility

self.isHidden alone doesn't reliably hide the accessory when the
toolbar is nil on macOS 26. Add view.alphaValue=0 and view.isHidden
as visual fallbacks. Crucially, don't zero preferredContentSize or
frames so fittingSize returns valid values when switching back.

* Set window.isMovable=false always to fix sidebar button clicks

window.isMovable=true in minimal mode blocks clicks on the sidebar
controls because the native titlebar drag intercepts mouse events in
the overlapping area. The sidebar's WindowDragHandleView already
handles drag-to-move via performDrag with withTemporaryWindowMovableEnabled,
so native isMovable isn't needed.

* Add drag-to-move from empty bonsplit tab bar space in minimal mode

* Use overlay for tab bar drag, smart hitTest passes through tabs/buttons

* Add double-click zoom/minimize to tab bar drag view

* Add leading padding for traffic lights when sidebar collapsed in minimal mode

* Add traffic light inset to tab bar when sidebar collapsed in minimal mode

* Fix accessory space and double-click in minimal mode

- Zero preferredContentSize in minimal mode (so accessory takes no
  space) but seed hostingView with cached size before querying
  fittingSize when switching back (so size can be restored).
- Skip EmptyTabBarDoubleClickMonitorView in minimal mode so
  DraggableTabBarView handles double-click for zoom instead.
- Remove redundant ContentView double-click monitor.

* Auto-detect traffic light inset in TabBarView via GeometryReader

Instead of propagating sidebar state through config, the tab bar
detects its own position relative to the window. If in minimal mode
and the tab bar's leading edge is near the window edge (< 20pt, no
sidebar), add 72pt spacer for traffic light clearance.

* Increase traffic light spacer to 80pt

* Fix tab click passthrough in minimal mode drag overlay

* Check full window for interactive hits in drag overlay

* Fix drag overlay capturing all clicks via reentrancy guard in hitTest

* Distinguish interactive controls from hosting views in drag hitTest

* Walk ancestor chain for button detection in drag overlay hitTest

* Replace overlay with background drag view per ensemble recommendation

* Only add traffic light inset for top-left pane

* Use GeometryReader for traffic light inset, check screen position

* Fix operator precedence in traffic light inset check

* Use window frame for traffic light inset detection

* Set tabBarLeadingInset from ContentView via onChange handlers

Replace unreliable coordinate-based detection with direct state from
ContentView, which knows both sidebar visibility and minimal mode.
Syncs on appear, sidebar toggle, and mode toggle.

* Use allPaneIds.first for top-left pane detection, no hierarchy threading needed

* Update bonsplit submodule to merged main

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-27 20:25:51 -07:00
Yinbo Wang
609a02c3f9
Add customizable sidebar selection highlight color (#1824)
* Add customizable sidebar selection highlight color

Expose a `sidebarSelectionColorHex` user default that overrides the
hardcoded blue (#0091FF) selection highlight in the sidebar. Add a
"Selection Highlight" color picker in Settings > Workspace Colors,
following the same pattern as existing tint color pickers. Falls back
to the default accent color when no custom color is set.

Closes #1753

* Fix review feedback: reactivity, reset button, localization

- Add @AppStorage subscription in TabItemView so sidebar selection
  color updates reactively when changed in Settings
- Add Reset button in Settings > Workspace Colors > Selection Highlight
- Localize debug panel strings for Selection Color picker
- Clear sidebarSelectionColorHex in resetAllSettings()

* Add customizable notification badge color in sidebar

Add `sidebarNotificationBadgeColorHex` user default to override the
unread notification badge color on workspace tabs. Add a "Notification
Badge" color picker in Settings > Workspace Colors, following the same
pattern as the selection highlight picker. Falls back to the default
accent color when no custom color is set.
2026-03-27 20:18:36 -07:00
Austin Wang
63904811f9
Revert "Fix Intel second-surface config inheritance crash (#2179)" (#2267)
This reverts commit 1f4fc476ab.
2026-03-27 20:07:12 -07:00
Austin Wang
1f4fc476ab
Fix Intel second-surface config inheritance crash (#2179) 2026-03-26 23:41:21 -07:00
Serhii Koval
c0ec7dc13f
feat(i18n): add Ukrainian (uk) localization (#2226)
* feat: add Ukrainian (uk) website translation

Translate all 681 keys from en.json to uk.json for the cmux website.
Preserves all placeholders and HTML-like tags. Testimonials kept in original language.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: register Ukrainian locale in web i18n routing config

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add Ukrainian translations to Localizable.xcstrings (916 keys)

Translated all 916 string keys from English to natural Ukrainian.
All format specifiers (%@, %lld, %1$@, etc.) preserved.
JSON syntax validated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add Ukrainian README translation (README.uk.md)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add Ukrainian language link to all README language selectors

Added Українська link to the language selector paragraph in all 20 existing
README files (README.md and README.*.md), pointing to README.uk.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix truncated Ukrainian translations for cli.claude-teams.usage and cli.omo.usage

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 21:27:36 -07:00
Daegun Choi 
bcf2180828
docs: Add localized assets for Korean README (#1811) 2026-03-26 21:24:36 -07:00
Lawrence Chen
9811ea34b5
Switch release builds to macOS Tahoe runner (#2233)
* Switch release builds to macOS Tahoe runner

Use warp-macos-26-arm64-6x for release builds to match nightly. Also add
a dry-run artifact upload path for workflow_dispatch so the release
pipeline can be tested without creating a real release.

* Gate release upload on event_name, not just ref prefix

Prevents workflow_dispatch triggered on a tag ref from accidentally
uploading real release assets.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 21:09:59 -07:00
Lawrence Chen
5c7cafeee2
Switch nightly builds to macOS Tahoe runner (#2231)
Use warp-macos-26-arm64-6x for nightly builds to get the latest Xcode
toolchain. Deployment target stays at 14.0 (Sonoma), so built binaries
remain compatible with Sonoma, Sequoia, and Tahoe.

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 20:43:21 -07:00
Lawrence Chen
6e5903e4e9
Improve cmux omo error when opencode is not installed (#2230)
* Improve cmux omo error when opencode is not installed

On a fresh computer without opencode, cmux omo would fail with the
unhelpful "Failed to launch opencode: No such file or directory".
Now checks for opencode before doing setup work and shows install
instructions: npm install -g opencode-ai

* Show npm install progress and improve first-run experience

On first run, npm install oh-my-opencode can take 30+ seconds.
Previously piped to /dev/null, making it look like cmux omo hung.
Now pipes npm output directly to stderr so users see progress.
Also improves the message: "this may take a minute on first run".

* Move opencode check before plugin setup to fail fast

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 19:28:22 -07:00
Lawrence Chen
30cb3718fc
Add --no-focus flag to cmux ssh (#2227)
* CLI: add --no-focus flag to cmux ssh

When cmux ssh is used from a script, workspace.select is called
immediately after workspace.remote.configure, stealing the user's
active workspace focus before SSH is established.

Add --no-focus flag (consistent with break-pane/join-pane) to skip
the workspace.select call so the caller's workspace retains focus.
The caller can then redirect to the new workspace later via
cmux select-workspace.

Addresses cmux ssh case of #140; complementary to #1418.

* CLI: add test coverage for --no-focus flag parsing

- Fix existing test that constructs SSHCommandOptions directly
  (add noFocus: false to the initializer call)
- Make parseSSHCommandOptions internal so it's accessible from tests
- Add testParseSSHCommandOptionsNoFocusFlag covering:
  - flag sets noFocus=true, destination parses correctly
  - absent flag defaults noFocus=false
  - combined with --name and --port

* Update CLI/cmux.swift

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Add --no-focus to top-level ssh help synopsis

---------

Co-authored-by: Łukasz Majcher <lukasz.majcher@samsara.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 19:05:40 -07:00
Lawrence Chen
1826cb5698
Fix tmux compat store decoding, layout cleanup, and cross-workspace fallback (#2207)
* Fix tmux compat store decoding, layout cleanup, and cross-workspace fallback

Three hardening fixes for the tmux compatibility layer:

1. Add custom init(from:) to TmuxCompatStore using decodeIfPresent so
   older store files missing mainVerticalLayouts/lastSplitSurface keys
   decode gracefully instead of silently resetting to an empty store.

2. Clear mainVerticalLayouts entry when a non-main-vertical layout is
   selected, preventing stale state from redirecting future splits.

3. Only enter caller-anchoring block in split-window when
   resolveWorkspaceId succeeds, avoiding cross-workspace splits when
   the fallback target.workspaceId differs from the caller's workspace.

* Add auto-equalize after teammate splits

After each teammate split-window, call workspace.equalize_splits with
orientation: "vertical" to evenly distribute panes in the agent column
without affecting the leader/column horizontal divider.

Uses the equalize_splits socket method from the omo integration
(PR #2087). The equalization works synchronously because bonsplit's
setDividerPosition sets ratios on the internal split state directly,
no layout flush needed.

* Refactor: DRY up claude-teams and omo shared launcher code

Extract shared functions from the nearly-identical claude-teams and omo
integration code:

- configureTmuxCompatEnvironment: parameterized env setup (tmux path
  prefix, bin env var, term override, extra vars)
- createTmuxCompatShimDirectory: shared tmux shim creation with
  writeShimIfChanged
- resolveExecutableInSearchPath: generic PATH search with optional
  skip predicate
- Rename ClaudeTeamsFocusedContext -> TmuxCompatFocusedContext,
  claudeTeamsFocusedContext -> tmuxCompatFocusedContext,
  claudeTeamsResolvedSocketPath -> tmuxCompatResolvedSocketPath

Both integrations are now thin wrappers over the shared functions.
No behavior changes.

* Address PR review comments: store load, stale state, pane targets

1. Move loadTmuxCompatStore() inside the caller-anchoring if-let so
   it's only called when env vars are present (avoids unnecessary
   file I/O on non-agent splits).

2. Clear lastSplitSurface alongside mainVerticalLayouts when switching
   away from main-vertical layout, preventing stale seed values on
   re-entry.

3. Resolve select-layout -t via tmuxResolvePaneTarget first (tmux
   accepts pane targets like %1), falling back to workspace target.
   Removes duplicate workspace resolution in the else branch.

* Fix select-layout -t fallback: don't apply to wrong workspace

When an explicit -t target fails to resolve, error instead of silently
falling back to the caller's current workspace. Only use the current
workspace as default when no -t was provided.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 18:54:18 -07:00
Achieve
71f0e69578
fix(socket): return not_found error when surface_id is provided but unresolvable (#2150)
Previously, v2SurfaceSendText, v2SurfaceSendKey, v2SurfaceClearHistory, and
v2SurfaceReadText would silently fall back to ws.focusedPanelId when a caller
supplied a surface_id that could not be resolved (e.g. a stale ref or an ordinal
whose mapping had not yet been registered). This caused two distinct bugs:

- #2042: Commands like `cmux send --surface surface:9999` would succeed (exit 0)
  and deliver input to the focused pane instead of returning an error, making
  automation that targets specific surfaces unreliable.

- #2045: When the fallback landed on a browser panel, the subsequent
  ws.terminalPanel(for:) check failed and returned "Surface is not a terminal",
  making valid terminal surfaces appear broken when addressed by ref.

The fix adds an explicit check: if params["surface_id"] is present but
v2UUID() returns nil (resolution failure), we immediately return a not_found
error instead of falling back to the focused pane. When surface_id is absent,
the existing focused-pane fallback is preserved for backward compatibility.

Fixes #2042, Fixes #2045

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:45:21 -07:00
Achieve
f3c797ee44
Support modifier+key combinations in send-key (ctrl+enter, shift+tab, etc.) (#1994)
* Support modifier+key combinations in send-key (ctrl+enter, shift+tab, etc.)

The send-key command only supported a few hardcoded ctrl+letter combos
and bare special keys. Generic modifier combinations like ctrl+enter
(needed for GitHub Copilot CLI submission) returned "Unknown key".

Now the parser splits on + and - separators, accumulates modifier flags
(ctrl, shift, alt/opt, cmd/super), and resolves the base key via a new
keycodeForNamedKey helper that maps named keys (enter, tab, escape,
backspace, space, arrow keys) to virtual keycodes.

Closes #1990

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix delete key mapping and filter empty parts in modifier parser

- Separate "delete" (forward delete, kVK_ForwardDelete) from "backspace"
  (kVK_Delete) in keycodeForNamedKey. The original mapping had both
  pointing to kVK_Delete (which is actually Backspace on macOS).
- Filter empty strings from split results to reject malformed inputs
  like "+shift+a" that would produce an empty modifier part.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix single named keys silently failing and remove force unwrap in send-key

Single named keys like "space", "up", "delete" were rejected by
guard parts.count >= 2 before reaching keycodeForNamedKey(). Now
standalone named keys are handled before the modifier-combo path.
Also replaced parts.last! with guard let for safe unwrapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:45:09 -07:00
Lawrence Chen
ccd84bd578
Fix nightly SSH remote daemon checksum mismatch (#2225)
* Fix nightly SSH remote daemon checksum mismatch

Each nightly build overwrites the shared cmuxd-remote-* assets on the
nightly release, but older nightly DMGs have manifests with checksums
from their build time. When a user's nightly is even one build behind,
the downloaded binary doesn't match their embedded manifest.

Two-layer fix:

1. CI: version nightly remote daemon asset names with the build number
   (e.g. cmuxd-remote-darwin-arm64-2362248028801) so each nightly's
   manifest points to immutable files. Unsuffixed "latest" copies are
   still uploaded for tooling compatibility.

2. Client: on checksum mismatch, fetch the live manifest from the
   release and verify against that. This handles users on older
   nightlies that predate the CI fix.

Fixes https://github.com/manaflow-ai/cmux/issues/1745

* Fix unsuffixed checksums file to use generic filenames

Regenerate cmuxd-remote-checksums.txt from the unsuffixed alias
binaries so `shasum -c` works against the generic asset names.
Also document that unsuffixed manifest intentionally keeps versioned
downloadURLs and that aliases don't carry attestation.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 17:24:37 -07:00
Lawrence Chen
1b03d23fee
fix(sidebar): use dedicated setting for port link browser preference (#2219)
Port links were reusing the PR-link preference
(openSidebarPullRequestLinksInCmuxBrowser), causing inconsistent
behavior when users toggled that setting. Adds a dedicated
openSidebarPortLinksInCmuxBrowser setting with its own toggle in
Settings so port and PR link behavior can be controlled independently.

Addresses review feedback from https://github.com/manaflow-ai/cmux/pull/1844

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 17:12:37 -07:00
Lawrence Chen
84af32c56e
Add cmux omo command for oh-my-openagent integration (#2087)
* Add `cmux omo` command for OpenCode + oh-my-openagent integration

Same pattern as `cmux claude-teams`: creates a tmux shim so
oh-my-openagent's TmuxSessionManager spawns agents as native cmux
splits instead of tmux panes. Sets TMUX/TMUX_PANE env vars, prepends
shim to PATH, and execs into opencode.

Closes https://github.com/manaflow-ai/cmux/issues/2085

* Auto-install oh-my-opencode plugin when running cmux omo

Before launching opencode, cmux omo now:
- Checks if oh-my-opencode is registered in ~/.config/opencode/opencode.json
- If not, creates/updates the config with the plugin entry
- Checks if the npm package is installed in node_modules
- If not, runs bun add (or npm install) to install it
- Then proceeds with tmux shim setup and exec

* Use shadow config dir to avoid modifying user's opencode setup

Instead of writing directly to ~/.config/opencode/opencode.json,
cmux omo now creates a shadow config at ~/.cmuxterm/omo-config/ that
layers oh-my-opencode on top of the user's existing config. Symlinks
node_modules, package.json, bun.lock, and plugin config from the
original dir. Sets OPENCODE_CONFIG_DIR to the shadow directory.

Running plain `opencode` remains unaffected.

* Add Agent Integrations docs section with Claude Code Teams and oh-my-opencode pages

Adds sectioned sidebar navigation to the docs site. The new Agent
Integrations section contains separate pages for cmux claude-teams and
cmux omo, documenting usage, tmux shim mechanics, directory layout,
environment variables, and the shadow config approach. Both pages
include a nightly-only warning. Full English and Japanese translations,
nav item keys added to all 19 locales.

* Remove uppercase from sidebar section headers

* Add more spacing above and below sidebar section headers

* Enable tmux mode in oh-my-opencode config, improve docs

- cmux omo now writes tmux.enabled=true to the shadow oh-my-opencode.json
  config. Without this, oh-my-openagent's TmuxSessionManager won't spawn
  visual panes even though $TMUX is set (the config defaults to false).
- Nightly warnings now link to /nightly instead of generic text.
- Added "What you get" section to oh-my-opencode docs explaining the
  visual pane behavior (auto-layout, idle cleanup, queueing).
- Added tmux.enabled step to first-run and how-it-works sections.

* Add terminal-notifier shim to route oh-my-openagent notifications to cmux

oh-my-openagent sends macOS notifications via terminal-notifier
(args: -title <t> -message <m> [-activate <id>]). The shim in
~/.cmuxterm/omo-bin/terminal-notifier intercepts these calls and
routes them through cmux notify, so notifications appear in cmux's
sidebar panel instead of as raw macOS notifications.

* Add pane geometry to tmux-compat for oh-my-openagent grid planning

oh-my-openagent's TmuxSessionManager needs pane geometry (columns,
rows, position, window dimensions) to decide where to spawn agent
panes. Without this data, agents run headlessly.

Server side:
- pane.list v2 response now includes pixel_frame, cell_size, columns,
  rows per pane, plus container_frame at the top level
- Uses BonsplitController.layoutSnapshot() for pixel geometry and
  ghostty_surface_size() for terminal grid dimensions

CLI side:
- tmuxEnrichContextWithGeometry() computes character-cell positions
  from pixel frames and cell dimensions for tmux format variables
  (pane_width, pane_height, pane_left, pane_top, pane_active,
  window_width, window_height)
- list-panes now resolves pane targets (%uuid) via tmuxResolvePaneTarget
  instead of failing with "Workspace not found"
- display-message enriched with geometry for format strings like
  #{pane_width},#{window_width}
- tmux -V now returns "tmux 3.4" (needed by oh-my-openagent's
  tmux-path-resolver verification)

* Add socket tests for tmux-compat pane geometry

6 tests verifying the geometry enrichment works end-to-end:
- pane.list returns pixel_frame, columns, rows, cell_size, container_frame
- tmux -V returns version string
- list-panes -F renders geometry format variables as integers
- list-panes -t %<uuid> resolves pane targets
- display -p renders pane_width and window_width
- After split, two panes have different positions and halved widths

All 6 pass on macmini (cmux-macmini).

* Handle tmux -V in shim script directly (no socket needed)

oh-my-openagent's tmux-path-resolver runs tmux -V to verify the binary
works. The __tmux-compat handler requires a socket connection, which
may not be established at verification time. Handle -V in the bash
shim directly to avoid the socket dependency.

* Lower default tmux pane min widths for cmux omo

oh-my-openagent defaults: main_pane_min_width=120, agent_pane_min_width=40,
requiring 161+ columns. Most terminal windows are narrower, causing
decideSpawnActions to return canSpawn=false and defer agents forever.

cmux omo now sets: main_pane_min_width=60, agent_pane_min_width=30,
main_pane_size=50, requiring only 91 columns. Also moved tmux -V
handling into the bash shim to avoid needing a socket connection for
the version check.

* Resolve merge conflicts with main (main-vertical layout, focus param)

- Keep upstream main-vertical layout anchoring from #2119
- Keep upstream focus param (v2Bool) instead of no_focus
- Combine with our -d flag handling: -d sets focus=false
- Include customCommands nav item from main

* Implement select-layout equalize and resize-pane absolute width

When oh-my-openagent spawns agent panes, it calls select-layout
main-vertical after each split to redistribute panes evenly, then
resize-pane -x <columns> to set the main pane width. Both were
previously no-ops, causing cascading uneven splits.

Server side:
- Add workspace.equalize_splits v2 API that calls the existing
  TabManager.equalizeSplits (sets all dividers to 0.5)

CLI side:
- select-layout now calls workspace.equalize_splits before tracking
  main-vertical state
- resize-pane -x <columns> without directional flags now computes
  the pixel delta from current to desired width and resizes accordingly

* Fix equalize to use proportional divider positions

The previous equalize set all dividers to 0.5, which in a right-
recursive binary tree (from successive splits) gives 50/25/12.5/6.25%
instead of equal sizes.

New algorithm counts leaf panes on each side of each split and sets
the divider to N_left / (N_left + N_right). For 5 panes in a chain:
1/5, 1/4, 1/3, 1/2, giving each pane exactly 20%.

* Fix select-layout main-vertical to only equalize vertical splits

The proportional equalize was treating the top-level horizontal split
(main vs agent column) the same as vertical splits, setting the main
pane to 1/6 of the window with 5 agents.

For main-vertical layout, only equalize vertical splits (the agent
column), leaving the horizontal main/agent divider untouched. The
subsequent resize-pane -x handles the main pane width.

workspace.equalize_splits now accepts an optional orientation filter
("vertical" or "horizontal") to scope which splits get equalized.

* Re-equalize agent column after kill-pane

* Address PR review comments

- Fix cmux omo --help: remove omo from the help-bypass guard so
  --help shows usage text instead of trying to launch opencode
- Don't overwrite unreadable opencode.json: fail with an error
  instead of silently resetting to empty config
- Drain installer pipes concurrently before waitUntilExit to
  prevent deadlock from full pipe buffers during bun/npm install

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 16:07:59 -07:00
Lawrence Chen
ee3460b8d7
docs: add Copilot CLI hooks section to i18n notifications page (#2217)
Follow-up to #1875 which added Copilot CLI hook instructions to
docs/notifications.md but not the live docs site. Adds the section
to page.tsx with translation keys for all 19 locales.

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 16:03:07 -07:00
Austin Wang
32124d9256
Revert "Merge pull request #1915 from elvistranhere/fix/split-crash-intel-1870" (#2221)
This reverts commit c5b306655d, reversing
changes made to 100612d96e.
2026-03-26 16:01:53 -07:00
Lawrence Chen
bc9e45c897
Fix workspace creation snapshot crash (#2176)
* Add workspace creation config regression test

* Sanitize workspace creation config snapshot

* Remove duplicate WorkspaceCreationConfigSanitizationTests class

Main already has this test class; the branch's version was a duplicate
that would cause a compilation error after merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 15:53:21 -07:00
Matt Van Horn
819ceb8ebb
feat(sidebar): make listening ports clickable to open in browser (#1844)
* feat(sidebar): make listening ports clickable to open in browser

Wrap each sidebar port in a Button that opens http://localhost:{port}
in the cmux built-in browser (or system browser as fallback), matching
the existing PR link click behavior.

Fixes #1602

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address bot review feedback on port clickability

- Localize port label text with String(localized:) instead of bare literal
- Add sidebar.port.label and sidebar.port.openTooltip keys to
  Localizable.xcstrings with English and Japanese translations
- Respect openSidebarPullRequestLinksInCmuxBrowser user preference in
  openPortLink, matching the openPullRequestLink pattern exactly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 15:49:38 -07:00
Harley Adams
9d8e6a8d05
docs: add Copilot CLI hook setup to notifications guide (#1875)
* docs: add Copilot CLI hook setup to notifications guide

Add GitHub Copilot CLI section with hooks for notifications and
sidebar status pills (userPromptSubmitted, agentStop, errorOccurred,
sessionEnd). Includes link to official GitHub hooks documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: address PR feedback for Copilot CLI hooks

- Use if/else instead of &&/|| to avoid false-positive osascript fallback
- Fix jq path: .error.message → .errorMessage for Copilot CLI schema
- Remove --icon/--color flags (let cmux handle styling)
- Add clear-notifications side-effect note
- Add concrete repo-level config example
- Document set-status/clear-status in CLI Commands section

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Harley Adams <harley.adams@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-26 15:49:36 -07:00
Mikhail Andreev
cfe6cf89d8
fix: keyboard shortcuts not working with Russian layout (#2202)
* fix: keyboard shortcuts not working with Russian and other non-Latin layouts

When a non-Latin input source (Russian, etc.) is active, event characters
are non-ASCII. The ANSI keyCode fallback was blocked when the layout-based
translation resolved a character, leaving no safety net. Now the keyCode
fallback is always available for non-Latin layouts, matching the physical
key position — similar to Ghostty's `physical:` keybinding behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add unit tests for Russian keyboard layout shortcut matching

Two tests for Cmd+T with non-Latin (Russian) keyboard layout:
1. Layout provider returns "t" (normal ASCII fallback) — verifies
   the layout-based matching path works with Cyrillic event chars.
2. Layout provider returns nil (translation failure) — verifies the
   ANSI keyCode fallback catches the shortcut by physical key position.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: scope ANSI keyCode fallback to non-ASCII events, add Russian to comments

Address review feedback:
- Split !hasUsableEventChars into two precise conditions:
  (hasEventChars && !eventCharsAreASCII) for non-Latin layouts, and
  (!hasEventChars && layoutCharacter empty) for synthetic/empty-char events.
  This prevents unintended keyCode fallback on Dvorak/Colemak with empty
  synthetic events.
- Add "Russian" to the non-Latin layout list in the guard comment at line 10626.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:25:36 -07:00
Usman Ehtesham Gul
66b0260442
Add --name flag to new-workspace CLI command (#2160)
Allow naming a workspace at creation time instead of requiring a
separate rename-workspace call afterward.

Threads a title parameter through:
- CLI: --name <title> flag parsed and sent as "title" in v2 params
- V2 handler: extracts title, passes to TabManager.addWorkspace()
- TabManager: uses provided title instead of auto-generated
  "Terminal N" and calls setCustomTitle() to persist it
- V1 handler: accepts optional name argument

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 15:18:20 -07:00
Achieve
0978732c93
fix: route PWD action to correct TabManager per tabId (#2147)
Previously, GHOSTTY_ACTION_PWD used AppDelegate.shared?.tabManager to
update the current working directory, which only returns the key window's
TabManager. In multi-window scenarios, cwd updates from non-key windows
were written to the wrong TabManager, causing panelDirectories to be
empty at save time and falling back to $HOME on restore.

Fix by using tabManagerFor(tabId:) to precisely route the update to
whichever window owns the tab, ensuring all windows persist their
working directories correctly.

Fixes #2125
2026-03-26 15:16:12 -07:00
Austin Wang
ccaebbd112
Fix workspace shortcut docs (#2211) 2026-03-26 15:13:50 -07:00
HyunJick Lee
46f560fd3e
chore: remove unused koreanRanges constant (#2158)
The Korean CJK font mapping added in PR #1017 was removed in PR #1700,
but the koreanRanges static property was left behind as dead code.

Related: #1700, #1693
2026-03-26 15:12:35 -07:00
Achieve
6a41c9f42f
fix(quit): enforce Warn Before Quit when Cmd+Q arrives via app switcher (#2186)
applicationShouldTerminate was returning .terminateNow unconditionally,
bypassing QuitWarningSettings. Now it shows the same confirmation alert
used by handleQuitShortcutWarning, with an isQuitWarningConfirmed flag
to prevent a double dialog when the Cmd+Q shortcut path already confirmed.

Fixes #2139

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 15:12:32 -07:00
Lawrence Chen
46589f531c
Fix SEO indexing: hreflang, canonicals, sitemap, trailing slash (#2193)
* Fix SEO indexing: add hreflang, canonicals, sitemap per-locale entries

Google Search Console showed 380 not-indexed vs 86 indexed pages.
Root causes: missing hreflang tags on rendered pages (only in sitemap),
no canonical on homepage, inconsistent canonicals wiping parent hreflang,
sitemap only listing English URLs, trailing slash duplicates, and
_next/static chunks being crawled as pages.

Changes:
- Add buildAlternates() utility for consistent canonical + hreflang
- Add hreflang tags to all pages via alternates.languages in metadata
- Add self-referencing canonical URLs to every page (homepage had none)
- Expand sitemap to emit separate entries for each locale
- Add missing /docs/custom-commands to sitemap
- Remove skipTrailingSlashRedirect to normalize trailing slashes
- Block /_next/ in robots.txt to stop chunk crawling

* Add per-page alternates to docs sub-pages and blog index

Docs sub-pages and blog index only returned title/description in
generateMetadata, so they inherited the parent layout's alternates
(pointing to /docs or /blog). Now each page sets its own
buildAlternates() with the correct path so canonical and hreflang
point to the actual page URL.

* Derive openGraph.url from buildAlternates to avoid drift

* Redirect non-English legal pages to English, remove from sitemap

Legal pages (privacy policy, TOS, EULA) are untranslated English content.
Serving them under every locale creates 54 duplicate URLs. Now:
- Middleware 301-redirects /ja/privacy-policy etc. to /privacy-policy
- Sitemap only includes English URLs for legal pages (no locale variants)
- Legal page metadata uses static English-only canonical

* Fix legal page redirect to only match /<locale>/<page> paths

endsWith matched too broadly (e.g. /docs/eula). Now only redirects
when the path after the first segment is an exact legal page match.

* Skip next-intl for legal pages to prevent locale redirect loop

Without this, a Japanese user hitting /privacy-policy could be
redirected by next-intl to /ja/privacy-policy, which our middleware
redirects back to /privacy-policy, creating a loop.

* Rewrite legal pages to /en/ instead of NextResponse.next()

Pages live under app/[locale]/, so skipping next-intl entirely
would break route resolution. Rewrite to /en/privacy-policy etc.
so Next.js can resolve the [locale] segment correctly.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 15:10:39 -07:00
Lawrence Chen
f507fd8141
Fix claude-teams pane anchoring with main-vertical layout (#2119)
* Fix claude-teams pane anchoring: main-vertical layout + focus

Claude's agent teams sends `split-window -h` for each teammate then
`select-layout main-vertical` to stack them vertically. Three fixes:

1. Implement select-layout main-vertical: track layout state in the
   tmux compat store so subsequent horizontal splits of the leader
   pane get redirected to vertical splits of the right-side column.

2. Pass focus:false to surface.split from the split-window handler
   so internal bonsplit focus stays on the leader pane.

3. Fix tmuxCompatStoreURL to respect $HOME env var (was using
   NSString.expandingTildeInPath which ignores $HOME).

Closes https://github.com/manaflow-ai/cmux/issues/2118

* Fix claude-teams split routing: auto-seed main-vertical on first right split

The split-window routing was broken in two ways:

1. After the first teammate split (right), the main-vertical state wasn't
   being created, so all subsequent splits also went right instead of
   stacking down in the right column.

2. The old code only redirected splits when main-vertical was already active
   AND the target was the leader surface. Claude's teams protocol targets
   arbitrary panes from list-panes, not necessarily the leader.

Now the first teammate split goes right (creating the column), auto-seeds
the main-vertical state, and all subsequent splits stack downward. The
caller's surface (CMUX_SURFACE_ID) is used as the anchor regardless of
which pane Claude targets.

Also adds caller surface preference in pane target resolution so the
caller's exact surface is used when the target pane matches, preventing
stale selected-surface references after tab switches.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 14:36:48 -07:00
Austin Wang
fe0443fa2b
Fix Cmd+N nightly crash: avoid local Workspace refs in ARC hotpath (#2204)
* test: reproduce Cmd+N snapshot workspace lifetime race

* fix: retain snapshot workspaces through Cmd+N creation

* fix: repair workspace lifetime regression test

* fix: extract workspace config through self to avoid Xcode 16.x ARC crash

The snapshot approach (c1998e34) navigated workspace → panel → surface
through local variables. Xcode 16.4's -O ARC optimizer aggressively
elides retains on these locals through inlined call chains, causing
use-after-free on every Cmd+N in CI-built nightlies.

Fix: extract preferredWorkingDirectory and inheritedTerminalFontPoints
through self (always retained) BEFORE capturing locals. The snapshot
is now purely value-typed with no Workspace references held in locals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 14:28:32 -07:00
Austin Wang
8a378152e8
Fix Cmd+N crash: retain snapshot workspaces through creation (#2183)
* test: reproduce Cmd+N snapshot workspace lifetime race

* fix: retain snapshot workspaces through Cmd+N creation

* fix: repair workspace lifetime regression test
2026-03-25 19:43:22 -07:00
Austin Wang
61e6a0e2b9
Fix Release-only Cmd+N workspace snapshot UAF (#2181)
* test: reproduce Cmd+N snapshot workspace lifetime race

* fix: retain snapshot workspaces through Cmd+N creation

* fix: repair workspace lifetime regression test
2026-03-25 19:12:24 -07:00