* Add dynamic OG image and use large Twitter cards
Generate a 1200x630 OG image with the cmux logo, tagline, and
description using next/og ImageResponse. Switch Twitter card type
from "summary" to "summary_large_image" across all pages so shared
links show a full-width preview instead of the tiny favicon thumbnail.
* Use Geist font and app screenshot in OG image, update landing/README images
Replace the centered text-only OG image with a split layout: branding
on the left (logo, name, tagline) and a full app screenshot on the
right. Load Geist Regular/SemiBold from Google Fonts for consistent
typography. Replace the homepage landing image and README screenshot
with a new screenshot showing cmux with multiple workspaces, tabs,
browser panel, and code diffs.
* Fine-tune OG image layout and update homepage/README screenshots
Apply tuned values from OG editor: 112px logo, 48px title with -8
translateY, 34px subtitle at #cfcfcf, 320px fade height. Use Geist
font loaded from Google Fonts. Render at 2x (2400x1260) for sharper
previews on social platforms. Remove GitHub URL from footer.
Add pre-resized og-screenshot.png (2208px wide) for the OG image to
avoid Satori downscale blur. Update homepage landing image and README
screenshot with new app screenshot.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Rapid pushes to main spawn 7+ WarpBuild jobs simultaneously, causing
intermittent runner failures (shutdown during submodule clone, lost
communication). Add workflow-level concurrency groups to ci.yml and
build-ghosttykit.yml.
On PRs, stale runs are canceled when a new push arrives. On main, runs
queue serially (cancel-in-progress is false to avoid GitHub treating
cancelled runs as failures).
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
CAPTCHA providers (reCAPTCHA, hCaptcha, Cloudflare Turnstile) detect
environment tampering in their cross-origin iframes. With
forMainFrameOnly: false, the telemetry hooks (overridden console.*)
and address bar focus tracker (__cmux* globals) run inside CAPTCHA
iframes, causing challenges to fail or score the session as a bot.
Change forMainFrameOnly from false to true on:
- telemetryHookBootstrapScriptSource
- addressBarFocusTrackingBootstrapScript
Both only need to run in the top-level page context.
Fixes#1429
When a non-Latin input source is active, event.charactersIgnoringModifiers
returns CJK characters that cannot match Latin shortcut keys. This adds
ASCII-capable input source fallback in KeyboardLayout and updates the
matchShortcut guard to skip early-return when event chars are non-ASCII.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two root causes for issue #879:
1. GhosttyNSView was missing makeBackingLayer(), so AppKit provided a
generic CALayer as the view's backing layer. libghostty expects
(view.layer as? CAMetalLayer) != nil to set up Metal rendering on
the existing layer. Without a CAMetalLayer backing layer, the Metal
surface defaulted to isOpaque=true, making the terminal area fully
opaque regardless of background-opacity config.
Fix: override makeBackingLayer() to return a CAMetalLayer with
isOpaque=false and bgra8Unorm pixel format (matching standalone
Ghostty's SurfaceView behavior).
2. ghostty_set_window_background_blur(app, window) is exposed in
ghostty.h but was never called in cmux. Without this call the
macOS window never gets the NSVisualEffectView blur backdrop that
background-blur requires.
Fix: add applyWindowBlurIfNeeded() on GhosttyApp that reads
background-blur from config via ghostty_config_get and calls
ghostty_set_window_background_blur when the value is non-zero.
Called from applyBackgroundToKeyWindow() and
applyWindowBackgroundIfActive() whenever the window is made
transparent.
Fixes#879
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes#1715
The resolveTabForReport() function had an early guard that returned nil
when self.tabManager was nil, even when a valid --tab argument was
provided. This prevented Claude hook commands from resolving tabs via
AppDelegate.shared when the local tabManager wasn't available.
Changes:
- Modified resolveTabForReport() to allow tab resolution via --tab param
even when self.tabManager is nil
- Removed early guard in upsertSidebarMetadata() that blocked resolution
Co-authored-by: BillionClaw <267901332+BillionClaw@users.noreply.github.com>
The titlebar control icons (sidebar, notifications, new tab) were
vertically misaligned — centered within the full titlebar+tab-bar
area instead of the titlebar alone.
Use the close button's superview height as the true titlebar height
so the icons sit at the same vertical center as the traffic lights,
matching the behavior of apps like Slack.
Change cancel-in-progress from true to false so concurrent nightly
runs queue instead of being killed.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* fix: skip Korean from CJK font-codepoint-map auto-injection
The automatic CJK font-codepoint-map injection (PR #1017) maps Korean
ranges to Apple SD Gothic Neo, which has a different style/weight from
the primary terminal font. This overrides Ghostty's native
CTFontCreateForString fallback, which dynamically selects a
better-matching font for Hangul.
Ghostty itself (ghostty-org/ghostty) has no hardcoded CJK font names
and relies entirely on CTFontCreateForString for fallback. For Korean,
this native fallback produces visually consistent results with the
primary font.
Remove the Korean branch from cjkFontMappings() so Ghostty's native
fallback handles Hangul rendering. Japanese and Chinese mappings are
unaffected.
* test: update CJK font mapping tests for Korean removal
- testCJKFontMappingsReturnsAppleSDGothicNeoWithHangulForKorean
→ renamed to testCJKFontMappingsReturnsNilForKoreanOnly
→ asserts nil since Korean is no longer auto-mapped
- testCJKFontMappingsMultiLanguageMapsScriptSpecificRanges
→ renamed to testCJKFontMappingsMultiLanguageSkipsKorean
→ asserts no Apple SD Gothic Neo mapping exists
→ Japanese mappings remain unchanged
---------
Co-authored-by: dante-ad-shield <danate@ad-shield.io>
Add English and Japanese translations for the named tab close
confirmation message. Uses %@ format specifier for the tab name
to support proper localization word order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Include the panel's custom title, terminal title, or directory name in
the close tab confirmation dialog's informative text so users know which
tab will be closed. Falls back to the generic message when no name is
available. The dialog title stays "Close tab?" for consistency with the
close-confirmation detection in AppDelegate.
Fixes#1603
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Four call sites used `line.data(using: .utf8)!` to convert a String to
Data for FileHandle.write(). While .utf8 encoding never fails for Swift
strings (making the force unwrap safe in practice), `Data(line.utf8)` is
the idiomatic Swift equivalent that avoids the force unwrap entirely.
Changed files:
- GhosttyTerminalView.swift (3 occurrences: init log, surface log, size log)
- WorkspaceContentView.swift (1 occurrence: panel debug log)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TerminalPanel.directory is never updated (updateDirectory() is
never called anywhere). Workspace.panelDirectories is kept up to
date via updatePanelDirectory() from OSC 7 / shell integration.
Before: working directory always returns ""
After: working directory returns correct cwd (e.g. "/Users/grimmer")
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The underlying Ghostty fork does not include the `macos-applescript`
config key (added in upstream ghostty commit 25fa58143, 2026-03-06).
As a result, `appleScriptAutomationEnabled()` always returns false,
causing `scriptWindows` to return an empty array.
This makes `count windows`, `every window`, `front window`, and all
AppleScript window/tab/terminal access silently return empty results.
Fix: always return true from `isAppleScriptEnabled` until the fork
is updated with the upstream config key.
Test results (before fix):
osascript -e 'tell application "cmux" to count windows' → 0
osascript -e 'tell application "cmux" to get version' → 0.62.2
Test results (after fix):
osascript -e 'tell application "cmux DEV ..." to count windows' → 1
osascript -e 'tell application "cmux DEV ..." to count terminals of tab 1 of window 1' → works
osascript -e 'tell application "cmux DEV ..." to input text "..." to terminal 1 of ...' → works
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR #1783 added -skip-testing for
testCmdWClosesWindowWhenClosingLastSurfaceInLastWorkspace in ci.yml
but missed ci-macos-compat.yml. The test hangs on headless runners
because Ghostty PTY teardown blocks without a TTY.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Fix CI test timeout: stream xcodebuild output and bump timeout to 30m
The test split PR (#1717) applied output streaming (tee) and timeout bump
only to ci-macos-compat.yml, not ci.yml. The main CI tests job was still
capturing all xcodebuild output in a $() subshell (making logs blank) and
using a 20 minute timeout (too tight after the test file split).
Port the same fixes from ci-macos-compat.yml:
- Stream xcodebuild output via tee so CI logs show progress in real time
- Bump timeout-minutes from 20 to 30
- Update the SPM retry guard test for the new tee pattern
* Fix hanging test: auto-confirm window close in last-surface Cmd+W test
testCmdWClosesWindowWhenClosingLastSurfaceInLastWorkspace hung for 26+
minutes on CI because it sent Cmd+W to close the last surface without
setting debugCloseMainWindowConfirmationHandler. The window close path
shows a modal confirmation dialog that blocks the RunLoop indefinitely
on headless runners.
Set the handler to auto-confirm, matching the pattern used by
testCmdCtrlWClosesWindowAfterConfirmation.
* Skip last-surface close test on CI: PTY teardown blocks on headless runners
The confirmation handler fix wasn't sufficient. The hang is in Ghostty
surface/PTY teardown when closing the last terminal surface, not the
window close confirmation. Shell process termination blocks indefinitely
on headless CI runners without a TTY.
Skip with XCTSkip when CI env var is set. The test still runs locally
and can be covered via E2E on runners with virtual displays.
* Skip hanging test via -skip-testing flag in xcodebuild
The CI env var isn't visible inside xcodebuild's test host process, so
the XCTSkip approach didn't work. Use -skip-testing on the xcodebuild
command line instead.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Show update-available banner automatically on launch
Probe for updates immediately on launch via Sparkle's
checkForUpdateInformation() so the sidebar surfaces a passive update
indicator without waiting for the 24h scheduler. When Sparkle detects
an available update in the background, the pill now shows
"Update Available: X.Y.Z" with accent styling while the updater is
idle. Clicking it triggers the full interactive update flow.
Also fixes thread safety in delegate callbacks by dispatching
@Published mutations to the main queue.
Closes#1643
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add periodic background update probe every 15 minutes
The launch-only probe wouldn't catch updates published while the app
is already running. Add a repeating 15-minute timer that calls
checkForUpdateInformation() so the sidebar banner appears within a
reasonable window after a new version is published.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Change background update probe interval to 30 minutes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Change update check interval to 1 hour and migrate existing users
Reduce Sparkle's scheduled check interval from 24h to 1h so update
banners appear sooner. Migrate users stuck on the old 24h default by
bumping the migration key to v2. Align background probe interval with
the Sparkle check interval instead of hardcoding 30 minutes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>