Skip to content

Collapse spinner terminal titles before ingress dedup - #9098

Merged
austinywang merged 5 commits into
mainfrom
issue-6291-spinner-title-sidebar-freeze
Jul 29, 2026
Merged

austinywang merged 5 commits into
mainfrom
issue-6291-spinner-title-sidebar-freeze

Conversation

@austinywang

@austinywang austinywang commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Closes #6291

Issue: #6291

Summary

  • Normalize leading Unicode Braille spinner tokens at the synchronous, view-scoped GhosttyTitleUpdateIngress boundary before its existing duplicate check.
  • Keep one title-churn owner: an immutable, injectable TerminalTitleChurnFilter in CmuxTerminalCore; no new timers, observers, or downstream copies of the normalization rule.
  • Drop label-less spinner frames so they cannot blank a meaningful settled title.
  • Preserve every ordinary title byte-for-byte, including intentional leading/trailing whitespace and non-leading Braille content.

Root cause and current architecture

The issue was filed against the former direct GHOSTTY_ACTION_SET_TITLE → NotificationCenter path. Current main has since added a per-view GhosttyTitleUpdateIngress, newest-value AsyncStream, and 50 ms per-surface dispatcher coalescing, so realistic spinner rates no longer reproduce the original total freeze.

One structural gap remains: each spinner frame is a different raw string (⠋ pnpm install, ⠙ pnpm install, ...), so raw duplicate rejection never fires. Every frame still pays normalization-independent AsyncStream.yield and executor scheduling before the dispatcher can coalesce it. Canonicalizing at the synchronous ingress makes the existing invariant semantic: equivalent terminal titles enqueue at most once per attachment.

The downstream TabManager and toolbar paths are intentionally unchanged. Workspace.updatePanelTitle already avoids identical mutations, and current titlebar observers already scope raw refreshes to the selected workspace. Duplicating normalization there would create multiple owners while retaining the hot-path cost.

Adapted from the current-main port in #9094 and the original filter work in #6907; Maxx Yung is credited as co-author on the fix commit.

Reproduction

Quoted from #6291:

  1. Open a workspace in cmux on macOS 15 or 26.
  2. In the terminal, run any command that emits a spinner-updating title:
    pnpm install
    # or: npm install, yarn install, cargo build (with progress), etc.
  3. While the install is running, try to click a different workspace in the sidebar, or click a different tab.

Expected: sidebar clicks register immediately while the spinner is active.

Testing

Two-commit red/green structure:

  1. test: expose spinner title ingress churn adds a behavioral regression to the already-wired TypingHotPathRegressionTests.swift. On main, all ten common pnpm spinner frames return as newly enqueued; the test requires only the first equivalent frame to enqueue while a genuinely different label still does.
  2. fix: collapse spinner titles before ingress dedup adds the normalizer, source integration, and pure package coverage.

Completed locally:

  • arch -arm64e swift test --package-path Packages/macOS/CmuxTerminalCore --filter TerminalTitleChurnFilterTests — 4/4 passed.
  • ./scripts/lint-pbxproj-test-wiring.sh — checked 617 files, passed.
  • python3 scripts/check-package-resolved-policy.py — passed.
  • python3 scripts/check-workspace-package-groups.py --check — passed.
  • git diff --check — passed.

The focused package tests cover the ten reported spinner frames, the full U+2800–U+28FF Braille Pattern range, multiple glyphs, leading whitespace, spinner-only titles, exact ordinary-title preservation, and non-leading Braille preservation. CI covers the app-host integration test; per issue instructions, no local xcodebuild test or XCUITest was run.

The historical .github/swift-file-length-budget.tsv and scripts/swift_file_length_budget.py were removed from current main in #8125. This PR changes neither budget TSV. New Swift files are 40 and 35 lines; the touched existing app-host test file remains under 500 lines at 491.

Localization

No app-visible, CLI, settings, or web copy changed. The only documentation change is the developer-facing package README entry and test construction example, so no localization catalog keys are required. Changed Swift/Markdown files were audited for user-facing literals.

Demo Video

Cloud-mac dev-build dogfood and a verified full-screen recording will follow after required CI is green.

  • Video URL or attachment: pending CI and tagged cloud build

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes UI stalls when terminals emit animated spinner titles by collapsing leading Braille spinner prefixes before ingress deduplication. Spinner frames like "⠋ pnpm install" now enqueue once per label, while ordinary and non-spinner Braille titles stay unchanged.

  • Bug Fixes
    • Added TerminalTitleChurnFilter in CmuxTerminalCore to strip known leading Braille spinner glyphs, trim whitespace, and drop spinner-only frames; preserves ordinary and non-spinner Braille titles byte-for-byte.
    • Applied the filter in GhosttyTitleUpdateIngress.submit before the duplicate check and AsyncStream enqueue to reject equivalent spinner frames early.
    • Added focused unit tests plus a regression test; updated the CmuxTerminalCore README with a short usage snippet.

Written for commit 9fa57a4. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Terminal titles with animated Braille spinner frames now resolve to a stable label, reducing unnecessary title changes.
    • Spinner-only updates are ignored, while ordinary titles continue to display unchanged.
    • Duplicate title updates are prevented during rapid spinner activity.
  • Documentation

    • Added guidance and examples for title normalization behavior and headless testing.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cd604574-f4fb-44d7-929e-ef891f9ee541

📥 Commits

Reviewing files that changed from the base of the PR and between 364da2a and 9fa57a4.

📒 Files selected for processing (5)
  • Packages/macOS/CmuxTerminalCore/README.md
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/TitleChurn/TerminalTitleChurnFilter.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/TerminalTitleChurnFilterTests.swift
  • Sources/GhosttyTitleUpdateIngress.swift
  • cmuxTests/TypingHotPathRegressionTests.swift

📝 Walkthrough

Walkthrough

Adds a public Braille spinner title normalization filter, integrates it into GhosttyTitleUpdateIngress, and adds unit and ingress regression tests confirming spinner-frame deduplication and stable title handling.

Changes

Terminal title churn

Layer / File(s) Summary
Implement and validate title normalization
Packages/macOS/CmuxTerminalCore/Sources/..., Packages/macOS/CmuxTerminalCore/Tests/..., Packages/macOS/CmuxTerminalCore/README.md
TerminalTitleChurnFilter removes recognized leading Braille spinner frames, preserves ordinary titles, and returns nil for spinner-only titles. Tests and package documentation cover the behavior.
Normalize titles at ingress
Sources/GhosttyTitleUpdateIngress.swift, cmuxTests/TypingHotPathRegressionTests.swift
GhosttyTitleUpdateIngress applies the filter before constructing updates and rejects unlabelled spinner frames. Regression coverage verifies that spinner bursts accept only the first submission while later distinct titles remain accepted.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#9094: Covers the same TerminalTitleChurnFilter integration into GhosttyTitleUpdateIngress.submit.
🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely describes the main change: collapsing spinner terminal titles before ingress deduplication.
Description check ✅ Passed The description is detailed and mostly matches the template, though the demo video is still pending and some checklist boxes are unchecked.
Linked Issues check ✅ Passed The changes implement the linked fix by normalizing leading spinner titles at ingress and adding regression coverage for spinner churn.
Out of Scope Changes check ✅ Passed The PR stays focused on spinner-title normalization, related tests, and documentation updates without clear unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Cmux Swift Actor Isolation ✅ Passed No new actor-isolation debt: the new filter is a pure Sendable value type, ingress stays nonisolated, and the mutable test helper is lock-protected @unchecked Sendable.
Cmux Swift Blocking Runtime ✅ Passed Production Swift adds pure normalization and AsyncStream/Task wiring; the only NSLock is in test-only scaffolding.
Cmux Browser Automation Off-Main ✅ Passed No browser socket commands, routing-policy changes, or worker-lane coverage moved in this diff; the touched files are unrelated to the browser-automation rule.
Cmux Expensive Synchronous Load ✅ Passed Diff only adds title-string normalization/tests/docs; no RestorableAgentSessionIndex.load(), agent-history parsing, or other expensive sync loader appears on an interactive path.
Cmux Cache Substitution Correctness ✅ Passed No authoritative read was replaced; the new state is a transient ingress dedupe filter on inbound titles, not a persistence/history/undo/snapshot cache.
Cmux No Hacky Sleeps ✅ Passed Diff touches only Swift sources/tests and README; no non-Swift runtime code, sleeps, timers, or polling were introduced.
Cmux Algorithmic Complexity ✅ Passed PASS: The diff only adds linear title-normalization over a single string and a fixed 10-frame spinner set; no nested scans or scalable-collection rescans in hot paths.
Cmux Swift Concurrency ✅ Passed Net diff adds a synchronous title filter and a stored/cancelled consumer Task; no new DispatchQueue, Combine, or unmanaged fire-and-forget async patterns.
Cmux Swift @Concurrent ✅ Passed No changed async helper needs @concurrent; new work is sync/pure, actor-isolated, or intentionally MainActor-bound in tests.
Cmux Swift Package Boundaries ✅ Passed Spinner normalization lives in CmuxTerminalCore; the app target only composes it in GhosttyTitleUpdateIngress and tests it.
Cmux Swiftpm Lockfiles ✅ Passed PR changes only source/docs/tests; no Package.swift, .gitignore, Xcode project, or Package.resolved diff is present.
Cmux Swift Logging ✅ Passed No added app/runtime print/debugPrint/dump/NSLog or unsafe Logger use; the only logging near the diff is existing DEBUG-only timing code.
Cmux User-Facing Error Privacy ✅ Passed Changed files are docs, tests, and internal title-normalization code; no user-facing errors, alerts, or recovery copy were added.
Cmux Full Internationalization ✅ Passed The PR only adds internal docs, pure logic, and tests; no user-facing Swift/UI/web copy or localization catalogs changed.
Cmux Swiftui State Layout ✅ Passed PASS: touched files are core/ingress/tests/README only; no SwiftUI views, ObservableObject/@published, GeometryReader, lazy-row store refs, or render-time state writes were introduced.
Cmux Architecture Rethink ✅ Passed PASS: The diff adds a pure, Sendable normalizer owned by the single Ghostty ingress path; it avoids new timers, observers, caches, or split UI ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only touches terminal-title normalization/tests/docs; no NSWindow/NSPanel/NSWindowController/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/docs/tests; none are logs, caches, DerivedData, temp dirs, or other source-control artifacts.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Production diffs add a real title normalizer and production injection point; no new #if DEBUG/debug*/ForTesting-style seam appears in shipping Sources.
Cmux No Ambient Global State ✅ Passed The new filter is an injected instance on an owning type; no top-level API, mutable globals, or new singleton state were added.
✨ 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-spinner-title-sidebar-freeze

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@austinywang

Copy link
Copy Markdown
Contributor Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@austinywang I can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 241,631 of the 240,000 allowed lines of code this month. Reviews resume on 1 August 2026 (in 4 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

@austinywang: I’ll review the changes in #9098.

✅ Action performed

Review finished.

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

1 participant