feat: add quick Terminal panel, CLI/socket controls, and automation-safe focus behavior#1996
feat: add quick Terminal panel, CLI/socket controls, and automation-safe focus behavior#1996jemutorres wants to merge 5 commits intomanaflow-ai:mainfrom
Conversation
Implements Quick Terminal across app shortcut/menu/settings, command palette, socket v2, and CLI (cmux quick-terminal). Also ensures socket/CLI quick terminal commands do not steal macOS focus and wraps animation completion state updates on MainActor. Adds README and CHANGELOG notes. Closes manaflow-ai#327
|
@jemutorres is attempting to deploy a commit to the Manaflow Team on Vercel. A member of the Team first needs to authorize it. |
|
To use Codex here, create a Codex account and connect to github. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a Quick Terminal feature: a toggleable NSPanel terminal with configurable position/size and auto-hide; exposes keyboard shortcut (⌥⌘ Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as App UI (Menu / Palette / Shortcut)
participant CLI as CLI / External
participant App as AppDelegate
participant QT as QuickTerminalController (NSPanel)
participant Term as TerminalSurface
User->>UI: trigger toggle/show/hide
CLI->>App: cmux quick-terminal [toggle|show|hide|status]
UI->>App: toggleQuickTerminalVisibility(activateApp?)
App->>QT: toggle/show/hide
alt Show flow
QT->>QT: compute finalFrame / hiddenFrame
QT->>QT: create/configure NSPanel
QT->>Term: instantiate/attach TerminalSurface
QT->>QT: animate frame & alpha in
QT-->>App: visible = true
else Hide flow
QT->>QT: animate frame & alpha out
QT->>QT: hide/detach NSPanel
QT-->>App: visible = false
end
App-->>UI: status / completion
App-->>CLI: JSON status (if requested)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR implements end-to-end Quick Terminal integration — a drop-down style floating terminal panel — wired up across keyboard shortcut, View menu, command palette, settings UI, socket v2 API, and CLI. The implementation follows established app patterns well: Key additions:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant Shortcut/Menu/Palette
participant AppDelegate
participant QuickTerminalController
participant NSPanel
participant SocketCLI as Socket/CLI
participant TerminalController
User->>Shortcut/Menu/Palette: Toggle Quick Terminal
Shortcut/Menu/Palette->>AppDelegate: toggleQuickTerminal(_:)
AppDelegate->>QuickTerminalController: toggle(activateApp: true)
QuickTerminalController->>NSPanel: orderFront + makeKeyAndOrderFront
QuickTerminalController->>NSPanel: animate (hidden→final frame, α 0→1)
NSPanel-->>QuickTerminalController: completionHandler (Task @MainActor)
QuickTerminalController->>NSPanel: level .floating, makeFirstResponder
SocketCLI->>TerminalController: quick_terminal.toggle/show/hide
TerminalController->>AppDelegate: toggleQuickTerminalVisibility(activateApp: false)
AppDelegate->>QuickTerminalController: toggle(activateApp: false)
QuickTerminalController->>NSPanel: orderFront (no makeKeyAndOrderFront)
Note over QuickTerminalController,NSPanel: macOS app focus NOT stolen
TerminalController->>SocketCLI: status payload (visible, position, sizes, auto_hide)
NSPanel->>QuickTerminalController: windowDidResignKey (autoHide=true)
QuickTerminalController->>NSPanel: animate (final→hidden frame, α 1→0)
NSPanel-->>QuickTerminalController: completionHandler (Task @MainActor)
QuickTerminalController->>NSPanel: orderOut
QuickTerminalController->>AppDelegate: previousFrontmostApp.activate (if set)
|
There was a problem hiding this comment.
1 issue found across 10 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/AppDelegate.swift">
<violation number="1" location="Sources/AppDelegate.swift:2128">
P2: Quick-terminal show/hide requests are dropped during animations: `show`/`hide` both `return` when `isTransitioning == true` and there’s no pending action replay in the completion handlers. A rapid `show` then `hide` (or toggle) will ignore the hide and leave the panel visible, causing automation-visible state mismatches.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
cmuxTests/WorkspaceUnitTests.swift (1)
188-194: Add key-equivalent conversion assertions for the backtick shortcut.Since
`is a special key in menu routing paths, consider assertingkeyEquivalent/menuItemKeyEquivalentandeventModifiershere too (similar to other shortcut tests) to catch regressions earlier.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmuxTests/WorkspaceUnitTests.swift` around lines 188 - 194, The test currently checks only the raw KeyboardShortcutSettings.Action.toggleQuickTerminal.defaultShortcut fields; add assertions that its keyEquivalent (or menuItemKeyEquivalent) equals "`" and that the computed eventModifiers/menu event modifiers match the expected set (command + option, no shift/control) so the shortcut's keyEquivalent/menuItemKeyEquivalent and eventModifiers are validated like the other shortcut tests; locate the shortcut via KeyboardShortcutSettings.Action.toggleQuickTerminal.defaultShortcut and assert both the menu key equivalent property and the event modifier representation to catch routing regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CHANGELOG.md`:
- Line 8: In CHANGELOG.md, escape the literal backtick in the shortcut text so
Markdown doesn't start an inline code span: replace the unescaped sequence
"Option+Command+`" with an escaped backtick form (e.g., "Option+Command+\`") or
wrap the whole shortcut safely as a code span that includes the escaped backtick
(e.g., `Option+Command+\``) so the "(default: ...)" text renders and lints
correctly.
In `@CLI/cmux.swift`:
- Around line 2596-2599: The parsing treats "--" as a subcommand when it's the
first token; update the logic that computes subcommand and remaining to
normalize commandArgs by removing a leading "--" (and any leading-only
separators) before selecting subcommand. Specifically, in the block that reads
commandArgs and assigns subcommand and remaining, first strip a leading "--"
from commandArgs (or skip all leading elements equal to "--"), then compute
subcommand = commandArgs.first?.lowercased() ?? "toggle" and remaining =
Array(commandArgs.dropFirst()).filter { $0 != "--" }; keep the existing
unknown-argument error throw but base it on the normalized args so "cmux
quick-terminal -- status" correctly interprets "status" as the subcommand.
In `@README.md`:
- Line 138: The markdown table row contains a raw backtick in the shortcut text
("⌥ ⌘ `") which can break rendering; update the README table row to escape the
backtick (e.g., use "⌥ ⌘ \`") or wrap the key(s) in <kbd> tags (e.g.,
<kbd>⌥</kbd> <kbd>⌘</kbd> <kbd>`</kbd>) so the shortcut displays correctly; edit
the cell that currently contains "⌥ ⌘ `".
In `@Sources/AppDelegate.swift`:
- Around line 9636-9641: The quick-terminal toggle currently consumes bare Cmd+`
(keyCode 50) or Cmd+Shift+` before AppKit can perform window cycling; before
calling matchShortcut/toggleQuickTerminalVisibility in AppDelegate.swift, add a
guard that rejects events where event.keyCode == 50 and the modifier flags
represent Command alone or Command+Shift (i.e., contains .command and optionally
.shift but no other modifiers), returning false so AppKit can handle native
window switching; alternatively, ensure KeyboardShortcutSettings.shortcut(for:
.toggleQuickTerminal) cannot be set to that key combo, but the immediate fix is
to check event.keyCode == 50 and modifierFlags and skip handling in this branch.
In `@Sources/TerminalController.swift`:
- Around line 141-143: The focus-intent allowlist erroneously includes
quick_terminal.toggle, quick_terminal.show, and quick_terminal.hide which grants
socket/CLI automation the ability to change in-app focus; remove these three
command entries from the allowlist (the list/array that currently contains
"quick_terminal.toggle", "quick_terminal.show", "quick_terminal.hide") so only
explicit focus-intent commands remain (e.g., window.focus, workspace.select/...,
surface.focus, pane.focus, browser focus commands and v1 focus equivalents);
after removal, run existing focus-related unit/integration checks to ensure no
other code depends on those quick_terminal entries and update any test mocks or
documentation referencing them.
---
Nitpick comments:
In `@cmuxTests/WorkspaceUnitTests.swift`:
- Around line 188-194: The test currently checks only the raw
KeyboardShortcutSettings.Action.toggleQuickTerminal.defaultShortcut fields; add
assertions that its keyEquivalent (or menuItemKeyEquivalent) equals "`" and that
the computed eventModifiers/menu event modifiers match the expected set (command
+ option, no shift/control) so the shortcut's
keyEquivalent/menuItemKeyEquivalent and eventModifiers are validated like the
other shortcut tests; locate the shortcut via
KeyboardShortcutSettings.Action.toggleQuickTerminal.defaultShortcut and assert
both the menu key equivalent property and the event modifier representation to
catch routing regressions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 24332016-970b-4090-a95e-b058dbf0ad31
📒 Files selected for processing (10)
CHANGELOG.mdCLI/cmux.swiftREADME.mdResources/Localizable.xcstringsSources/AppDelegate.swiftSources/ContentView.swiftSources/KeyboardShortcutSettings.swiftSources/TerminalController.swiftSources/cmuxApp.swiftcmuxTests/WorkspaceUnitTests.swift
There was a problem hiding this comment.
♻️ Duplicate comments (2)
Sources/AppDelegate.swift (2)
9636-9641:⚠️ Potential issue | 🟠 MajorDon't let this bypass palette protection or native `Cmd+`` window cycling.
Because this branch runs before the palette-consumption guard and doesn't reserve
keyCode == 50, a user-assigned quick-terminal shortcut can still fire while the command palette is typing, and bareCmd+\`` /Cmd+Shift+`` can preempt AppKit's native window switcher.Based on learnings, "Cmd+` (command-backtick) should not be routed directly" and "when the command palette is visible ... do not reorder shortcut handlers to bypass this guard."🛡️ Suggested guard
+ let isReservedWindowCycleShortcut = + event.keyCode == 50 && + (normalizedFlags == [.command] || normalizedFlags == [.command, .shift]) + // Allow quick-terminal toggle even when the key event comes from the quick // panel itself (which is not a main terminal window context). - if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .toggleQuickTerminal)) { + if !commandPaletteEffectiveInTargetWindow, + !isReservedWindowCycleShortcut, + matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .toggleQuickTerminal)) { _ = toggleQuickTerminalVisibility() return true }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/AppDelegate.swift` around lines 9636 - 9641, The current early-return branch that matches matchShortcut(... shortcut: .toggleQuickTerminal) and calls toggleQuickTerminalVisibility() must not bypass the command-palette guard or allow native Cmd+` (keyCode == 50) to be handled here; update the condition to first check the palette-consumption guard (the same guard used elsewhere to prevent palette key handling) and explicitly ignore events with keyCode == 50 (and when necessary with shift modifier for Cmd+Shift+`) before calling toggleQuickTerminalVisibility(); locate the matchShortcut call, KeyboardShortcutSettings.shortcut(for: .toggleQuickTerminal) lookup, and toggleQuickTerminalVisibility invocation and add the additional checks so the quick-terminal shortcut is suppressed while the palette is visible and does not preempt AppKit window cycling.
2119-2125:⚠️ Potential issue | 🟠 MajorReturn whether the quick-terminal request actually ran.
show/hidecan legitimately no-op here (isTransitioning, already visible/hidden, orensurePanel()failure). ReturningVoidmakes those dropped requests indistinguishable from a real state change for automation callers.Also applies to: 2127-2130, 2191-2198
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/AppDelegate.swift` around lines 2119 - 2125, Change the toggle/show/hide call paths to return a Bool indicating whether the requested show/hide actually ran (instead of Void) so automation callers can distinguish no-ops; update toggle(activateApp:) to return Bool and return the result of calling show(activateApp:) or hide(restorePreviousApp:), and similarly change the other related methods (the other toggle overloads and the show/hide functions referenced in this diff) to return Bool and propagate true only when the action proceeds (i.e. not isTransitioning, not already in the target state, and ensurePanel() succeeded) and false when they legitimately no-op.
🧹 Nitpick comments (1)
Sources/AppDelegate.swift (1)
5764-5789: Default the scriptable quick-terminal APIs to non-activating behavior.These wrappers are the automation-facing entry points, but their defaults still opt into activation/restoration. Making the safe behavior the default here reduces the chance of a future CLI/socket caller regressing the no-focus-steal contract.
As per coding guidelines, "Socket/CLI commands must not steal macOS app focus (no app activation/window raising side effects)."♻️ Suggested change
- func toggleQuickTerminalVisibility(activateApp: Bool = true) -> Bool { + func toggleQuickTerminalVisibility(activateApp: Bool = false) -> Bool { quickTerminalController.toggle(activateApp: activateApp) return true } - func showQuickTerminal(activateApp: Bool = true) -> Bool { + func showQuickTerminal(activateApp: Bool = false) -> Bool { quickTerminalController.show(activateApp: activateApp) return true } - func hideQuickTerminal(restorePreviousApp: Bool = true) -> Bool { + func hideQuickTerminal(restorePreviousApp: Bool = false) -> Bool { quickTerminalController.hide(restorePreviousApp: restorePreviousApp) return true } `@objc` func toggleQuickTerminal(_ sender: Any?) { _ = sender - _ = toggleQuickTerminalVisibility() + _ = toggleQuickTerminalVisibility(activateApp: true) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/AppDelegate.swift` around lines 5764 - 5789, Change the scriptable quick-terminal wrapper defaults to avoid activating or restoring the app: update the default parameter values on toggleQuickTerminalVisibility(activateApp: Bool = true), showQuickTerminal(activateApp: Bool = true), and hideQuickTerminal(restorePreviousApp: Bool = true) to false, and ensure the ObjC wrapper toggleQuickTerminal(_:) calls toggleQuickTerminalVisibility(activateApp: false) (or otherwise forwards false) so automation/CLI/socket callers do not cause app activation; locate these symbols: toggleQuickTerminalVisibility, showQuickTerminal, hideQuickTerminal, and toggleQuickTerminal to make the edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@Sources/AppDelegate.swift`:
- Around line 9636-9641: The current early-return branch that matches
matchShortcut(... shortcut: .toggleQuickTerminal) and calls
toggleQuickTerminalVisibility() must not bypass the command-palette guard or
allow native Cmd+` (keyCode == 50) to be handled here; update the condition to
first check the palette-consumption guard (the same guard used elsewhere to
prevent palette key handling) and explicitly ignore events with keyCode == 50
(and when necessary with shift modifier for Cmd+Shift+`) before calling
toggleQuickTerminalVisibility(); locate the matchShortcut call,
KeyboardShortcutSettings.shortcut(for: .toggleQuickTerminal) lookup, and
toggleQuickTerminalVisibility invocation and add the additional checks so the
quick-terminal shortcut is suppressed while the palette is visible and does not
preempt AppKit window cycling.
- Around line 2119-2125: Change the toggle/show/hide call paths to return a Bool
indicating whether the requested show/hide actually ran (instead of Void) so
automation callers can distinguish no-ops; update toggle(activateApp:) to return
Bool and return the result of calling show(activateApp:) or
hide(restorePreviousApp:), and similarly change the other related methods (the
other toggle overloads and the show/hide functions referenced in this diff) to
return Bool and propagate true only when the action proceeds (i.e. not
isTransitioning, not already in the target state, and ensurePanel() succeeded)
and false when they legitimately no-op.
---
Nitpick comments:
In `@Sources/AppDelegate.swift`:
- Around line 5764-5789: Change the scriptable quick-terminal wrapper defaults
to avoid activating or restoring the app: update the default parameter values on
toggleQuickTerminalVisibility(activateApp: Bool = true),
showQuickTerminal(activateApp: Bool = true), and
hideQuickTerminal(restorePreviousApp: Bool = true) to false, and ensure the ObjC
wrapper toggleQuickTerminal(_:) calls toggleQuickTerminalVisibility(activateApp:
false) (or otherwise forwards false) so automation/CLI/socket callers do not
cause app activation; locate these symbols: toggleQuickTerminalVisibility,
showQuickTerminal, hideQuickTerminal, and toggleQuickTerminal to make the edits.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 32da7582-8e06-454e-a983-b6f00dc0cbc9
📒 Files selected for processing (2)
CLI/cmux.swiftSources/AppDelegate.swift
✅ Files skipped from review due to trivial changes (1)
- CLI/cmux.swift
|
Hi! The PR is ready for review. All checks pass except Vercel (likely permission-related). Thank you! :) |
Implements Quick Terminal across app shortcut/menu/settings, command palette, socket v2, and CLI (cmux quick-terminal).
Also ensures socket/CLI quick terminal commands do not steal macOS focus and wraps animation completion state updates on MainActor.
Adds README and CHANGELOG notes.
Closes #327
Summary
Why
Testing
Review Trigger (Copy/Paste as PR comment)
Checklist
Summary by cubic
Adds a drop-down Quick Terminal panel with keyboard/menu/palette controls and scriptable CLI/socket APIs. Automation-safe: CLI/socket calls don’t steal macOS focus; supports configurable position, size, auto-hide, and restores the previous app when hiding.
New Features
cmux quick-terminal [toggle|show|hide|status]andcmux --json quick-terminal status(prints visibility and settings).quick_terminal.toggle,quick_terminal.show,quick_terminal.hide,quick_terminal.status.Bug Fixes
Written for commit cd779b1. Summary will update on new commits.
Summary by CodeRabbit
New Features
Documentation
Tests