Skip to content

Fix SSH tmux terminal typing hot-path overhead - #4686

Closed
austinywang wants to merge 38 commits into
mainfrom
issue-4681-ssh-tmux-typing-lag
Closed

austinywang wants to merge 38 commits into
mainfrom
issue-4681-ssh-tmux-typing-lag

Conversation

@austinywang

@austinywang austinywang commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • skip right-sidebar mode shortcut resolution for plain terminal typing before shortcut lookup/layout work
  • add a cheap notification dismissal activity guard and cache cmux notification hook resolution by cwd
  • dedupe per-surface Ghostty title notifications and add DEBUG key-to-render timing logs
  • deliver terminal desktop notifications asynchronously on the main actor instead of blocking Ghostty callbacks

Fixes #4681.

Verification

  • Not run locally per task instructions; CI is the verification path.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with 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: TabManager bails out early unless TerminalNotificationStore reports 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: CmuxConfigStore caches 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

    • Cache and preload right‑sidebar mode shortcut bindings; skip lookup for plain typing and only check when modifiers are held or a direct key‑code binding exists (supports Shift‑only and modifierless keys).
    • Deduplicate per‑surface title notifications with bounded tracking; allow the same title after a surface replacement.
    • Deliver terminal desktop notifications on the main actor; decide route at delivery using current workspace selection and focused surface; use notification.fallback_title. Keep rendered‑frame notifications active when timing is enabled.
    • Cache notification hook resolution by standardized working directory; validate via file size/mtime; refresh on edits and new ancestor configs; ignore .. traversal, preserve trailing‑space paths; clear caches on config reload.
    • Start the automation socket as soon as the initial model exists.
  • Bug Fixes

    • Recheck suppression before delivering app/surface notifications; fall through when the workspace/surface is unresolved; preserve fallback routing and dock/tmux‑mirror pane notifications.
    • Emit cmuxSelectedWorkspaceDidChange on selection and active‑context changes to keep routing in sync.
    • Add a fast guard to skip dismissal work when a tab has no dismissible activity; track panel‑only visual badges separately and clear them on interaction/close.

Written for commit 1c59c03. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Prevent duplicate title notifications; improved desktop-notification suppression and dismissal checks
    • Keyboard shortcuts ignore plain typing by filtering irrelevant modifier keys
  • Performance

    • Per-directory caching for resolved notification hooks; caches cleared on config reload
  • New Features

    • More robust desktop-notification routing with suppression and cached route snapshots
    • Workspace selection now emits selection-change notifications
  • Tests

    • Added tests for title deduplication and dismissible-activity tracking
  • Localization

    • Added fallback notification title (English/Japanese)

Review Change Stack

@vercel

vercel Bot commented May 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 4, 2026 4:02am
cmux-staging Building Building Preview, Comment Jul 4, 2026 4:02am

@coderabbitai

coderabbitai Bot commented May 24, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Reduces 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.

Changes

Typing latency optimization: per-keystroke work, config caching, title coalescing, and dismissal guards

Layer / File(s) Summary
Mode shortcut modifier filtering
Sources/RightSidebarPanelView.swift, cmuxTests/ShortcutAndCommandPaletteTests.swift
RightSidebarMode.modeShortcut(for:) now short-circuits when event modifiers lack relevant flags via shouldCheckModeShortcut(for:), avoiding unnecessary shortcut lookup calls on each keyDown; tests ensure plain typing doesn't trigger lookups.
Notification hook caching by directory
Sources/CmuxConfig.swift
CmuxConfigStore caches resolved notification hooks per normalized starting-directory key and maintains a separate nil-key cache; notificationHooks(startingFrom:) reads/writes these caches and loadAll() clears them on reload.
Title notification coalescing
Sources/GhosttyTerminalView.swift, cmuxTests/TabManagerUnitTests.swift
Introduces GhosttyTitleNotificationDispatcher to deduplicate .ghosttyDidSetTitle postings per (tabId, surfaceId) and updates surface-target set-title handler to use it; unit tests cover suppression and eviction.
App desktop-notification route & MainActor enqueue
Sources/GhosttyTerminalView.swift
Adds cached app desktop-notification route snapshot with main-actor observers and updates app- and surface-target desktop-notification handlers to consult the cached route and enqueue delivery into TerminalNotificationStore on @MainActor.
Debug input/render timing instrumentation
Sources/GhosttyTerminalView.swift
DEBUG-only PendingInputRenderTiming state and recording/logging are added; GhosttyRenderedFrameNotificationDemand.isActive is forced during typing timing probes, and sendTimedGhosttyKey records dispatch timing so a subsequent rendered-frame flush can log input→render latency.
Dismissible activity tracking and guard
Sources/TerminalNotificationStore.swift, Sources/TabManager.swift, Sources/Workspace.swift, Sources/AppDelegate.swift, cmuxTests/WorkspaceManualUnreadTests.swift
Adds panelDismissibleActivityWorkspaceIds and hasDismissibleActivity(forTabId:) to TerminalNotificationStore; Workspace.syncPanelDerivedWorkspaceUnread() sets panel dismissible activity separately from panel-derived workspace unread; TabManager.dismissNotification early-returns when no dismissible activity; TabManager and AppDelegate post .cmuxSelectedWorkspaceDidChange; tests exercise state transitions including visual-only restored panel unread behavior.
Localization
Resources/Localizable.xcstrings
Adds notification.fallback_title translations for English and Japanese.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#3986: Modifies GhosttyTerminalView key-input/debug instrumentation paths; overlaps with timed key-send and render-correlation changes.
  • manaflow-ai/cmux#4550: Related changes to workspace/tab unread/badge derivation and session-restore handling; overlaps with panel-derived unread and restored-unread logic.
  • manaflow-ai/cmux#4458: Related app startup socket/listener timing adjustments affecting createMainWindow startup flow.

Suggested reviewers

  • jesstelford

Poem

🐰
I hop on keys and quiet the noise,
Cache the hooks and hush repeated voice.
Titles only wake when new and bright,
Tasks now queue to keep the main light.
— a rabbit, timing each tiny bite


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (4 errors, 2 warnings, 2 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error NSLock added in GHOSTTY_ACTION_SET_TITLE and notification callbacks. Terminal rendering callbacks are latency-sensitive and should not use blocking synchronization. Replace NSLock with actor-based synchronization or lock-free algorithms to avoid blocking in Ghostty callbacks.
Cmux Swift File And Package Boundaries ❌ Error PR adds +277 lines to GhosttyTerminalView.swift (already 1,821 over budget), exceeding >250-line threshold. GhosttyTitleNotificationDispatcher is extractable but stays in app target. Extract GhosttyTitleNotificationDispatcher into package target or remove >200 lines from GhosttyTerminalView.swift via other responsibility extraction.
Cmux Full Internationalization ❌ Error New localization entry notification.fallback_title added for desktop notification title fallback has only 2 locales (en, ja) but Localizable.xcstrings supports 20 locales; missing 18 translations. Add translations for missing locales (ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant) to the notification.fallback_title entry in Resources/Localizable.xcstrings.
Cmux Architecture Rethink ❌ Error Mutable cache panelDismissibleActivityWorkspaceIds duplicates panel-unread state ownership already in Workspace; requires setPanelDismissibleUnreadActivity calls to stay synchronized. Compute panelDismissibleActivityWorkspaceIds on-demand from existing owned state instead of accepting mutations.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning Only Summary and Verification are filled; the required Testing, Demo Video, Review Trigger, and Checklist sections are missing or incomplete. Add the missing template sections: Testing, Demo Video, Review Trigger, and Checklist, and briefly state what was tested and verified.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Cmux Swift @Concurrent ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (9 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Code changes directly address all primary objectives from #4681: skip shortcut resolution for plain typing, add dismissal guard, cache notification hooks, deduplicate titles, and move notifications off blocking paths.
Out of Scope Changes check ✅ Passed All changes are scoped to reducing typing latency and optimizing notification delivery paths; no extraneous refactoring or unrelated feature additions.
Cmux No Hacky Sleeps ✅ Passed No violations found. Sleep calls introduced in web/scripts/db-local.sh and dev-local.sh are for development-only helpers, not production application runtime code as scoped by the rule.
Cmux Swift Concurrency ✅ Passed Fire-and-forget Tasks for desktop notifications cross callback boundary (allowed per guidelines); all Task usage is at Ghostty C callback boundary for main-actor work.
Cmux Swift Logging ✅ Passed No new logging violations found; the PR adds no print/NSLog/debugPrint/dump/file-logging statements or MainActor-coupled Logger declarations to production Swift code.
Cmux User-Facing Error Privacy ✅ Passed PR adds only a generic fallback notification title; no vendor names, credentials, environment variables, or internal details exposed in user-facing text.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state layout violations. All changes are private caches/tracking sets (not @Published), non-SwiftUI utility classes, or callback operations outside view code.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new NSWindow/NSPanel/NSWindowController added. New types are notification helpers, not windows. Lint script passes with no violations.
Title check ✅ Passed The title is concise and matches the PR’s main goal of reducing terminal typing overhead in SSH/tmux paths.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4681-ssh-tmux-typing-lag

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread Sources/TabManager.swift Outdated
Comment thread Sources/GhosttyTerminalView.swift Outdated
@greptile-apps

greptile-apps Bot commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This 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 CmuxConfig notification-hook resolution by working directory with file-signature invalidation.

  • Shortcut skip: RightSidebarMode.shouldCheckModeShortcut gates the mode-shortcut walk on modifier key presence or direct-key-code bindings; a private-static candidate cache invalidates on KeyboardShortcutSettings.didChangeNotification.
  • Notification dismissal guard: TerminalNotificationStore tracks panel-only dismissible badges (panelDismissibleActivityWorkspaceIds) separately from workspace unread counts; NotificationDismissalModel bails out early via storeHasDismissibleActivity before any lookup work.
  • Desktop notifications: GhosttyTitleNotificationDispatcher deduplicates title posts (NSLock-safe, 1024-entry FIFO); app-level delivery resolves route (deliver/suppress/fallThrough) on the main actor before committing; notification.fallback_title is now localised in both en and ja.

Confidence Score: 4/5

Safe 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 detailedDismissLookupObserverForTesting: a #if DEBUG-guarded closure named …ForTesting wired directly into four production query functions in Sources/TerminalNotificationStore.swift, crossing the rule against test-observability seams in shipping source.

Sources/TerminalNotificationStore.swift — the detailedDismissLookupObserverForTesting property and its four #if DEBUG call-sites in production query functions.

Important Files Changed

Filename Overview
Sources/TerminalNotificationStore.swift Adds panelDismissibleActivityWorkspaceIds tracking and hasDismissibleActivity guard; introduces a #if DEBUG test-observability callback (detailedDismissLookupObserverForTesting) wired into four production query methods, violating the no-test-seam-in-production-source rule.
Sources/GhosttyTerminalView.swift Adds GhosttyTitleNotificationDispatcher (bounded FIFO cache, NSLock-safe dedup), GhosttyDesktopNotificationRoute snapshot helpers, and #if DEBUG key-to-render timing; refactors app/surface notification delivery to resolve route on main actor before async dispatch. Logic looks correct.
Sources/CmuxConfig.swift Adds notification-hooks LRU cache keyed by standardised working directory with file-signature invalidation (stat per config path on miss); cache cleared on reload. notificationHookCacheKey is a thin alias for notificationHookSearchDirectory (redundant but harmless). Logic looks correct for bounded 128-entry cache.
Sources/RightSidebarPanelView.swift Adds shouldCheckModeShortcut modifier guard (skips shortcut walk for plain typing) and private static shortcut candidate cache with KeyboardShortcutSettings.didChangeNotification invalidation. Cache is private-static on the type extension; observer lifecycle is correct (static retention, one-time setup).
Sources/Workspace+PanelLifecycle.swift Adds @discardableResult and change-tracking return values to removeAgentPIDOwnership/recordAgentPIDOwnership; calls clearRestoredUnreadIndicator on panel close to keep panelDismissibleActivity in sync.
Sources/AppDelegate.swift Posts cmuxSelectedWorkspaceDidChange on workspace selection changes; starts automation socket on initial model ready instead of waiting for AppKit window construction.
Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift Adds storeHasDismissibleActivity early-exit guard before executing dismissal work, short-circuiting the lookup chain when no dismissible state exists.
Resources/Localizable.xcstrings Adds notification.fallback_title, rightSidebar.mode.history, shortcut.switchRightSidebarToHistory.label, and three menu/command strings, all with both en and ja translations.

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
Loading
%%{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
Loading

Reviews (27): Last reviewed commit: "fix: cache right-sidebar mode shortcuts" | Re-trigger Greptile

coderabbitai[bot]
coderabbitai Bot previously requested changes May 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8627dc9 and e61ca3d.

📒 Files selected for processing (8)
  • Sources/CmuxConfig.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/RightSidebarPanelView.swift
  • Sources/TabManager.swift
  • Sources/TerminalNotificationStore.swift
  • cmuxTests/ShortcutAndCommandPaletteTests.swift
  • cmuxTests/TabManagerUnitTests.swift
  • cmuxTests/WorkspaceManualUnreadTests.swift

Comment thread Sources/RightSidebarPanelView.swift Outdated
Comment thread Sources/TerminalNotificationStore.swift
Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment thread cmuxTests/ShortcutAndCommandPaletteTests.swift
Comment thread Sources/GhosttyTerminalView.swift
Comment thread Sources/TerminalNotificationStore.swift
Comment thread Sources/CmuxConfig.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e61ca3d and 6b02e56.

📒 Files selected for processing (5)
  • Sources/GhosttyTerminalView.swift
  • Sources/TerminalNotificationStore.swift
  • Sources/Workspace.swift
  • cmuxTests/TabManagerUnitTests.swift
  • cmuxTests/WorkspaceManualUnreadTests.swift

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment thread Sources/GhosttyTerminalView.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Don't cache the fallback tab title in the route snapshot.

tabTitle is 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 resolve titleForTab(...) inside the @MainActor delivery 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

📥 Commits

Reviewing files that changed from the base of the PR and between e09221d and 7ee4f38.

📒 Files selected for processing (6)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/TabManager.swift
  • Sources/TerminalNotificationStore.swift
  • cmuxTests/WorkspaceManualUnreadTests.swift

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment thread Sources/GhosttyTerminalView.swift Outdated
@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 25, 2026 20:56

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.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 1c59c031 Deployed Jul 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typing latency: cmux + ssh + tmux is seriously laggy while Ghostty + ssh + tmux is not (local also slightly laggy)

3 participants