* Add sidebar help menu
* Fix help menu test wiring
* Fix help menu accessibility
* Use native popup for help menu
* Use icon button for sidebar help
* Add feedback composer and feedback API
* Allow preview builds without feedback env
* Tighten feedback upload limits
* Adjust sidebar footer padding
* Tighten sidebar footer spacing
* Add link affordances to help menu
* Polish sidebar feedback composer
* Move feedback icon to trailing edge
* Normalize help menu trailing icon sizes
* Enlarge help menu trailing icons
* Reduce help menu link icon size
* Shrink help menu link arrow
* Reduce help menu link arrow again
* Fix feedback message editor focus
* Add send feedback keyboard shortcut
* Polish feedback launch and delivery
* Return browser screenshot image URL
* Make screenshot path/url best effort
* cli: omit screenshot png_base64 from json output
* browser wait: fail fast on js errors and include screenshot in help
* browser wait: avoid main-actor default world warning
* tests: scope contentWorld regression check to function signature
* browser screenshot: clean up output handling and tests
* browser wait: resolve snapshot refs in selector waits
Locale initialization on the main thread (os.locale.ensureLocale /
NSLocale._preferredLanguages) can race with Sentry's background
init thread calling posix.getenv, causing a SIGSEGV and leaving
the SDK disabled.
Related to #836
* fix: avoid NSTextView tracking loop in omnibar mouseDown (#917)
Replace the synthetic mouseUp timeout workaround with direct cursor
positioning via NSTextView.characterIndexForInsertion(at:). The previous
approach posted a fake mouseUp event via NSApp.postEvent after 3 seconds,
but the NSTextView tracking loop does not always dequeue events from the
application event queue when stuck in an infinite
NSTextLayoutManager.enumerateTextLayoutFragments cycle, so the hang
persisted.
The new approach bypasses super.mouseDown entirely when the field editor
is already active, positioning the cursor (or extending the selection
with Shift+click) without entering the tracking loop. Drag-to-select is
not supported in this code path, but for a single-line omnibar this is
an acceptable trade-off.
* fix: handle double-click, UTF-16 length, and shift-click anchor
Address review feedback:
- Forward double/triple-click events to editor.mouseDown(with:) to
preserve word and line selection without entering NSTextField's
tracking loop
- Use (editor.string as NSString).length instead of String.count for
NSRange clamping (NSRange uses UTF-16 indices)
- Track shift-click anchor independently via shiftClickAnchor property
to correctly handle bidirectional selection extension
* fix: reset shiftClickAnchor on keyDown to prevent stale anchor
Clear the shift-click selection anchor whenever a key is pressed, so
that keyboard navigation (arrow keys, Shift+arrow, Home/End, etc.)
properly invalidates the mouse-originated anchor. A subsequent
Shift+click will then use the current selection position as anchor
instead of a stale value from a prior mouse interaction.
* fix: reset shiftClickAnchor in performKeyEquivalent and on re-focus
Key equivalents (Cmd+A, Cmd+V, etc.) bypass keyDown and go through
performKeyEquivalent, so the anchor must also be cleared there.
Similarly, re-focusing the field (currentEditor() == nil path) should
reset the anchor since selectAll changes the selection state.
During IME composition (e.g. Japanese input), Ctrl+H should delete
composing characters via the IME, not bypass it and send a backspace
directly to the terminal. Add a hasMarkedText() check so the fast path
is only taken when no IME composition is active, letting
interpretKeyEvents() handle the key instead.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Pass inherited working directory when creating split panes (panelDirectories
fallback to currentDirectory)
- Suppress bash job-done "[N] Done ..." notifications in shell integration
by toggling job control (set +m / set -m) around background probes
- Add integration test for split/tab CWD inheritance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix orphaned child processes when closing workspace tabs
When closing a workspace tab via the sidebar X button, child processes
(login → zsh → claude) survived as orphans because TabManager.closeWorkspace()
only removed the workspace from the tabs array without explicitly freeing
Ghostty surfaces. It relied on ARC to cascade deallocation, but SwiftUI views
and Combine publishers held references, delaying or preventing
ghostty_surface_free() (which sends SIGHUP) from ever running.
This adds explicit teardown on the workspace close path:
- TerminalSurface.teardownSurface(): idempotent method to free the Ghostty
runtime surface eagerly, matching the existing deinit logic
- TerminalPanel.close() now calls teardownSurface() to ensure SIGHUP is sent
- Workspace.teardownAllPanels() iterates all panels and closes them
- TabManager.closeWorkspace() calls teardownAllPanels() before removing
the workspace from the tabs array
* Harden workspace teardown and ownership checks
* Address follow-up teardown review feedback
---------
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
* Fix Claude wrapper hook injection when cmux socket is stale
* Harden socket listener lifecycle and rearm policy
* Unset CLAUDECODE in stale-socket passthrough
* Harden listener cleanup and bound claude ping probe
* Guard socket unlink during listener startup window
* Add localization for 16 new languages
Add translations for all 637 UI string keys and 3 InfoPlist keys in:
ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, zh-Hans, zh-Hant
Update AppLanguage enum and knownRegions to include all 18 languages.
Total supported languages: en, ja + 16 new = 18.
* Reorder languages: English first, rest alphabetical, explicit display names
Use "Chinese Simplified" / "Chinese Traditional" naming. Show native script
with English name in parentheses for non-Latin languages.
* Add Chinese native characters to language display names
* Delay language restart dialog until picker dropdown closes
* Fix Arabic bidi rendering in language picker with LTR mark
* Defer AppleLanguages write to app launch, fix picker animation lag
Writing AppleLanguages to UserDefaults triggers synchronous locale
recalculation on the main thread, causing the picker dropdown dismiss
animation to stutter. Since a restart is already required, move the
AppleLanguages write to init() on next launch instead of onChange.
* Fix reset path and add apply() back to delayed onChange
- resetAllSettings() now calls LanguageSettings.apply(.system) and
shows restart alert if language was changed from launch value
- onChange also calls apply() inside the 0.3s delay block, so
AppleLanguages is set before restart (avoids two-restart issue)
- init() still calls apply() as belt-and-suspenders for launch
* Fix deferred alert race: re-check current language in closure
If user changes language then changes back within 0.3s, the stale
closure would fire with the old value. Now reads current appLanguage
inside the closure instead of capturing newValue.
* Fix Spanish and Danish translations: restore missing diacritics
Spanish was missing all áéíóúñ characters (now 315 diacritics).
Danish was missing all æøå characters (now 401 diacritics).
Both languages fully retranslated with correct orthography.
* Fix reset restart alert: compare new value against launch language
Was comparing previousLanguage against languageAtLaunch, which would
miss the case where user launched in Spanish and reset to System
(Spanish != Spanish = false, so no alert). Now compares the new
appLanguage (system) against languageAtLaunch.
* Add markdown viewer panel with live file watching
Introduce a new PanelType.markdown that renders .md files in a dedicated
panel using MarkdownUI (SwiftUI), with live file watching via DispatchSource
so content auto-updates when the file changes on disk.
- New MarkdownPanel class with file system watcher (write/delete/rename/extend)
- New MarkdownPanelView with custom cmux theme (headings, code blocks, tables,
blockquotes, inline code, lists, horizontal rules, light/dark mode)
- Full workspace integration: SurfaceKind, creation methods, tab subscription
- Session persistence: snapshot/restore across app restarts
- V2 socket command: markdown.open (validates path, resolves workspace, splits)
- CLI command: cmux markdown open <path> with routing flags and help text
- Agent skill: skills/cmux-markdown/ with SKILL.md, openai.yaml, and references
- Cross-link from skills/cmux/SKILL.md to the new markdown skill
- SPM dependency: gonzalezreal/swift-markdown-ui 2.4.1
* Fix unreachable guard in markdown subcommand dispatch
Use looksLikePath() to distinguish subcommands from path arguments
so the guard can catch unknown subcommands and future subcommands
are parsed correctly.
* Use .isoLatin1 fallback instead of .ascii for encoding recovery
ASCII is a strict subset of UTF-8, so falling back to .ascii after
UTF-8 fails is dead code. Use .isoLatin1 which accepts all 256 byte
values and covers legacy encodings like Windows-1252.
* Mark fileWatchSource as nonisolated(unsafe) for deinit safety
deinit is not guaranteed to run on the main actor, so accessing
@MainActor-isolated storage is a data race under strict concurrency.
DispatchSource.cancel() is thread-safe, so nonisolated(unsafe) is
sufficient with a documented invariant that writes only occur on main.
* Fix file watcher reattach: retry loop with cancellation guard
- Replace one-shot 500ms retry with up to 6 attempts (3s total window)
so files that reappear after a slow atomic replace are picked up
- Add isClosed flag checked before each retry to prevent restarting
the watcher after close()/deinit
* Harden path validation in markdown.open command
Reject directories and non-absolute paths before panel creation
to prevent ambiguous behavior and generic downstream failures.
* Always reattach file watcher on delete/rename events
After an atomic save (delete old + create new), the DispatchSource still
points to the old inode. Previously we only reattached when the file was
unreadable, so successful atomic saves left the watcher on a stale inode
and live updates silently stopped. Now we always stop and reattach:
immediately if the new file is readable, via retry loop if not.
* Restore markdown panels even when file is missing at launch
MarkdownPanel already handles unavailable files gracefully (shows
'file unavailable' UI and retries via the reattach loop). Dropping
the panel on restore lost the user's layout for files that may
reappear shortly after (network drives, build artifacts, etc.).
* Harden markdown CLI parsing and startup reconnect behavior
---------
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
JavaScript-based find using TreeWalker + <mark> highlights with
match counter, next/previous navigation, and drag-to-corner overlay
matching the existing terminal find bar.
- BrowserFindJavaScript: JS generation for search/next/prev/clear
- BrowserSearchOverlay: SwiftUI overlay with IME-safe onSubmit
- BrowserSearchState: Observable state (needle/selected/total)
- TabManager routing: Cmd+F/G dispatches to browser when focused
- Visibility filter: skips script/style/hidden/aria-hidden elements
- Stale DOM guard: isConnected check in next/previous scripts
- Navigation cleanup: clears find on didFinish and didFailNavigation
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Swift's split(separator:) omits empty subsequences by default, so a
payload like "||" or "||body" produces an empty or misaligned array.
Accessing parts[0] unconditionally then triggers an out-of-bounds trap
(EXC_BREAKPOINT / SIGTRAP).
Two changes:
1. Pass omittingEmptySubsequences: false to preserve field positions
across the pipe delimiters, so "title||body" correctly yields
["title", "", "body"] instead of ["title", "body"].
2. Guard parts[0] with a bounds check, consistent with how parts[1]
and parts[2] are already accessed.
Reproduces when cmux notify is called with empty --title or via
Claude Code's Notification hook where env vars may be empty.