Adds a File menu item and command palette entry to pick a local folder
and open it in an inline VS Code browser panel. Extracts the inline
VS Code open logic from ContentView into a reusable AppDelegate method
so both the right-click context action and the new open-panel flow
share the same code path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix transparent background flash during sidebar toggle
Move terminal background rendering from the Metal GPU pass to a
CALayer (backgroundView). The GPU bg_color pass is disabled via a
new Ghostty config flag (macos-background-from-layer). The CALayer
resizes instantly with its parent NSView, eliminating the 3-5 frame
gap where the desktop was visible through the transparent window
during sidebar toggles and layout transitions.
Also simplifies the titlebar and sidebar opacity formulas since
there is now a single background layer instead of two stacked
semi-transparent layers.
* Document macos-background-from-layer fork change
* Pin GhosttyKit checksum for macos-background-from-layer
* Address review feedback: fix fallback config path, inline identity wrapper
- Inject macos-background-from-layer in the fallback config path too,
preventing alpha double-stacking when user config is invalid
- Inline panelBackgroundFillColor (now an identity function) at its
two call sites and remove the wrapper
* Address adversarial review: skip fullscreen bg draw call explicitly
The bg_color uniform alpha is still zeroed for cell compositing (so
transparent cells pass through to the CALayer), but the fullscreen
background fill draw step is now explicitly skipped instead of relying
on alpha=0 as a no-op.
* Pin GhosttyKit checksum for bg draw-call skip
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* 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>
* 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>
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>
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>
* 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>
* 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>
* 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>
* 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>
- 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>