* Add regression test for terminal notification click dismissal
* Dismiss terminal notifications on direct clicks
* Add regression for focused terminal notification ring
* Keep focused terminal notifications unread until click
* Verify direct notification dismiss triggers flash
* Use focus-flash path for direct notification dismiss
* Align notification dismiss flash with ring geometry
* Add workspace pages in the titlebar
* Add workspace pages UI test target entry
* Relax workspace pages UI test titlebar checks
* Use page close button in workspace pages UI test
* Stabilize workspace pages UI test interruptions
* Skip page close confirms in UI tests
* Clean up superseded workspace handoffs
* Tighten page hint UI assertions
---------
Co-authored-by: cmux <cmux@cmuxs-Mac-mini.local>
* 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
* 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>
* Vi mode P0 improvements: half-page scroll, visible cursor, gg fix
Three changes to keyboard copy mode (vi mode):
1. Ctrl+U/D now scroll half-page (was full-page). Ctrl+B/F remain
full-page. Uses Ghostty's scroll_page_fractional binding.
2. Entering copy mode now creates a 1-cell selection at the terminal
cursor via select_cursor_cell, giving the user a visible cursor
indicator. Visual mode (v) is tracked separately from Ghostty's
has_selection so the cursor selection doesn't make every motion
behave as visual. Exiting visual mode (v again) collapses back
to the cursor cell.
3. Single 'g' is now a prefix key requiring 'gg' to scroll to top,
matching standard vim behavior. Uses the same pendingG state
machine pattern as pendingYankLine for 'yy'.
Part of https://github.com/manaflow-ai/cmux/issues/846
* Fix viewport row refresh with persistent cursor selection
The 1-cell cursor selection made refreshKeyboardCopyModeViewportRowFromVisibleAnchor
always bail (has_selection was always true). Guard on keyboardCopyModeVisualActive
instead so viewport row updates work after scrolling in non-visual mode. Also
re-creates the cursor cell after refresh to preserve visibility.
Additionally: use performBindingAction(_:repeatCount:) for scrollHalfPage,
add state-reset assertion to testGGWithSelectionAdjustsToHome.
Addresses review feedback from CodeRabbit, Cubic, Codex, and Greptile.
* Add keyboard copy mode for terminal scrollback
* Show vim copy mode indicator in terminal
* Fix vi copy-mode symbol keys and pending yank handling
* Refine copy-mode badge wording and font
* Rename keyboard copy-mode badge to VI MODE
* Address PR feedback for copy-mode routing and keyup handling
* Refresh copy-mode viewport row after scrolling
* Add setting to hide Cmd-hold shortcut hints
* Bump bonsplit for Cmd-hold pane hint toggle
* Document tagged app link format in agent notes
* Disable Ctrl pane hints when hold-hints toggle is off
When sibling.hitTest() triggers a SwiftUI layout pass during the
drag handle's sibling walk, AppKit can call back into
windowDragHandleShouldCaptureHit before the outer invocation
finishes. This re-entry accesses SwiftUI view state that is already
held exclusively, causing a Swift runtime SIGABRT.
Add a module-level re-entrancy guard that bails out (returns false)
on nested calls to the sibling walk. Since hitTest is always called
on the main thread, a simple Bool flag is sufficient.
Crash was reproduced on macOS Sequoia 15.1.1 (24B91) in a UTM VM.
The crash stack: DraggableView.hitTest -> windowDragHandleShouldCaptureHit
-> sibling.hitTest -> SwiftUI body evaluation -> hitTest (re-entry)
-> exclusive-access violation -> SIGABRT.