* 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>
* 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>
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>
* 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>
* 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>
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>
* 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>
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>
* 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>
* 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>
* 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>
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>
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
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
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>
* 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>
* 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>
* 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>
Dev builds should use the most permissionless socket mode so coding
agents and external tools can connect without any restrictions.
Changes automation → allowAll in the LSEnvironment plist entries and
the launch-time env vars.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Fix update attempt refreshing pill without actually updating
The attemptUpdate() subscriber watched for .updateAvailable state to
auto-confirm, but showUpdateFound used setStateAfterMinimumCheckDelay
which delays the transition by up to 2 seconds. During that window,
dismissUpdateInstallation (from a background probe race) could cancel
the pending transition, reverting state to idle without ever confirming.
The subscriber then tore down on the transient idle, silently abandoning
the update.
Fix: move auto-confirm to the Sparkle driver level via an
autoInstallOnNextUpdate flag. When set, showUpdateFound immediately
calls reply(.install) bypassing the delay entirely. The subscriber
is kept as a fallback but no longer tears down on transient idle
while the flag is active.
Closes https://github.com/manaflow-ai/cmux/issues/2166
* Revert "Fix update attempt refreshing pill without actually updating"
This reverts commit 1cd842dd924bf114b096f222851c47d2e36ad4d9.
* Fix update attempt refreshing pill without actually updating
The attemptUpdate() subscriber tore down monitoring whenever it saw
.idle after observing progress. During check startup (retry loop,
background probe race), state can transiently return to .idle before
Sparkle's interactive check begins. The subscriber interpreted this
as "check completed" and stopped monitoring, so the auto-confirm
for .updateAvailable never fired.
Fix: add !state.isIdle to the teardown guard so monitoring only
stops on terminal failures (.notFound, .error), not transient idle.
Closes https://github.com/manaflow-ai/cmux/issues/2166
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* New window inherits size from current window
When creating a new window via Cmd+Shift+N, use the key window's
frame dimensions instead of the hardcoded 460x360 default. The new
window cascades from the existing window's position so it doesn't
stack directly on top.
* Use Ghostty's cascade algorithm for new window positioning
Match upstream Ghostty's window cascade logic: maintain a
lastCascadePoint that tracks where the next window should appear.
First window seeds the point from its own top-left corner, subsequent
windows advance the cascade point via NSWindow.cascadeTopLeft(from:).
On window close, reset the cascade point to the closing window's
position so the next window appears nearby.
New windows still inherit the key window's size so Cmd+Shift+N
creates a window matching the previous one's dimensions.
* Fix frame-to-contentRect conversion and use preferred window resolver
Convert existingFrame to a content rect via
NSWindow.contentRect(forFrameRect:styleMask:) so the new window
matches the source window's actual size instead of growing by
titlebar insets on each Cmd+Shift+N.
Use preferredMainWindowContextForWorkspaceCreation to resolve the
source window, consistent with showOpenFolderPanel and other
callers.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
The confirm dialog showed a sanitized command (BiDi/zero-width stripped)
but executed the raw string, creating a display/consent mismatch.
Now the command is sanitized once and the same string is used for both
display and execution.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Handle Cmd+O in handleCustomShortcut to prevent Documents folder open
Cmd+O for "Open Folder" was only handled in SwiftUI menu, which can
fail due to focus bugs when terminal is focused. This caused AppKit's
default NSDocumentController to open the Documents folder instead.
Now Cmd+O is intercepted in handleCustomShortcut like other shortcuts.
Fixes#2010
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix fallback directory loss and deduplicate Open Folder logic
Address review feedback:
1. Pass selected directory URL to fallback window creation so the
user's folder choice is not silently discarded
2. Replace inline NSOpenPanel code in cmuxApp.swift menu action
with a call to AppDelegate.showOpenFolderPanel() to avoid
future divergence between the two code paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Set NSOpenPanel directoryURL to current terminal working directory
Address review feedback: set panel.directoryURL to the focused
terminal's working directory so Open Folder starts in a contextually
relevant location instead of AppKit's default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use shared main-window resolver and openWorkspaceForExternalDirectory in showOpenFolderPanel
Address review feedback: use preferredMainWindowContextForWorkspaceCreation
for directory seeding (works when auxiliary windows are key) and
openWorkspaceForExternalDirectory for workspace creation (ensures
shouldBringToFront and consistent fallback behavior).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add set-color/clear-color workspace actions for tab color via CLI
Expose the existing tab color functionality through the workspace-action
CLI command, enabling programmatic tab color setting without the GUI
context menu.
Supports both named colors (Red, Blue, Amber, etc.) and hex values
(#RRGGBB). Named colors resolve against the built-in palette via
case-insensitive matching.
Usage:
cmux workspace-action --action set-color --color blue
cmux workspace-action --action set-color --color "#C0392B"
cmux workspace-action set-color Amber
cmux workspace-action clear-color
* Return explicit null color in clear_color JSON response
Restore "color": null in the clear_color response payload so JSON
consumers can distinguish "color was cleared" from "no color field".
---------
Co-authored-by: Ariel Tobiana <arieltobiana@gmail.com>