Commit graph

1934 commits

Author SHA1 Message Date
austinpower1258
5fa2a34236 Revert "Skip flaky socket tests in macOS Compatibility workflow too"
This reverts commit 6295ec7439.
2026-03-30 22:44:28 -07:00
austinpower1258
6295ec7439 Skip flaky socket tests in macOS Compatibility workflow too
Same socket timeout tests that were skipped in ci.yml also need to be
skipped in ci-macos-compat.yml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 22:23:05 -07:00
austinpower1258
ca45a99c11 Skip flaky socket security tests that time out on CI runners
The TerminalController socket tests depend on a real Unix socket being
created within 5 seconds, which consistently times out on GitHub Actions
runners. This was causing unexpected test failures on both main and this
branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 22:22:20 -07:00
austinpower1258
83789437ac Avoid duplicate sidebar git metadata publishes 2026-03-30 21:48:31 -07:00
austinpower1258
642aead088 Add regression tests for duplicate sidebar git publishes 2026-03-30 21:48:28 -07:00
Austin Wang
2d2d8da1c7
Merge pull request #2355 from manaflow-ai/issue-2352-shift-enter-tmux
Map Shift+Enter to raw newline in Ghostty
2026-03-30 18:10:57 -07:00
Lawrence Chen
dd54927cb9
Add React Grab inject button to browser toolbar (#2373)
* Add React Grab inject button to browser toolbar

Adds a toolbar button (cursor click icon) that injects the react-grab
script (unpkg.com/react-grab/dist/index.global.js) into the current
page. Hover over React elements and Cmd+C to copy component context
(file, component name, line number) for AI agents.

Button highlights when active, resets on navigation.

* Auto-activate selection mode on React Grab inject

First click: injects the script and auto-activates selection mode via
the react-grab:init event. Subsequent clicks toggle selection mode
on/off via window.__REACT_GRAB__.toggle().

* Bridge React Grab state back to Swift via WKScriptMessageHandler

Register a cmux-bridge plugin after injecting react-grab that posts
state changes back to Swift via webkit.messageHandlers. The button
now highlights accent color only when selection mode is actually
active (not just when the script is loaded), and deactivates when
the user exits selection mode via Escape or the react-grab toolbar.

* Fetch react-grab script via URLSession to bypass CSP

Sites like vercel.com block loading external scripts via CSP headers.
Fetch the script with URLSession (not subject to page CSP), cache it,
and inject inline via evaluateJavaScript. Also guard against duplicate
injection on repeated clicks.

* Prefetch react-grab script on first browser panel init

Kick off a low-priority background fetch of the react-grab script
when the first BrowserPanel is created. The script is cached
statically so clicking the button is instant.

* Eliminate react-grab button and callback lag

Three changes:
1. Fire-and-forget: use evaluateJavaScript with completionHandler
   instead of await, so button taps return immediately.
2. Single JS payload: combine bootstrap listener + script source
   into one evaluateJavaScript call (one IPC round-trip, not two).
3. Dedupe state callbacks: only post webkit message when isActive
   actually changes, not on every hover/drag state update.

* Fix duplicate state callback on react-grab toggle

toggleReactGrab was sending an explicit postMessage AND the plugin's
onStateChange hook was firing too, causing two @Published updates per
toggle. Remove the explicit postMessage since the plugin hook handles
it. Also add dlog instrumentation for debugging.

* Add Cmd+Shift+G shortcut for React Grab (configurable)

- Add toggleReactGrab to KeyboardShortcutSettings with Cmd+Shift+G default
- Add View menu item with customizable shortcut
- Add command palette entry (searchable as "react grab" or "inspect element")
- Simplify button to use toggleOrInjectReactGrab, remove local state tracking

* Fix Codex review findings: pin version, verify hash, fix retry and state

1. Pin react-grab to exact version (0.1.29) with SHA-256 integrity
   check. Script is verified before evaluation to prevent supply-chain
   attacks via compromised CDN responses.
2. Clear prefetchTask on failure so subsequent attempts retry the
   download instead of reusing a permanently failed task.
3. Remove premature isReactGrabActive=true. State is now only set
   by the onStateChange message handler callback after confirmed
   initialization, or explicitly reset on evaluation error.

* Extract React Grab into own file, make version configurable

Move all react-grab logic (settings, script loader, message handler,
BrowserPanel extension) into Sources/Panels/ReactGrab.swift.

Add a "React Grab Version" text field in Settings > Browser that lets
the user pin which npm version is fetched. Only versions with a known
SHA-256 integrity hash in ReactGrabSettings.knownHashes are accepted.
The cache invalidates when the configured version changes.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 18:00:45 -07:00
Lawrence Chen
978dd2c023
Add hover background to split action buttons (#2271)
* Add hover background to split action buttons

Split buttons (terminal, browser, split right/down) now show a subtle
rounded-rect background highlight on hover. Matches standard macOS
toolbar button behavior.

* Prevent fade overlays from bleeding into bottom separator

* Render bottom separator above fade overlays to prevent bleed

* Exclude drop zone from scroll fade threshold

* Revert button hover, fix fade threshold with 32pt buffer

* Fix right fade threshold: subtract drop zone, 4pt tolerance

* Add leading padding to split buttons, fix fade threshold

* Rework tab bar: full-width scroll with floating split buttons

* Use ultraThinMaterial blur for floating split buttons

* Make split buttons group full height

* Full-height split button blur background

* Inset split buttons from bottom separator

* Fix blur overlapping separator

* Use matching tab bar bg for split buttons, clear separator

* Use regularMaterial blur for split buttons

* Try thickMaterial for split buttons

* Fade gradient + solid barFill for floating split buttons

* Use extended right fade as split buttons backdrop

* Add 5 debug styles for split button background

* Add Split Button Style debug window to Debug menu

* Clean up: no-bg floating buttons, alphabetical debug menu

- Split buttons float with no background (tabBarBackground covers the
  area, scroll padding prevents tabs from appearing behind buttons)
- Default splitButtonsWidth to 120 so first render has correct padding
- Remove split button style debug window and debug styles
- Alphabetize Debug Windows menu entries, remove dividers

* Revert to HStack sibling layout, add debug menu docs to CLAUDE.md

- Split buttons are HStack siblings of the ScrollView, not overlays.
  Single .background() on parent, no compositing mismatch.
- Alphabetize Debug Windows menu, remove dividers.
- Document Debug menu in CLAUDE.md.

* Add Split Button Layout debug window with 5 switchable approaches

* Fix fade gradient color to match tab bar background

* Add fade color debug window with 6 color options

* Use mask for scroll fades, fixes color mismatch

* Hide scroll fades in minimal mode unless hovering

* Remove split buttons from layout when hidden in minimal mode

* Always show fades, overlay buttons to prevent scroll jump

* Add hover-only mask fade behind split buttons

* Reduce button mask area to 90pt

* Animate button mask smoothly

* Fade entire button group together via opacity

* Add blur behind buttons with fade mask

* Use theme barBackground for button backdrop

* Blur + theme tint for button backdrop

* More tint (0.85), less blur

* Tint 0.2, clear bottom border

* Use terminal bg color, add scroll trailing padding for buttons

* Less blur, paneBackground at 0.75 opacity

* paneBackground at 0.9 opacity

* 0.97 opacity for button backdrop

* Test: fully opaque paneBackground

* Test: solid red backdrop

* Gradient + solid paneBackground backdrop, no mask

* Force opaque paneBackground for button backdrop

* Use barBackground for button backdrop

* Use terminal bg (paneBackground forced opaque)

* Pre-composite backdrop color for exact match

* Add 6 switchable backdrop styles in debug window

* Mask-based button area hiding, no backdrop color needed

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 17:02:53 -07:00
Lawrence Chen
d015ace094
Fix minimal mode tab bar disappearing in fullscreen (#2375)
Three issues caused the Bonsplit horizontal tab bar to be hidden
when entering fullscreen with minimal mode enabled:

1. ignoresSafeArea(.container, edges: .top) was applied unconditionally
   in minimal mode, pushing content behind the fullscreen menu bar area.
   Now gated on !isFullScreen.

2. effectiveTitlebarPadding returned -titlebarPadding in minimal mode
   regardless of fullscreen state. In fullscreen there is no native
   titlebar to compensate for, so the negative offset pushed content
   off the top of the screen. Now returns 0 in fullscreen.

3. Traffic light leading inset (80px) was applied in fullscreen minimal
   mode even though there are no traffic light buttons. Now gated on
   !isFullScreen, and syncTrafficLightInset is called on fullscreen
   enter/exit.

Closes https://github.com/manaflow-ai/cmux/issues/2317
Based on https://github.com/manaflow-ai/cmux/pull/2341

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 16:41:05 -07:00
Lawrence Chen
c2abfb9760
Switch nightly Sparkle feed URL to R2 (#2366)
Point cmux NIGHTLY's SUFeedURL to files.cmux.com/nightly/appcast.xml
(Cloudflare R2) instead of the GitHub Release asset. R2 uses atomic
PutObject for replacement, eliminating the transient SUDownloadError
2001 that occurs when GitHub Release assets are being overwritten
during a nightly publish.

The isNightly detection (checks for "/nightly/" in the URL) still
works with the new R2 URL. DMGs continue to be served from GitHub
Releases. Only the appcast feed URL changes.

Stable builds are unchanged (still use GitHub Releases).

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 14:48:50 -07:00
Lawrence Chen
9d9559fb93
Relicense from AGPL-3.0 to GPL-3.0 (#2364)
* Relicense from AGPL-3.0 to GPL-3.0 (keep dual-license with commercial option)

AGPL's network-use clause is irrelevant for a desktop app, but triggers
blanket corporate bans. GPL-3.0 still requires forks to stay open source
(preventing proprietary commercial forks) while being accepted by most
corporate policies for desktop software.

Changes:
- LICENSE: Replace AGPL-3.0 text with GPL-3.0 text
- Update dual-license header (AGPL → GPL)
- Update all README translations, CONTRIBUTING.md, package.json files
- Historical changelog/project entries left as-is

* Fix French and Italian grammar in license section

AGPL starts with a vowel so "l'AGPL" / "all'AGPL" were correct.
GPL starts with a consonant, so use "la GPL" / "alla GPL" instead.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 04:54:00 -07:00
Lawrence Chen
27aab3a035
Simplify R2 to appcast-only (keep DMGs on GitHub) (#2363)
* Simplify R2 upload to appcast-only (keep DMGs on GitHub)

DMGs are immutable per-build on GitHub Releases (unique filenames,
no overwrite), so there's no race condition for them. Only the
appcast.xml needs atomic replacement, which R2 PutObject provides.

Upload the original appcast.xml as-is (GitHub Release DMG URLs)
to R2. No sed URL rewriting, no DMG uploads, less storage/bandwidth.

* Move R2 appcast upload after GitHub Release publish

The R2 appcast references GitHub Release DMG URLs, so it must be
uploaded after the DMGs exist on GitHub. Previously the R2 upload
ran before the publish step, creating a brief window where the
appcast pointed to a not-yet-existing DMG.

* Add semver guard to stable R2 appcast upload

Prevents a backport tag (e.g. v0.62.1 pushed after v0.63.1) from
overwriting the stable appcast with an older version. Uses sort -V
to compare all non-prerelease tags and only uploads if the current
tag is the highest.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 04:50:18 -07:00
Lawrence Chen
f6c949add7
Add cmd-click fallback for bare filenames (ls output) (#2294)
* Add cmd-click fallback for bare filenames in terminal output

When cmd-clicking text that ghostty's built-in URL/path regex doesn't
match (e.g. bare filenames from `ls` like README.md, src, config.json),
fall back to checking if the word under cursor is a valid file or
directory in the terminal panel's CWD. Uses the existing
ghostty_surface_quicklook_word API to extract the word, then resolves
it against the panel's working directory and opens it if it exists.

* Add pointing-hand cursor on Cmd-hover over bare filenames

When holding Cmd and hovering over a word that resolves to an existing
file/directory in the terminal's CWD, show the pointing-hand cursor.
Hooks into mouseMoved and flagsChanged so the cursor updates both when
moving the mouse with Cmd held and when pressing/releasing Cmd while
the mouse is stationary.

* Address PR review comments

- Refresh ghostty mouse position before quicklook_word in mouseUp and
  flagsChanged so stale coordinates don't resolve the wrong word
- Use failable String(bytes:encoding:.utf8) instead of lossy decoding
- Skip absolute-path words (already handled by ghostty's regex)
- Guard against remote terminal sessions (local fileExists would be wrong)
- Use invalidateCursorRects instead of forcing iBeam on hover deactivation
  to avoid overwriting ghostty/AppKit's cursor state

* Add preferred editor setting for cmd-click file opens

New "Open Files With" picker in Settings > App lets users choose which
editor opens when cmd-clicking bare filenames. Options: System Default,
Cursor, VS Code, Windsurf, Zed, Sublime Text, Xcode. Reuses the
existing TerminalDirectoryOpenTarget app detection infrastructure.
Defaults to system default (NSWorkspace default handler).

* Replace editor picker with free-form command field, respect $VISUAL/$EDITOR

The "Open Files With" setting is now a text field where users can type
any command (code, zed, subl, open -a Xcode, etc.). Resolution order:
1. User-configured command from settings
2. $VISUAL environment variable
3. $EDITOR environment variable
4. System default (NSWorkspace)

Removes the fixed PreferredEditor enum in favor of flexibility.

* Fix stuck pointing-hand cursor using NSCursor push/pop

invalidateCursorRects did nothing since the view has no cursor rects.
Use NSCursor push/pop stack instead so the previous cursor is properly
restored when the hover deactivates.

* Remove $VISUAL/$EDITOR fallback, use system default when empty

$EDITOR/$VISUAL are typically terminal editors (vim, nano) that can't
launch as GUI subprocesses. Empty field now falls back to system default
(opens in Finder/default app) which is the expected behavior.

* Address PR review comments (round 2)

- Use broader CWD fallback chain (panelDirectories → requestedWorkingDirectory
  → workspace currentDirectory) matching Workspace split creation logic
- Pop cursor stack in viewDidMoveToWindow to balance push if view is removed
  while hover is active
- Reset preferredEditorCommand in resetAllSettings()
- Fall back to NSWorkspace.open when the custom editor command exits non-zero
  (e.g. command not found exits 127 but /bin/sh itself succeeds)

* Clear cursor on mouse exit to prevent stuck pointing-hand

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 04:49:17 -07:00
austinpower1258
8336aae865 Use pane TTY fallback for tmux Shift+Enter 2026-03-30 04:15:23 -07:00
Lawrence Chen
d6d9130c72
Add R2 dual-write for nightly appcast and DMGs (#2335)
* Add R2 dual-write for nightly appcast and DMGs

Upload nightly DMGs and appcast to Cloudflare R2 (files.cmux.com)
alongside the existing GitHub Release assets. R2 uses atomic PutObject
so the appcast never 404s during replacement, fixing the transient
SUDownloadError 2001 that occurs when GitHub Release assets are
being overwritten.

DMGs are uploaded before the appcast so the feed never references a
file that doesn't exist yet. The GitHub Release upload is unchanged,
so existing nightly users are unaffected.

A follow-up PR will switch the Sparkle feed URL in the app bundle
from GitHub Releases to R2 after manual verification.

* Add continue-on-error to R2 steps

R2 upload failures should not block the existing GitHub Release
publish. This keeps the nightly pipeline safe while R2 is new.

* Add R2 dual-write for stable release appcast and DMG

Same pattern as nightly: upload DMG then appcast to R2
(files.cmux.com/stable/) alongside the GitHub Release.
Both steps use continue-on-error so R2 failures can't
block the release.

* Address review feedback: cache headers, no double build, AWS CLI guard

- Add Cache-Control headers: immutable versioned DMGs get max-age=1yr,
  mutable appcast.xml and latest DMG get no-cache to prevent stale CDN
- Replace separate appcast generation step with sed URL replacement,
  avoiding a second Sparkle clone+build (signature is over DMG content,
  not the URL)
- Add AWS CLI availability check with fallback brew install

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-30 03:54:01 -07:00
Austin Wang
90a9edb761
Fix browser pane hangs from redundant portal refreshes (#2353)
* Fix browser pane hangs from redundant portal refreshes (#2326)

* Preserve force-refresh recovery for browser panes

* Keep browser refresh generations monotonic

* Preserve inspector reattach refreshes during churn

* Fix hosted inspector restore after portal reattach
2026-03-30 03:51:18 -07:00
austinpower1258
f4c99d34f3 Fix tmux Shift+Enter state reporting 2026-03-30 03:49:00 -07:00
Austin Wang
0666a98ae9
Fix Dock persistence for manual app icons (#2360) 2026-03-30 03:34:35 -07:00
Austin Wang
2c5c4fcf8d
Fix tmux-compat split-window surface resolution (#2351)
* Add tmux-compat split-window ref regression tests

* Fix tmux-compat split-window surface resolution

* Fix stale tmux caller surface fallback

* Add stale tmux-compat split-window regressions

* Fix stale tmux-compat split-window anchors

* Preserve tmux fallback and column anchor
2026-03-30 03:28:25 -07:00
Austin Wang
867c93e4fa
Keep cmux browser Find shortcuts authoritative (#2356)
* Route browser Find shortcuts through web content first

* Keep cmux browser Find shortcuts authoritative

* Add browser Find inspector regression test

* Fix browser Find routing follow-ups
2026-03-30 03:16:10 -07:00
austinpower1258
9ce4997ced Fix Shift+Enter tmux follow-up regressions 2026-03-30 03:10:20 -07:00
Austin Wang
6a39bac0e1
Fix update error details dialog overflow (#2359) 2026-03-30 03:05:48 -07:00
Austin Wang
ae59e571a8
Fix #2347 terminal focus and surface recovery (#2354)
* Add regressions for issue #2347 terminal focus loss

* Fix issue #2347 terminal focus and surface recovery

* Add regressions for missing-surface recovery review cases

* Fix missing-surface recovery lifecycle and focus replay
2026-03-30 03:01:39 -07:00
austinpower1258
9080248393 Gate Shift+Enter newline remap to tmux 2026-03-30 02:55:19 -07:00
Austin Wang
29c0f525db
Add New Window to Dock menu (#2340)
* Add Dock menu new-window regression test

* Add New Window to Dock menu
2026-03-30 02:19:17 -07:00
austinpower1258
540015537d Map Shift+Enter to raw newline in Ghostty 2026-03-30 02:18:15 -07:00
Austin Wang
27db2eb8dd
Add reset-terminal terminal menu workaround (#2349) 2026-03-30 02:05:13 -07:00
Austin Wang
63dd7281f5
Fix fullscreen new windows opening in current Space (#2345)
* Fix fullscreen new windows opening in current Space

* works

* Stabilize fullscreen tiling regression test
2026-03-30 01:43:41 -07:00
Austin Wang
79bcf1d370
Fix browser pane dark-mode leak on light pages (#2346)
* Fix browser pane dark-mode leak

* Restore browser theme mode without CSS injection
2026-03-30 00:57:25 -07:00
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