Repository navigation
Fix SSH tmux terminal typing hot-path overhead - #4686
austinywang wants to merge 38 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReduces per-keystroke work, caches notification-hook resolution by starting-directory, coalesces unchanged terminal-title posts, moves desktop-notification enqueueing to the MainActor with a cached route snapshot, tracks panel-level dismissible activity, and adds DEBUG input→render timing instrumentation. ChangesTyping latency optimization: per-keystroke work, config caching, title coalescing, and dismissal guards
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (4 errors, 2 warnings, 2 inconclusive)
✅ Passed checks (9 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 trims the SSH/tmux typing hot path by skipping right-sidebar shortcut evaluation for plain key events (no modifiers, no direct-key-code binding), deduplicates per-surface Ghostty title notifications with a bounded FIFO cache, delivers desktop notifications on the main actor with a fresh route snapshot, and caches
Confidence Score: 4/5Safe to merge after removing the ForTesting callback wired into four production query functions in TerminalNotificationStore; all other changes are well-structured and covered by new tests. The shortcut skip, title deduplication, notification routing, and config caching changes are correctness-sound and well-tested. The one issue is Sources/TerminalNotificationStore.swift — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
KD[keyDown event] --> SC{shouldCheckModeShortcut?\nmodifiers held or\ndirect-key-code binding}
SC -- No --> GK[send key to Ghostty\nskip shortcut walk]
SC -- Yes --> MW[walk modeShortcutCandidates\ncached per shortcut settings]
MW --> GK
GK -->|title update| TD[GhosttyTitleNotificationDispatcher\nbounded FIFO dedup]
TD -->|title changed| MQ[DispatchQueue.main.async\npost ghosttyDidSetTitle]
TD -->|duplicate| DROP[dropped]
GK -->|DESKTOP_NOTIFICATION| RM{performOnMain\nappDesktopNotificationRoute}
RM -->|.deliver| DA[deliverAppDesktopNotificationIfNeeded\nTerminalNotificationStore.addNotification]
RM -->|.suppress| TR[return true - handled]
RM -->|.fallThrough| FA[return false - Ghostty fallback]
subgraph DismissalGuard
DI[dismissNotificationOnTerminalInteraction] --> HA{storeHasDismissibleActivity?}
HA -- No --> EXIT[return false early]
HA -- Yes --> LOOKUP[check manual/panel/restored unread]
end
subgraph ConfigCache
NH[notificationHooks startingFrom cwd] --> CK{cache hit?\nconfigPaths + signatures match}
CK -- Yes --> touch[touch LRU entry\nreturn cached hooks]
CK -- No --> PARSE[parse config hierarchy\nstore in 128-entry LRU cache]
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
KD[keyDown event] --> SC{shouldCheckModeShortcut?\nmodifiers held or\ndirect-key-code binding}
SC -- No --> GK[send key to Ghostty\nskip shortcut walk]
SC -- Yes --> MW[walk modeShortcutCandidates\ncached per shortcut settings]
MW --> GK
GK -->|title update| TD[GhosttyTitleNotificationDispatcher\nbounded FIFO dedup]
TD -->|title changed| MQ[DispatchQueue.main.async\npost ghosttyDidSetTitle]
TD -->|duplicate| DROP[dropped]
GK -->|DESKTOP_NOTIFICATION| RM{performOnMain\nappDesktopNotificationRoute}
RM -->|.deliver| DA[deliverAppDesktopNotificationIfNeeded\nTerminalNotificationStore.addNotification]
RM -->|.suppress| TR[return true - handled]
RM -->|.fallThrough| FA[return false - Ghostty fallback]
subgraph DismissalGuard
DI[dismissNotificationOnTerminalInteraction] --> HA{storeHasDismissibleActivity?}
HA -- No --> EXIT[return false early]
HA -- Yes --> LOOKUP[check manual/panel/restored unread]
end
subgraph ConfigCache
NH[notificationHooks startingFrom cwd] --> CK{cache hit?\nconfigPaths + signatures match}
CK -- Yes --> touch[touch LRU entry\nreturn cached hooks]
CK -- No --> PARSE[parse config hierarchy\nstore in 128-entry LRU cache]
end
Reviews (27): Last reviewed commit: "fix: cache right-sidebar mode shortcuts" | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/RightSidebarPanelView.swift`:
- Line 68: Replace the double-negative intersection check by using
isDisjoint(with:) on the flags collection: instead of computing
flags.intersection(shortcutRelevantModifiers).isEmpty, call isDisjoint(with:) on
flags with shortcutRelevantModifiers and negate that result to express “not
disjoint”; update the return in the containing method accordingly (references:
flags and shortcutRelevantModifiers).
In `@Sources/TerminalNotificationStore.swift`:
- Around line 1139-1145: Replace the duplicated workspace unread checks in
hasDismissibleActivity(forTabId:) by reusing the existing unreadCount(forTabId:)
and workspaceIsUnread(forTabId:) helpers: change the function body to return
unreadCount(forTabId: tabId) > 0 || workspaceIsUnread(forTabId: tabId), removing
the direct references to indexes.unreadCountByTabId,
focusedReadIndicatorByTabId, manualUnreadWorkspaceIds,
panelDerivedUnreadWorkspaceIds, and restoredUnreadWorkspaceIds to centralize the
logic in the existing methods.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6827af83-0bec-4f86-9571-367e1025fc37
📒 Files selected for processing (8)
Sources/CmuxConfig.swiftSources/GhosttyTerminalView.swiftSources/RightSidebarPanelView.swiftSources/TabManager.swiftSources/TerminalNotificationStore.swiftcmuxTests/ShortcutAndCommandPaletteTests.swiftcmuxTests/TabManagerUnitTests.swiftcmuxTests/WorkspaceManualUnreadTests.swift
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 4176-4180: The fallback hard-coded title "Terminal" used when
building the GhosttyDesktopNotificationTarget should be localized; replace the
literal with String(localized: "notification.fallback_title", defaultValue:
"Terminal") where the GhosttyDesktopNotificationTarget is returned (see
GhosttyDesktopNotificationTarget and owningManager.titleForTab(tabId)); also add
the key "notification.fallback_title" with the English and Japanese entries to
Resources/Localizable.xcstrings so the UI string is translated for supported
locales.
- Around line 4346-4350: The code currently calls performOnMain to synchronously
get GhosttyApp.appDesktopNotificationTarget() which may block via
DispatchQueue.main.sync; instead remove the performOnMain call and the
synchronous guard, return true immediately if you need to continue, and move the
resolution of appDesktopNotificationTarget() into the asynchronous Task {
`@MainActor` ... } block (i.e. call GhosttyApp.appDesktopNotificationTarget()
inside that Task and bail from the Task if nil). Update usage of
notificationTarget, command, and actionBody inside the Task so the callback path
is non-blocking and all main-thread access happens within the `@MainActor` Task.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 72f6c20c-e716-432c-86f0-d0a4faf77a21
📒 Files selected for processing (5)
Sources/GhosttyTerminalView.swiftSources/TerminalNotificationStore.swiftSources/Workspace.swiftcmuxTests/TabManagerUnitTests.swiftcmuxTests/WorkspaceManualUnreadTests.swift
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/GhosttyTerminalView.swift (1)
4227-4234:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDon't cache the fallback tab title in the route snapshot.
tabTitleis now part of the cached route, but title changes are not one of the snapshot invalidation triggers. That means the first app-target notification with an empty title after a tab title update will still use the previous title. Cache only stable routing IDs here and resolvetitleForTab(...)inside the@MainActordelivery path, or invalidate on.ghosttyDidSetTitle.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Sources/GhosttyTerminalView.swift` around lines 4227 - 4234, The snapshot is caching a mutable tabTitle causing stale titles; instead remove tabTitle from the cached route and only cache stable routing IDs (e.g. tabId and surfaceId) in GhosttyDesktopNotificationTarget, then resolve owningManager.titleForTab(tabId) inside the `@MainActor` delivery path when building the actual notification (i.e. call titleForTab(...) during delivery rather than when creating the route snapshot); alternatively if you must keep tabTitle in the snapshot add snapshot invalidation on .ghosttyDidSetTitle, but prefer resolving the title at delivery time to avoid caching mutable UI state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 4426-4427: The callback currently calls
cachedAppDesktopNotificationRouteForCallback() then unconditionally calls
refreshAppDesktopNotificationRouteSnapshot(), causing a refresh to be enqueued
for every notification; modify the callback to coalesce refreshes by adding a
refreshScheduled boolean guard (e.g., set refreshScheduled = true when
scheduling a main-queue refresh and clear it inside the scheduled work) before
calling refreshAppDesktopNotificationRouteSnapshot(), or instead remove the
direct call and rely on existing state-change observers to trigger
refreshAppDesktopNotificationRouteSnapshot() so only the latest route causes a
snapshot update.
---
Outside diff comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 4227-4234: The snapshot is caching a mutable tabTitle causing
stale titles; instead remove tabTitle from the cached route and only cache
stable routing IDs (e.g. tabId and surfaceId) in
GhosttyDesktopNotificationTarget, then resolve owningManager.titleForTab(tabId)
inside the `@MainActor` delivery path when building the actual notification (i.e.
call titleForTab(...) during delivery rather than when creating the route
snapshot); alternatively if you must keep tabTitle in the snapshot add snapshot
invalidation on .ghosttyDidSetTitle, but prefer resolving the title at delivery
time to avoid caching mutable UI state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: d43ed111-1851-436d-87e7-ed124ce4316c
📒 Files selected for processing (6)
Resources/Localizable.xcstringsSources/AppDelegate.swiftSources/GhosttyTerminalView.swiftSources/TabManager.swiftSources/TerminalNotificationStore.swiftcmuxTests/WorkspaceManualUnreadTests.swift
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Dismiss stale CodeRabbit changes-requested review: all associated threads are resolved, latest CI is green, and a fresh CodeRabbit review request completed without new findings.
Summary
Fixes #4681.
Verification
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.Note
Medium Risk
Touches Ghostty callback threading, notification routing/suppression, and workspace dismissal semantics; behavior is well covered by new tests but affects interactive terminal paths.
Overview
This PR trims work on the SSH/tmux typing hot path and tightens terminal notification behavior.
Typing and shortcuts: Right-sidebar mode shortcuts are only evaluated when Command, Control, or Option is held, so plain key input no longer walks shortcut bindings.
Notification dismissal:
TabManagerbails out early unlessTerminalNotificationStorereports dismissible activity for the tab. The store tracks panel-only dismissible badges separately from workspace unread counts, and workspace sync sets that flag when panels show unread/restored indicators without necessarily bumping the workspace unread total.Config:
CmuxConfigStorecaches notification hooks by standardized working directory and clears the cache on reload.Ghostty / desktop notifications: Per-surface title notifications are deduped (bounded cache). App-level desktop notifications use a cached route snapshot (refreshed on workspace selection, surface focus, and main-window context changes), are handled asynchronously on the main actor instead of blocking Ghostty callbacks, and use localized
notification.fallback_title. Surface-level delivery uses the same fallback string.Startup: The automation socket can start when the initial tab model is ready, not only after AppKit windows exist.
DEBUG: Optional key-to-render timing after successful Ghostty key sends; tick/render demand treats typing timing as active when enabled.
Tests cover shortcut filtering, title deduplication, dismissible activity, and empty-terminal dismissal fast paths.
Reviewed by Cursor Bugbot for commit 43da013. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Reduce SSH/tmux typing lag by trimming hot‑path work, caching right‑sidebar mode shortcuts, and routing terminal notifications on the main thread with correct targets; dedupe per‑surface title updates. Fixes #4681.
Performance
notification.fallback_title. Keep rendered‑frame notifications active when timing is enabled...traversal, preserve trailing‑space paths; clear caches on config reload.Bug Fixes
cmuxSelectedWorkspaceDidChangeon selection and active‑context changes to keep routing in sync.Written for commit 1c59c03. Summary will update on new commits.
Summary by CodeRabbit
Bug Fixes
Performance
New Features
Tests
Localization