* feat: support window.open() popup windows (#742)
Return a live WKWebView from createWebViewWith using WebKit's supplied
configuration, preserving popup browsing-context semantics (window.opener,
postMessage). This fixes OAuth/OIDC flows and any site relying on standard
popup patterns.
- Add BrowserPopupWindowController: NSPanel-based popup with self-retention,
KVO title/URL, read-only URL label, nested popup depth limit (3),
insecure-HTTP prompt parity, auth challenge parity, download delegate
- Classifier: scripted requests (window.open) create popups; user-initiated
actions (Cmd+click, middle-click, context menu) open tabs
- Retarget context menu "Open Link in New Tab" to bypass createWebViewWith,
wired in both main browser and popup web views
- Cmd+W fast path in AppDelegate for popup windows
- Opener panel owns popup lifecycle; close() tears down all child popups
* fix: Cmd+W closes only the popup, not the parent tab
Add BrowserPopupPanel (NSPanel subclass) that intercepts Cmd+W in
performKeyEquivalent before the swizzled cmux_performKeyEquivalent
can dispatch it to the main menu's "Close Tab" action.
Also refine the popup classifier to reuse browserNavigationShouldOpenInNewTab
for Cmd+click/middle-click detection, add download delegate wiring, and
wire onContextMenuOpenLinkInNewTab for popup web views.
* fix: tighten popup routing and window behavior
* test: cover oversized popup frame clamping
* test: cover plain link-activated popup routing
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Add Startpage (startpage.com) to the BrowserSearchEngine enum alongside
Google, DuckDuckGo, Bing, and Kagi. Startpage provides an OpenSearch-
compatible suggestions endpoint at /osuggestions, so autosuggestions
work out of the box using the existing OSJSON parser.
Co-authored-by: Zlatko Cajic <git@zlat.co>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
keyCodeForShortcutKey() did not map arrow glyphs (←→↑↓) to their
macOS key codes (123-126). The shortcut recorder correctly stored
arrow keys as glyphs via storedKey(), but matchShortcut() could
not resolve them back to key codes for ANSI fallback matching.
This only affected non-directional shortcut actions (e.g. Next/Previous
Surface) since pane focus shortcuts use a separate matchDirectionalShortcut()
path that already handles arrow keys.
- Config: sidebar-background supports plain hex (#336699) or
light/dark syntax (light:#fbf3db,dark:#103c48)
- Config: sidebar-tint-opacity overrides tint opacity
- Settings UI: per-scheme color pickers, opacity slider (0-70%), reset
- SidebarBackdrop resolves light/dark hex based on @Environment colorScheme
- applySidebarAppearanceToUserDefaults guards on rawSidebarBackground presence
so UI picks survive appearance toggles when no config is set
- Stale light/dark UserDefaults keys cleared when config switches from
dual-mode to single or sidebar-background is removed
- applyPreset() and Reset Tint clear per-scheme overrides
- Debug snapshot (combinedPayload + copySidebarConfig) includes new keys
- ColorPicker labels use String(localized:) per localization policy
- Opacity slider capped at 0.7 to match debug view vibrancy constraint
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix stale Claude status in sidebar by adding missing hooks and OSC suppression
The Claude Code integration only used 3 hooks (SessionStart, Stop, Notification),
leaving gaps that caused stale sidebar status. Now uses 6 hooks:
- SessionEnd: clears status when Claude exits (covers Ctrl+C where Stop doesn't fire)
- UserPromptSubmit: clears "Needs input" and sets "Running" on new prompt
- PreToolUse (async): clears "Needs input" when Claude resumes after permission grant
Also:
- Suppress OSC 9/99 desktop notifications for workspaces with active Claude hook
sessions to prevent duplicates from the raw OSC path
- Store Claude process PID in status entries for stale-session detection
- Add 30-second sweep timer that checks agent PIDs and clears stale entries
(safety net for SIGKILL/crash where no hook fires)
- Update wrapper test expectations for the new hook set
Fixes https://github.com/manaflow-ai/cmux/issues/1301
* Don't show "Running" status on Claude launch, only when actually working
SessionStart now registers the PID for tracking and OSC suppression via
set_agent_pid without setting a visible status entry. "Running" only
appears when the user submits a prompt (UserPromptSubmit) or Claude
starts using tools (PreToolUse).
Added set_agent_pid / clear_agent_pid socket commands to decouple PID
tracking from visible status entries. OSC suppression checks agentPIDs
instead of statusEntries so it works during the initial idle period.
* Don't restore status entries across app restarts
Status entries are ephemeral runtime state tied to running processes
(e.g. claude_code "Running"). Restoring them after restart shows stale
status for processes that no longer exist.
* Address PR review comments and remove debug logging
- session-end: only clear status/PID/notifications when Stop didn't fire first
- PID sweep: check errno == ESRCH instead of treating all kill(pid,0) failures as dead
- Validate CMUX_CLAUDE_PID > 0
- Propagate tracked PID in pre-tool-use setClaudeStatus
- OSC suppression: use tabManagerFor(tabId:) for multi-window support
- clearAgentPID: resolve tab UUID before async dispatch
- restoreSessionSnapshot: also clear agentPIDs alongside statusEntries
- Fix AskUserQuestion surfaceId overwrite (wrong workspace notification)
- Fix notification text matching for "Claude Code needs your attention"
- AskUserQuestion: render option labels as bracketed inline text
- Remove artificial text truncation limits
- Remove temporary JSONL debug logging from all handlers
* Use resolveTabIdForSidebarMutation in clearAgentPID
The SwiftUI TextField lost arrow-key and backspace handlers when the
query prefix (">") was deleted, because the scope transition tore down
the .onKeyPress modifiers. Using an NSViewRepresentable with an AppKit
field editor keeps navigation commands (up/down/enter/escape) on the
native delegate, making them immune to SwiftUI scope changes.
Fixes#1409
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fallback stable socket listener to user socket path
* Move stable socket path out of /tmp
* Keep socket health checks active on fallback paths
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Previously, Cmd+W on the last surface kept the workspace alive with a
replacement shell, unless the user toggled a hidden setting. This was
confusing—users expect Cmd+W to close the window when there's nothing
left.
Now Cmd+W (and the tab-strip X button) always close the workspace when
they close its last surface, and close the window when that was the last
workspace. Internal/programmatic closes (e.g. process exit, panel moves)
still spawn a replacement shell so the workspace stays alive.
Key changes:
- Track explicit user close gestures via markExplicitClose / onTabCloseRequest
- Remove the LastSurfaceCloseShortcutSettings toggle (now always-on)
- Use window.performClose for last-workspace window close
- Update tests to match the new behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Snapshot TabManager's published state (tabs, selectedTabId) at the start
of workspace creation so mutations don't bounce through Combine-backed
accessors mid-creation. Prevents crashes when adding a workspace via
Cmd+N, the new-workspace button, or any other creation path that
triggers re-entrant publishes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>