* 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>
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>
* Pre-launch app for browser UI test on headless CI runners
XCUIApplication.launch() blocks ~60s then fails on headless WarpBuild
runners because foreground activation requires a GUI login session.
Apply the same pre-launch strategy used for the display resolution test:
- CI shell launches the app with env vars before running xcodebuild
- Test detects pre-launched app via manifest, uses activate() instead of
launch() to avoid killing and relaunching the app
- Falls back to clicking the window for focus via accessibility framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "Pre-launch app for browser UI test on headless CI runners"
This reverts commit a540e2fd99aaa1395b91a8d50caa797cdd7551b8.
* feat: cmux.json for custom commands
* tests: add cmux json tests
* fix: pr review feedback: validation, translations, input handling, and palette improvements
- Fix Danish ("Overfladedef inition") and Norwegian ("rotmapp") translation typos
- Add empty-string check for baseCwd fallback in command palette handlers
- Coalesce \r\n into single Return keypress in sendInput
- Redact command text from timeout log to prevent secret leakage
- Add decode-time validation: reject hybrid/empty commands, ambiguous layout
nodes, wrong split children count, and empty pane surfaces
- Namespace custom command IDs with "cmux.config.command." prefix
- Forward command description to palette subtitle when available
- Update tests for new validation rules and ID prefix
* fix: address PR review feedback — per-window config isolation, blank validation, ancestor walk,
palette sanitization
* fix: fallback to current dir cmux.json watching if no any cmux.json found in full acesor walk
* ci: trigger CI for fork PR
* Add directory trust for cmux.json command confirmation
The confirm dialog now shows the actual command text and has an "Always
trust commands from this folder" checkbox. When checked, future confirm
commands from that directory skip the dialog.
Trust is scoped to the git repo root if the cmux.json is inside a repo,
so trusting once covers all subdirectories. Non-git directories are
trusted by exact path. Global config is always trusted.
Trusted directories are persisted in ~/Library/Application Support/cmux/
trusted-directories.json.
* Add trusted directories section to Settings
Shows all trusted directories with per-directory revoke buttons and a
Clear All option. Placed in a "Custom Commands" section between
Automation and Browser in Settings.
* Replace trusted directories list with editable textarea
One path per line, with a Save button that activates on changes.
Users can add, remove, or edit paths directly.
* Auto-save trusted directories on edit, remove Save button
Matches the behavior of other textarea settings (browser host
whitelist, external URL patterns) which auto-save via @AppStorage.
* Sanitize command text in confirm dialog against BiDi attacks
Strip zero-width and BiDi override characters from the command preview
so the dialog shows exactly what will be executed.
---------
Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Fix panel resize stuttering when tiled with browser panels (#1968)
During divider drag, the portal sync system was doing O(N²) work per
frame: each geometry callback synced ALL web views, and multiple
callbacks fired per layout pass (setFrameSize + setFrameOrigin + layout).
Two changes:
1. synchronizeWebViewForAnchor now only syncs the primary web view and
defers the all-sync. Each panel fires its own geometry callback, so
secondary syncs are redundant on the hot path.
2. HostContainerView.setFrameOrigin/setFrameSize use markGeometryDirtyIfNeeded
which defers the callback to layout(), coalescing 2-3 notifications
per frame into one. An async fallback ensures origin-only changes
(without a subsequent layout) are still delivered.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix premature geometryRevision increment in markGeometryDirtyIfNeeded
Address reviewer feedback (Greptile, CodeRabbit): geometryRevision and
lastReportedGeometryState are now only updated when the callback
actually fires, not eagerly. This prevents updateNSView from seeing a
premature revision delta and triggering a redundant synchronizeForAnchor
before the coalesced notification arrives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a pane split occurs, SwiftUI recreates host views and the portal
system rebinds the WKWebView to a new container. However, the bind path
never called BrowserWindowPortalRegistry.refresh(), so WebKit's internal
rendering state (_exitInWindow/_enterInWindow) was never cycled. This
left the WKWebView frozen in the original pane after a split.
Add refresh() calls after every portal bind that changes the host, in
three code paths: the main update path (shouldBindNow), onDidMoveToWindow,
and onGeometryChanged. The refresh is a no-op when no reattach is needed
(browserPortalNeedsRenderingStateReattach == false), so normal rendering
is unaffected.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a non-Latin input source like Korean 두벌식 is active,
event.charactersIgnoringModifiers returns Hangul characters (e.g. ㅅ
for T key) instead of Latin letters. This caused all character-based
shortcut matching to fail — Cmd+T, Cmd+D, Cmd+1-9, Ctrl+N/P, etc.
Root cause: KeyboardLayout.character(forKeyCode:modifierFlags:) assumed
CJK input sources lack kTISPropertyUnicodeKeyLayoutData, but Korean
두벌식 has it. UCKeyTranslate returned Korean characters and the ASCII
fallback was never reached.
Fix:
- KeyboardLayout.character(): check result is ASCII before accepting;
fall through to TISCopyCurrentASCIICapableKeyboardInputSource() when
the current source returns non-ASCII characters
- Add KeyboardLayout.normalizedCharacters(for:) helper that normalizes
event.charactersIgnoringModifiers for shortcut comparison
- Apply normalization in handleCustomShortcut (AppDelegate),
BrowserPanelView omnibar key handler, and BrowserPopupWindowController
Cmd+W handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add regression test for browser back history
* Fix browser back history handoff
* Fix browser tab favicon not updating on navigation
Two issues caused stale or missing favicons in browser tabs:
1. KVO race: The isLoading observer read webView.isLoading inside a deferred
Task instead of capturing the KVO change value at observation time. For fast
navigations (back-forward cache), isLoading flips true→false before the Task
runs, so handleWebViewLoadingChanged(true) was never called and the old
favicon was never cleared.
2. SPA favicon discovery: Sites that inject <link rel="icon"> via JavaScript
(e.g. React apps) had no favicon link in the DOM when didFinish fired. The
fallback to /favicon.ico often 404'd, leaving the globe icon permanently.
Now retries the JS query after 600ms to give client-side scripts time to
add the tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.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