Skip to content

Collapse spinner-frame terminal titles to fix sidebar freeze (#6291) - #6319

Closed
austinywang wants to merge 2 commits into
mainfrom
issue-6291
Closed

austinywang wants to merge 2 commits into
mainfrom
issue-6291

Conversation

@austinywang

@austinywang austinywang commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Closes #6291

Root cause

CLI tools like pnpm, npm, cargo, and Node/ora-based spinners update the
terminal title on every animation frame with a leading Unicode braille
spinner glyph (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏). Ghostty forwards each title change through
GHOSTTY_ACTION_SET_TITLE → .ghosttyDidSetTitle. Every post drove
TabManager.enqueuePanelTitleUpdate (panel-title coalescer) and
WindowToolbarController.scheduleFocusedCommandTextUpdate. Frame N and frame
N+1 differ only by the leading glyph, so each was treated as a distinct title —
the redundant churn flooded the main thread enough to starve sidebar
hit-testing, leaving the sidebar unclickable until the spinner finished.

Fix

Collapse spinner-frame titles to a stable form and dedupe at the source so the
redundant frames never reach the coalescer or the toolbar updater:

  1. TabManager.stableTerminalPanelTitle(_:) (nonisolated, pure) strips any
    run of leading braille spinner glyphs + following whitespace, then trims.
    Frame N and N+1 with the same trailing text map to one string. Limited to
    braille glyphs, which never legitimately lead a real title.
  2. GhosttyTerminalView posts the stable title and drops the post entirely
    when it equals the surface's lastPublishedTerminalTitle — killing the churn
    at the source (before the coalescer and the toolbar command-text updater).
  3. TabManager.enqueuePanelTitleUpdate early-returns when the same stable
    title is already queued for that panel (belt-and-suspenders dedupe).

Verification

  • ./scripts/reload.sh --tag issue-6291 — clean Debug build.
  • New regression test cmuxTests/TabManagerWorkspaceOwnershipTests (wired into
    project.pbxproj): spinner frames collapse to one stable title, non-spinner
    titles are only trimmed, and distinct trailing text stays distinct.

Scope / tradeoffs

Narrowly the sidebar-freeze regression. The source-level dedupe (#2) makes the
toolbar-updater and coalescer guards from the issue's proposed fix list
unnecessary, so I did not add them. Title growth in the two large files is
covered by a .github/swift-file-length-budget.tsv refresh (splitting those
files is out of scope for this fix).


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes the sidebar freeze by collapsing spinner-frame terminal titles into a stable form and deduping updates at the source. Stops rapid title churn from pnpm, npm, cargo, codex, and ora spinners from flooding the UI (fixes #6291).

  • Bug Fixes
    • Added pure, nonisolated TabManager.stableTerminalPanelTitle to strip leading spinner glyphs (braille/block) and following whitespace, then trim.
    • GhosttyTerminalView now posts the stable title only when it changes; tracks lastPublishedTerminalTitle to drop duplicates.
    • TabManager uses the stable title when enqueuing and skips if the same value is already pending; added cmuxTests/TabManagerTerminalTitleCollapseTests.swift to cover collapse, non-spinner preservation, distinct trailing text, and effective dedup.

Written for commit 84670fd. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Stabilized terminal panel title updates by collapsing spinner animation artifacts into a consistent “stable” title.
    • Prevented redundant title-change notifications when the normalized title hasn’t changed, reducing flicker and unnecessary updates.
  • Tests
    • Expanded regression coverage for title stabilization and deduplication, including handling of leading spinner glyphs, whitespace trimming for non-spinner titles, and correct behavior across genuine transitions.

@vercel

vercel Bot commented Jun 17, 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 Jun 17, 2026 9:51pm
cmux-staging Building Building Preview, Comment Jun 17, 2026 9:51pm

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9504fc27-2548-47b9-979a-941ba5be3c84

📥 Commits

Reviewing files that changed from the base of the PR and between b1c0bdf and 84670fd.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmux.xcodeproj/project.pbxproj

📝 Walkthrough

Walkthrough

Adds a stableTerminalPanelTitle static helper to TabManager that strips leading braille spinner glyph runs from raw terminal titles. This stable form is used in enqueuePanelTitleUpdate to skip redundant enqueues and in GhosttyNSView to skip redundant .ghosttyDidSetTitle posts. A new test suite with five comprehensive test cases validates the normalization logic, wired into the Xcode project.

Changes

Spinner Title Deduplication

Layer / File(s) Summary
stableTerminalPanelTitle helper and spinner character set
Sources/TabManager.swift
Adds terminalTitleSpinnerCharacters (braille spinner set) and nonisolated static stableTerminalPanelTitle(_:) which iteratively removes leading spinner glyph runs plus following whitespace, then trims the result.
Deduplication guards at enqueue and publish sites
Sources/TabManager.swift, Sources/GhosttyTerminalView.swift
enqueuePanelTitleUpdate now normalizes via stableTerminalPanelTitle, early-returns on empty stable result, and skips enqueuing when the same (tabId, panelId) already has an identical pending title. GhosttyNSView gains lastPublishedTerminalTitle and skips posting .ghosttyDidSetTitle when the stable title is unchanged.
Regression test suite and Xcode project wiring
cmuxTests/TabManagerTerminalTitleCollapseTests.swift, cmux.xcodeproj/project.pbxproj
Adds five test cases covering spinner-frame collapse to stable title, non-spinner pass-through (including leading - preservation), distinct trailing-text divergence, long spinner-stream dedup coalescing, and genuine content transitions post-dedup. Registers the test file in the Xcode project via PBXBuildFile, PBXFileReference, group children, and PBXSourcesBuildPhase.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • manaflow-ai/cmux#4562: The PBXBuildFile and related Xcode project wiring in this PR follows the exact test-wiring pattern that PR's lint guard was designed to enforce.

Poem

🐇 The spinner spins, the braille glyphs fly,
Each frame a title streaking by.
But now I strip those whirling dots,
And dedupe all those useless lots.
The sidebar clicks — no freeze, no lag!
One stable title waves its flag. 🎉

🚥 Pre-merge checks | ✅ 20 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: collapsing spinner-frame terminal titles to fix the sidebar freeze regression (#6291).
Description check ✅ Passed The description covers all required template sections: Summary (root cause and fix), Testing (verification approach and test names), and Checklist items marked complete.
Linked Issues check ✅ Passed The PR implements all coding requirements from #6291: TabManager.stableTerminalPanelTitle strips spinner glyphs, GhosttyTerminalView dedupes via lastPublishedTerminalTitle, TabManager.enqueuePanelTitleUpdate early-returns on identical queued titles, and comprehensive tests verify collapse/trim/dedup behavior.
Out of Scope Changes check ✅ Passed All changes are directly focused on the sidebar-freeze regression: spinner collapse logic, deduplication at three layers, test coverage, and project file wiring for tests. No out-of-scope refactoring or unrelated features detected.
Cmux Swift Actor Isolation ✅ Passed Production changes properly mark pure helpers as nonisolated static (terminalTitleSpinnerCharacters, stableTerminalPanelTitle) and access mutable property (lastPublishedTerminalTitle) only within...
Cmux Swift Blocking Runtime ✅ Passed The PR introduces no blocking primitives, semaphores, waits, sleeps, or timing-based synchronization. New code uses pure string functions and equality-based deduplication, avoiding all patterns fla...
Cmux Expensive Synchronous Load ✅ Passed PR adds only lightweight string operations (trim, iterate, contains checks) for terminal title normalization. No expensive loaders, disk I/O, or syscalls introduced; all existing costly ops unchanged.
Cmux Cache Substitution Correctness ✅ Passed lastPublishedTerminalTitle is a transient, non-persisted UI hint for deduplication (not persisted, not Codable, not in snapshots/undo/history). No cold/stale cache issue applies.
Cmux No Hacky Sleeps ✅ Passed PR makes no changes to TypeScript, JavaScript, shell, or build/runtime scripts—only Swift code and tests. Xcode project config change merely wires test file. No sleeps, timers, polling, or delays i...
Cmux Algorithmic Complexity ✅ Passed All code changes involve tiny fixed-size collections (19 braille glyphs) or operations bounded by string length (~256 max). No nested collection scans, per-target rescans, or iterations over worksp...
Cmux Swift Concurrency ✅ Passed PR adds nonisolated pure functions and property fields without introducing legacy async patterns; DispatchQueue.main.async used only for required AppKit callback boundary, consistent with existing...
Cmux Swift @Concurrent ✅ Passed PR introduces two nonisolated static members in @MainActor TabManager: a static let constant (terminalTitleSpinnerCharacters) and a pure synchronous helper function (stableTerminalPanelTitle). Both...
Cmux Swift File And Package Boundaries ✅ Passed PR complies with swift-file-package-boundaries.md: focused bug fix adding 49 net lines to already-oversized files (well below 250-line threshold), introducing no mixed responsibilities, with pure t...
Cmux Swift Logging ✅ Passed Production code adds no print/debugPrint/dump/NSLog violations. One cmuxDebugLog call is properly DEBUG-guarded, uses safe preview utilities, and doesn't expose sensitive data.
Cmux User-Facing Error Privacy ✅ Passed No new user-facing error messages, alerts, or command output exposed in the diff. Changes only normalize existing terminal title text (stripped of spinner glyphs) and deduplicate UI updates; test d...
Cmux Full Internationalization ✅ Passed PR introduces only internal code changes (property, algorithm, test file) with no new user-facing text, catalogs unchanged, and no web localization updates required.
Cmux Swiftui State Layout ✅ Passed PR adds no new SwiftUI state violations. Changes are isolated to AppKit (GhosttyNSView.lastPublishedTerminalTitle) and pure helper functions (TabManager.stableTerminalPanelTitle). No @Published/@ob...
Cmux Architecture Rethink ✅ Passed Small correctness fix with clear owners, named invariants, pure transformations, and no timing/locking repairs. Reduces redundant title updates via source-level deduplication and a simple guard che...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR introduces no new NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup. Changes are limited to String property, helper method, and unit tests for terminal title normalization to...
Cmux Source Artifacts ✅ Passed All changed files are intentional hand-written source code, test files, and Xcode project configuration with no source control artifacts, build output, or generated code.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-6291

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a sidebar freeze (#6291) caused by CLI spinners (pnpm, cargo, codex, ora-based tools) flooding the main thread by posting a new GHOSTTY_ACTION_SET_TITLE notification on every animation frame. The fix collapses any leading run of braille spinner glyphs to a stable title, then deduplicates at two layers so redundant frames never reach the panel-title coalescer or toolbar command-text updater.

  • TabManager.stableTerminalPanelTitle is a new nonisolated static pure function that strips leading braille glyph runs plus following whitespace, then trims — correctly annotated to be callable from both the off-main Ghostty action callback and the @MainActor-isolated TabManager.
  • GhosttyTerminalView now computes the stable title inside the main-queue hop, guards against republishing an unchanged title via lastPublishedTerminalTitle, and posts the collapsed title in the notification — killing churn before it reaches the coalescer.
  • TabManager.enqueuePanelTitleUpdate adds a belt-and-suspenders guard: if the same stable title is already in pendingPanelTitleUpdates for that panel, the update is dropped early (before the debug log and coalescer signal).

Confidence Score: 4/5

Safe to merge; the churn fix is well-scoped and the dedup logic is correct. The one open edge case — lastPublishedTerminalTitle not being cleared when terminalSurface is replaced — was flagged in a prior review round and could silently suppress the first title notification after a new shell session reuses the same panel.

The surface-replacement gap in lastPublishedTerminalTitle (flagged in the previous thread) is still open: if the stable form of the new session's first title matches the old session's last published title, that first update is silently dropped. This is unlikely in most flows but is a real suppression path.

Sources/GhosttyTerminalView.swift — specifically the lastPublishedTerminalTitle property and whether it is cleared when terminalSurface is set to nil.

Important Files Changed

Filename Overview
Sources/GhosttyTerminalView.swift Adds lastPublishedTerminalTitle to GhosttyNSView and uses it for source-level dedup inside the main-queue hop; the stale-on-surface-replacement edge case was flagged in a prior review thread.
Sources/TabManager.swift Adds terminalTitleSpinnerCharacters and stableTerminalPanelTitle (both correctly nonisolated static on the @MainActor class), and a coalescer-level early-return guard; logic is correct and idiomatic.
cmuxTests/TabManagerTerminalTitleCollapseTests.swift New test suite with four focused cases: spinner collapse, non-spinner preservation, distinct-trailing-text distinctness, and a stream-level dedup simulation; all assertions are deterministic despite Set iteration order.
cmux.xcodeproj/project.pbxproj Adds PBXBuildFile and PBXFileReference entries for the new test file; straightforward wiring with no structural issues.
.github/swift-file-length-budget.tsv Budget refreshed to reflect the line growth in GhosttyTerminalView.swift and TabManager.swift; both files stay within their updated budgets.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GIO as Ghostty I/O Thread
    participant MAIN as Main Thread (DispatchQueue.main)
    participant GNV as GhosttyNSView<br/>(lastPublishedTerminalTitle)
    participant NC as NotificationCenter
    participant TM as TabManager<br/>(pendingPanelTitleUpdates)
    participant UI as Sidebar / Toolbar

    GIO->>MAIN: GHOSTTY_ACTION_SET_TITLE ("⠋ pnpm install")
    MAIN->>MAIN: "stable = stableTerminalPanelTitle → "pnpm install""
    MAIN->>GNV: "lastPublishedTerminalTitle != stable?"
    GNV-->>MAIN: """ != "pnpm install" → YES"
    MAIN->>GNV: "lastPublishedTerminalTitle = "pnpm install""
    MAIN->>NC: post .ghosttyDidSetTitle (title: "pnpm install")
    NC->>TM: enqueuePanelTitleUpdate(title: "pnpm install")
    TM->>TM: "pendingPanelTitleUpdates[key] != "pnpm install" → enqueue + signal coalescer"
    TM->>UI: flushPendingPanelTitleUpdates → updatePanelTitle

    GIO->>MAIN: GHOSTTY_ACTION_SET_TITLE ("⠙ pnpm install")
    MAIN->>MAIN: "stable = "pnpm install""
    MAIN->>GNV: "lastPublishedTerminalTitle != stable?"
    GNV-->>MAIN: ""pnpm install" == "pnpm install" → NO (dropped here)"

    Note over GIO,UI: Frame N+1…N+119 all dropped at source — coalescer and toolbar never see them
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"}}}%%
sequenceDiagram
    participant GIO as Ghostty I/O Thread
    participant MAIN as Main Thread (DispatchQueue.main)
    participant GNV as GhosttyNSView<br/>(lastPublishedTerminalTitle)
    participant NC as NotificationCenter
    participant TM as TabManager<br/>(pendingPanelTitleUpdates)
    participant UI as Sidebar / Toolbar

    GIO->>MAIN: GHOSTTY_ACTION_SET_TITLE ("⠋ pnpm install")
    MAIN->>MAIN: "stable = stableTerminalPanelTitle → "pnpm install""
    MAIN->>GNV: "lastPublishedTerminalTitle != stable?"
    GNV-->>MAIN: """ != "pnpm install" → YES"
    MAIN->>GNV: "lastPublishedTerminalTitle = "pnpm install""
    MAIN->>NC: post .ghosttyDidSetTitle (title: "pnpm install")
    NC->>TM: enqueuePanelTitleUpdate(title: "pnpm install")
    TM->>TM: "pendingPanelTitleUpdates[key] != "pnpm install" → enqueue + signal coalescer"
    TM->>UI: flushPendingPanelTitleUpdates → updatePanelTitle

    GIO->>MAIN: GHOSTTY_ACTION_SET_TITLE ("⠙ pnpm install")
    MAIN->>MAIN: "stable = "pnpm install""
    MAIN->>GNV: "lastPublishedTerminalTitle != stable?"
    GNV-->>MAIN: ""pnpm install" == "pnpm install" → NO (dropped here)"

    Note over GIO,UI: Frame N+1…N+119 all dropped at source — coalescer and toolbar never see them
Loading

Reviews (5): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

/// the workspace panel-title coalescer or the toolbar command-text updater
/// (issue #6291). Main-thread only (mutated from the title action handler's
/// main-queue hop).
var lastPublishedTerminalTitle: String = ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Stale lastPublishedTerminalTitle on surface replacement

lastPublishedTerminalTitle is initialised to "" and is never cleared when terminalSurface is replaced (e.g. when the terminal process exits and a new shell spawns in the same panel). The property is keyed on the GhosttyNSView instance, not on the (tabId, surfaceId) pair, so if the view is reused with a fresh TerminalSurface the dedup guard will silently suppress the new session's first title notification whenever its stable form matches the previous session's last published title. Clearing lastPublishedTerminalTitle to "" when terminalSurface is set to nil (in the detach path near line 7058) would close the window.

/// flood the panel-title coalescer and toolbar command-text updater and starve
/// sidebar hit-testing. The fix collapses such titles to a stable form so the
/// redundant frames dedupe instead of re-driving the main thread (issue #6291).
@Suite struct TabManagerWorkspaceOwnershipTests {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The suite name TabManagerWorkspaceOwnershipTests describes workspace ownership semantics, but every test in the file exercises stableTerminalPanelTitle / spinner collapsing. A future reader looking for workspace-ownership coverage would find spinner tests here, and spinner tests are hard to discover by name.

Suggested change
@Suite struct TabManagerWorkspaceOwnershipTests {
@Suite struct TabManagerSpinnerTitleCollapseTests {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@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 `@cmuxTests/TabManagerWorkspaceOwnershipTests.swift`:
- Line 16: The test class name TabManagerWorkspaceOwnershipTests does not
accurately reflect the test content, which focuses on spinner-glyph
deduplication and stableTerminalPanelTitle functionality rather than workspace
ownership. Rename the `@Suite` struct TabManagerWorkspaceOwnershipTests to a name
that better describes its actual purpose, such as
TabManagerTerminalTitleDeduplicationTests, to improve code clarity and
maintainability.

In `@Sources/GhosttyTerminalView.swift`:
- Around line 2886-2889: The `lastPublishedTerminalTitle` property used for
deduplication in the GhosttyNSView survives when the view is rebound to a
different surface, causing the dedupe guard check to incorrectly drop title
updates if the first title of the new surface matches the previous surface's
last published title. Locate where GhosttyNSView is rebound to a different
surface and reset `lastPublishedTerminalTitle` to nil at that point to clear the
stale dedupe state. Apply the same fix to the related code section that also
handles similar title change logic.
🪄 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: c572cc34-bdf0-4552-b400-9a043032950e

📥 Commits

Reviewing files that changed from the base of the PR and between e26dca1 and 8a6057e.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (4)
  • Sources/GhosttyTerminalView.swift
  • Sources/TabManager.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/TabManagerWorkspaceOwnershipTests.swift

/// flood the panel-title coalescer and toolbar command-text updater and starve
/// sidebar hit-testing. The fix collapses such titles to a stable form so the
/// redundant frames dedupe instead of re-driving the main thread (issue #6291).
@Suite struct TabManagerWorkspaceOwnershipTests {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Verify test class name aligns with test content.

The class TabManagerWorkspaceOwnershipTests suggests workspace-ownership testing, but the suite actually tests spinner-glyph deduplication via stableTerminalPanelTitle. Consider renaming to TabManagerTerminalTitleDeduplicationTests or similar for clarity.

🤖 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 `@cmuxTests/TabManagerWorkspaceOwnershipTests.swift` at line 16, The test class
name TabManagerWorkspaceOwnershipTests does not accurately reflect the test
content, which focuses on spinner-glyph deduplication and
stableTerminalPanelTitle functionality rather than workspace ownership. Rename
the `@Suite` struct TabManagerWorkspaceOwnershipTests to a name that better
describes its actual purpose, such as TabManagerTerminalTitleDeduplicationTests,
to improve code clarity and maintainability.

Comment thread Sources/GhosttyTerminalView.swift

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

♻️ Duplicate comments (1)
Sources/GhosttyTerminalView.swift (1)

2880-2889: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset title dedupe state when the view rebinds to a different surface.

lastPublishedTerminalTitle is stored on GhosttyNSView, but dedupe semantics are surface-scoped. When a reused view attaches a new surface, the first title publish can be dropped if it matches the previous surface’s stable title (see attachSurface(_:) around Line 3799), leaving stale downstream title state.

💡 Proposed fix
 func attachSurface(_ surface: TerminalSurface) {
     let isSameSurface = terminalSurface === surface
     let isAlreadyAttached = surface.isAttached(to: self)
     if !isSameSurface {
         appliedColorScheme = nil
+        // Dedupe state is surface-scoped; clear when rebinding this view.
+        lastPublishedTerminalTitle = ""
     }
     terminalSurface = surface
     tabId = surface.tabId

Also applies to: 3425-3431

🤖 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 2880 - 2889, The
lastPublishedTerminalTitle property is used for deduplication of terminal title
updates, but when a reused GhosttyNSView rebinds to a different surface in the
attachSurface method (around line 3799), the old title state from the previous
surface persists. This causes the deduplication guard in the title update logic
to incorrectly skip the first title of the new surface if it matches the
previous surface's stable title. Reset lastPublishedTerminalTitle to nil in the
attachSurface method when binding to a new surface to ensure clean dedupe state
for each surface-view binding. Apply the same reset fix to other relevant
locations mentioned (around lines 3425-3431).
🤖 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.

Duplicate comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 2880-2889: The lastPublishedTerminalTitle property is used for
deduplication of terminal title updates, but when a reused GhosttyNSView rebinds
to a different surface in the attachSurface method (around line 3799), the old
title state from the previous surface persists. This causes the deduplication
guard in the title update logic to incorrectly skip the first title of the new
surface if it matches the previous surface's stable title. Reset
lastPublishedTerminalTitle to nil in the attachSurface method when binding to a
new surface to ensure clean dedupe state for each surface-view binding. Apply
the same reset fix to other relevant locations mentioned (around lines
3425-3431).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2a69bff2-99bd-495e-bfd7-60ebb9f8c664

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6057e and ae28258.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (4)
  • Sources/GhosttyTerminalView.swift
  • Sources/TabManager.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/TabManagerTerminalTitleCollapseTests.swift

# Conflicts:
#	.github/swift-file-length-budget.tsv
@Solarrixs

Copy link
Copy Markdown
Contributor

Heads up - the GHOSTTY_ACTION_SET_TITLE block this patches got replaced on main by GhosttyTitleUpdateIngress (sync dedup + 50ms per-surface coalescing), so this doesn't apply anymore (git apply --check fails). I also tested main directly and can't reproduce #6291 at realistic spinner rates - details here: #6291 (comment). Probably fine to close.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 84670fd6 Deployed Jun 17, 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.

Sidebar becomes unclickable when CLI tools emit frame-by-frame spinner characters in terminal titles

4 participants