Skip to content

Fix TextBox IME composition rendering - #8688

Merged
austinywang merged 32 commits into
mainfrom
issue-8625-zhuyin-ime-composition-invisible
Jul 24, 2026
Merged

austinywang merged 32 commits into
mainfrom
issue-8625-zhuyin-ime-composition-invisible

Conversation

@austinywang

@austinywang austinywang commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • keep native NSTextView text storage as the sole owner of live IME composition
  • make every setMarkedText mutation synchronously enter the existing TextBox layout-measurement boundary before commit
  • invalidate the native text view for redraw so marked text and the insertion cursor render during composition
  • cover Bopomofo preedit wrapping through the real NSTextInputClient entrypoint
  • leave the separate Ghostty terminal-preedit path unchanged

Root cause

AppKit updates the TextBox text storage and TextKit glyphs for marked text, but this path does not emit textDidChange or trigger cmux's TextBox layout callback. The SwiftUI height binding therefore remained committed-text-only until the IME committed its preedit buffer.

Verification

  • Red proof: macOS 26 run 29963869481 executed one test on the test-only code and failed on the missing layout callback, unchanged measured height, native/bound height mismatch, and redraw invalidation.
  • Green proof: macOS 26 run 29965370443 executed the same test on PR HEAD; 1 test passed.
  • A direct AppKit probe confirmed that setMarkedText updates TextKit layout without emitting textDidChange; the regression test calls that same native entrypoint and verifies pre-commit marked state, reflow, measured height, frame synchronization, and redraw invalidation.
  • I could not truthfully verify real interactive Zhuyin input. A cloud Mac reached the GUI-ready hold, but SSH/CUA access from this machine failed through the available fleet/Tailscale routes, so the run was cancelled. No local or cloud cmux app build was performed.
  • The requested Swift file-length budget script/TSV are not present in this checkout; the new test file remains below 500 lines and no budget file was added or changed.

Test structure

  1. test: cover TextBox IME composition layout — regression test only
  2. fix: lay out TextBox marked text before commit — production fix
  3. harness-only follow-up hosts the view in an offscreen NSWindow, because AppKit does not retain dirty-region state for an unattached view

Current-main test unblock

Current main does not compile cmuxTests because AgentResumeLivenessTests omits the new processLiveness initializer argument. This branch temporarily carries the exact authored commit from #8651 so the focused and full test lanes can execute; that diff disappears when #8651 lands.

Localization

No user-facing strings or surfaces changed. Resources/Localizable.xcstrings and all message catalogs are untouched; the changed Swift source/test contain no newly introduced UI copy.

Closes #8625

Summary by CodeRabbit

  • Bug Fixes

    • Improved text field layout updates while entering and expanding IME composition text.
    • Ensured composed text correctly triggers display refreshes and height synchronization.
    • Improved working-directory handling when opening terminal locations from directory URLs.
  • Tests

    • Added coverage for IME composition layout and dynamic height changes.
    • Improved reliability of window, developer-tools, routing, and offscreen startup tests, especially in headless environments.

@cursor

cursor Bot commented Jul 22, 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.

@coderabbitai

coderabbitai Bot commented Jul 22, 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
📝 Walkthrough

Walkthrough

Changes

IME composition layout

Layer / File(s) Summary
Marked-text layout synchronization
Sources/TextBoxInput.swift
TextKit line-fragment measurements now flow through layout completion, while marked-text updates explicitly invalidate layout and display.
Marked-text regression coverage
cmuxTests/TextBoxIMECompositionLayoutTests.swift, cmux.xcodeproj/project.pbxproj
Adds and registers tests for successive IME preedits, deferred reflow, height synchronization, and redraw.

SSH working-directory derivation

Layer / File(s) Summary
Parent directory path derivation
Sources/AppDelegate+CmuxSSHURL.swift
Terminal request working directories now use the parent URL’s decoded path.

Test runtime stability

Layer / File(s) Summary
Browser lifecycle and routing determinism
cmuxTests/BrowserConfigTests.swift, cmuxTests/BrowserDeveloperToolsLifecycleTests.swift
Browser tests add focused-window capture, lookup polling, transition waits, and headless skips.
Test window lifetime safety
cmuxTests/BrowserConfigTests.swift, cmuxTests/BrowserDeveloperToolsLifecycleTests.swift, cmuxTests/GhosttyConfigTests.swift, cmuxTests/TerminalNotificationOpenPanelFallbackTests.swift, cmuxTests/WindowKeyDownReplayGuardTests.swift, cmuxTests/WindowTitleTemplateTests.swift
Programmatically created windows disable automatic release when closed.
Detached terminal startup timing
cmuxTests/TerminalAndGhosttyTests.swift
Terminal startup tests use bounded polling and verify runtime-surface creation without window attachment.

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

Sequence Diagram(s)

sequenceDiagram
  participant IMETest
  participant TextBoxInputTextView
  participant TextBoxInputViewCoordinator
  IMETest->>TextBoxInputTextView: setMarkedText(preedit)
  TextBoxInputTextView->>TextBoxInputTextView: layout and measure fragments
  TextBoxInputTextView->>TextBoxInputViewCoordinator: report measured layout
  TextBoxInputViewCoordinator->>TextBoxInputTextView: recalculate height
Loading
🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes unrelated AppDelegate URL handling and broad test window-lifecycle changes beyond the Zhuyin IME fix. Move the unrelated AppDelegate and window-lifecycle test edits into a separate PR unless they are required for #8625.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (23 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main TextBox IME composition rendering fix.
Description check ✅ Passed The description covers summary, root cause, testing, and verification, but omits the demo video, review trigger, and checklist sections.
Linked Issues check ✅ Passed The TextBox marked-text and layout changes directly address #8625 by making IME preedit render before commit.
Cmux Swift Actor Isolation ✅ Passed Changed production code is UI-bound (TextBoxInputView/Coordinator) or a plain value struct; no new actor-isolation debt, Sendable refs, or background store access were introduced.
Cmux Swift Blocking Runtime ✅ Passed The touched production Swift changes only adjust TextBox layout/marked-text handling and URL path computation; the only polling/waiting added is test-only scaffolding.
Cmux Browser Automation Off-Main ✅ Passed The PR diff only changes TextBox/layout code and browser test scaffolding; it does not touch TerminalController.swift or ControlCommandExecutionPolicy.swift, so no browser socket routing was mo...
Cmux Expensive Synchronous Load ✅ Passed The production Swift diff only adjusts TextBox layout/marked-text sync and a URL path string; it adds no agent-history load, JSON parsing, or other heavy sync loader on an interactive path.
Cmux Cache Substitution Correctness ✅ Passed The touched production code only refactors IME layout measurement and URL path handling; it does not replace any authoritative persistence/history/undo/snapshot read with a cached value.
Cmux No Hacky Sleeps ✅ Passed The only non-Swift change is a deterministic TS test, and it adds no sleeps, timers, or polling; no production runtime scripts were touched.
Cmux Algorithmic Complexity ✅ Passed Only test scaffolding and small production tweaks were added; no new nested scans, repeated rescans, or hot-path collection rebuilding.
Cmux Swift Concurrency ✅ Passed Diff adds AppKit/XCTest callback wiring and sync layout/state updates only; no new DispatchQueue, Combine, completion-handler, or lifecycle Tasks.
Cmux Swift @Concurrent ✅ Passed Touched Swift changes stay sync/MainActor-bound; no new @concurrent, nonisolated async, or un-hopped UI async work was introduced.
Cmux Swift Package Boundaries ✅ Passed PASS: the production edits are NSTextView/AppKit layout glue and a URL open-request tweak; no independent reusable domain logic was added or expanded.
Cmux Swiftpm Lockfiles ✅ Passed No .gitignore, workflow, or Package.resolved changes appear in the PR; the Xcode project hunk only adds a test file, with no SwiftPM package-reference edits.
Cmux Swift Logging ✅ Passed No logging APIs were added or changed in the touched production Swift files; searches of Sources/TextBoxInput.swift and Sources/AppDelegate+CmuxSSHURL.swift found none.
Cmux User-Facing Error Privacy ✅ Passed PASS: The only production tweak changes workingDirectory path normalization; no new or changed user-facing errors, alerts, command output, or sensitive details appear in the diff.
Cmux Full Internationalization ✅ Passed Production edits only adjust IME/layout and URL-path handling; no new user-facing copy, locale keys, or catalog/plist changes were introduced.
Cmux Swiftui State Layout ✅ Passed Diff stays in an NSViewRepresentable/AppKit bridge and tests; no new ObservableObject/@Published/GeometryReader or render-time state writes were introduced.
Cmux Architecture Rethink ✅ Passed PASS: The diff keeps IME state owned by NSTextView and uses only platform bridge callbacks; the only polling is bounded test-only waitUntil, which the rules allow.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Only test-only NSWindow fixtures were changed; no new cmux-owned standalone windows or identifier registrations were added in Sources.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/test/config files; none are local outputs, caches, screenshots, or other artifact directories.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: The production diffs add runtime layout/marked-text plumbing and a URL path fix; the new callback is wired from TextBoxInputView, not a test/debug-only seam.
Cmux No Ambient Global State ✅ Passed The production changes stay inside existing types (TextBoxInputView/TextBoxInputTextView and TerminalDefaultFileOpenRequest); no new file-scope func/var/singleton was added.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8625-zhuyin-ime-composition-invisible

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.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a long-standing AppKit gap where setMarkedText updates TextKit glyph layout but never fires textDidChange, leaving the SwiftUI textViewHeight binding stale throughout the entire IME composition session. The fix schedules an NSView layout pass (needsLayout = true, needsDisplay = true) inside setMarkedText, and the existing layout() override propagates the pre-computed lineFragmentCount into onLayoutCompleted so the coordinator's recalculateHeight path avoids a redundant recompute.

  • IME composition reflow (TextBoxInput.swift): setMarkedText now schedules layout and display; layout() passes the measured line-fragment count to onLayoutCompleted; recalculateHeight conditionally skips recenterSingleLineTextContainer when the count is already provided (centering already ran inside layout() before the callback).
  • Trailing-slash working-directory fix (AppDelegate+CmuxSSHURL.swift): URL.path(percentEncoded:false) kept a trailing slash on the parent directory of the opened file; switching to URL.path removes it, fixing {activeDirectory} rendering and path comparisons.
  • New regression test (TextBoxIMECompositionLayoutTests.swift): exercises the real NSTextInputClient entrypoint with Bopomofo preedit, verifies height reflow, frame synchronization, caret geometry, and stable re-measurement when preedit size is unchanged.

Confidence Score: 5/5

Safe to merge. The production changes are a focused, well-tested AppKit integration fix with no new global state, no blocking primitives, and no user-facing string changes.

All three production changes (IME layout scheduling, trailing-slash path fix, spurious-await removal) are narrowly scoped and each comes with a clear mechanical explanation. The recalculateHeight refactor moves ensureLayout before recenterSingleLineTextContainer (correct ordering) and uses an optional lineFragmentCount parameter to avoid double measurement on the layout path without changing the direct-call behavior. The layout() override's guard against re-entrant callbacks is preserved. The new regression test exercises the real NSTextInputClient entrypoint through a live NSWindow, and red/green CI runs validate the before/after behavior. No correctness risks were identified.

No files require special attention.

Important Files Changed

Filename Overview
Sources/TextBoxInput.swift Core IME fix: setMarkedText now schedules NSView layout via needsLayout/needsDisplay; layout() passes the pre-computed lineFragmentCount to onLayoutCompleted, eliminating a redundant recompute; recalculateHeight conditionally skips recenterSingleLineTextContainer when called from the layout path (already done before onLayoutCompleted fires). Logic is correct.
cmuxTests/TextBoxIMECompositionLayoutTests.swift New regression test for Bopomofo/IME preedit layout. Exercises the real NSTextInputClient entrypoint, verifies height reflow, frame synchronization, and caret geometry for both expanding and stable preedit buffers. Test is hosted in a real NSWindow to retain AppKit dirty-region state. Well structured.
Sources/AppDelegate+CmuxSSHURL.swift One-line bug fix: URL.path(percentEncoded:false) preserved a trailing slash on directory URLs from deletingLastPathComponent(), which surfaced in window title {activeDirectory} and directory comparisons. Switching to URL.path removes it correctly.
Sources/Mobile/MobileHostIrohRuntime+Activation.swift Removes a spurious await on a synchronous stored property access (diagnosticLog). Correct clean-up; no behavioral change.
cmuxTests/BrowserConfigTests.swift Adds XCTSkipIf import, installs the existing shortcut-routing window capture seam in setUp/tearDown for headless reliability, and adds a helper to locate the SwiftUI-hosted main window. Test stability improvements only.
cmux.xcodeproj/project.pbxproj Adds TextBoxIMECompositionLayoutTests.swift to the cmuxTests target. Routine project file update.

Sequence Diagram

sequenceDiagram
    participant IME as IME Input
    participant TV as TextBoxInputTextView
    participant LM as NSLayoutManager
    participant C as Coordinator
    participant SW as SwiftUI Binding

    Note over IME,SW: Old path — height not updated during composition
    IME->>TV: setMarkedText(_:selectedRange:replacementRange:)
    TV->>LM: (TextKit layout updates internally)
    Note over TV: textDidChange NOT fired
    Note over SW: textViewHeight stays at committed-text-only height

    Note over IME,SW: New path — height updated before commit
    IME->>TV: setMarkedText(_:selectedRange:replacementRange:)
    TV->>TV: super.setMarkedText (TextKit updates glyphs)
    TV->>TV: onMarkedTextStateChanged(true)
    TV->>TV: "needsLayout = true, needsDisplay = true"
    TV->>TV: layout() triggered by AppKit
    TV->>LM: ensureLayout(for: textContainer)
    TV->>TV: visualLineFragmentCount()
    TV->>TV: recenterSingleLineTextContainer(lineFragmentCount:)
    TV->>C: onLayoutCompleted(self, lineFragmentCount)
    C->>C: recalculateHeight(_:lineFragmentCount:)
    C->>LM: ensureLayout (idempotent)
    C->>TV: "frame.size.height = preferredHeight"
    C->>SW: "textViewHeight = preferredHeight"
Loading

Reviews (14): Last reviewed commit: "test: cover synchronous TextBox IME layo..." | Re-trigger Greptile

Comment on lines +52 to +54
textView.onMarkedTextStateChanged = { hasMarkedText in
coordinator.noteMarkedTextStateChanged(hasMarkedText, from: textView)
}

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 onMarkedTextStateChanged closure captures textView strongly and is stored as a property on textView itself, forming a retain cycle (textView → stored closure → textView). The equivalent production wiring in TextBoxInput.swift avoids this with [weak coordinator, weak textView]. In this headless test the cycle is harmless in practice, but both the coordinator and text view stay alive past the function return until the autorelease pool drains rather than being released immediately.

Suggested change
textView.onMarkedTextStateChanged = { hasMarkedText in
coordinator.noteMarkedTextStateChanged(hasMarkedText, from: textView)
}
textView.onMarkedTextStateChanged = { [weak coordinator, weak textView] hasMarkedText in
coordinator?.noteMarkedTextStateChanged(hasMarkedText, from: textView)
}

austinywang and others added 20 commits July 22, 2026 15:34
The cmux-unit test target does not compile on main:

    cmuxTests/AgentResumeLivenessTests.swift:25:25: error: missing argument
    for parameter 'processLiveness' in call

#8547 added a `processLiveness` field to RestorableAgentSessionIndex.Entry with
no default, which makes it required in the memberwise initializer. This test
helper still builds an Entry without it, so the whole target fails to build and
no suite in it can run.

hasLiveProcess decides from the PID set alone, so the value only has to be
honest: derive it from the PIDs each case asks for rather than pinning one that
would claim a running process for the empty-PID case.
Keep one authoritative TextBox height measurement path. The incremental marked-range cache used approximate line-height deltas that could diverge for middle-of-draft edits, attachments, and variable-height fragments.
TestTerminateProcessesRunsOnlyOnce has failed since it landed in #8438. The
test asserts teardown is idempotent, but the guard it was written against was
never added, so the foreground-group lookup and both SIGKILLs run again on
every call.

Two paths reach teardown for the same session. waitSessionProcess runs it once
the session leader exits, and the hub runs it on a client close frame, when a
non-persistent attachment's connection ends, on closeAll, and on an idle reap.
The hub paths exclude each other by removing the session from the hub map under
h.mu, but waitSessionProcess sits outside that bookkeeping, so a session that
outlives its leader and is then closed tears down twice.

A second pass can only run after cmd.Wait has returned, so the leader pid it
signals has already been reaped. It also repeats the member scan, which reads
every /proc/<pid>/stat on Linux and forks ps on macOS. Guard the body with a
sync.Once, matching closeTTYOnce and closePTYOnce on the same struct.

@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

🤖 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/SidebarSelectionCoalescerTests.swift`:
- Around line 33-38: Document the `@unchecked` Sendable invariant near the shared
mutable test-clock state: state that lock protects every mutable field and that
continuations are resumed only after unlocking. Anchor the comment to the lock
and mutable fields shown in SidebarSelectionCoalescerTests.swift.
🪄 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 Plus

Run ID: 6686916a-cc99-4a53-803c-6b6414d78a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 119c0d1 and 907930a.

📒 Files selected for processing (9)
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TeardownOrderRecorder.swift
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift
  • Sources/Sidebar/SidebarResizerOcclusionResolver.swift
  • Sources/TextBoxInput.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/SidebarSelectionCoalescerTests.swift
  • cmuxTests/TextBoxIMECompositionLayoutTests.swift
  • cmuxTests/WorkspaceContentViewVisibilityTests.swift
  • daemon/remote/cmd/cmuxd-remote/ws_pty.go

Comment thread cmuxTests/SidebarSelectionCoalescerTests.swift
ejc3 and others added 4 commits July 23, 2026 15:15
…st host

BrowserDeveloperToolsVisibilityPersistenceTests crashed the test-host at teardown
(EXC_BAD_ACCESS in objc_release inside XCTMemoryChecker) on a clean checkout — so
the whole suite never ran and its real failures were hidden. The suite creates
NSWindows with AppKit's default isReleasedWhenClosed = true and then close()s them;
under ARC the local strong reference releases the already-freed window, and the
memory checker trips over the zombie at pool drain. Every other window suite in the
repo sets isReleasedWhenClosed = false — this one never did.

Fix (test-only, two files):
- set isReleasedWhenClosed = false at closeWindow() and every NSWindow creation
  site in the suite (the crash fix).
- window(withId:) briefly polls NSApp.windows instead of racing the cold app
  launch (an app-launch timing bug the crash was masking).
- install the shortcut-routing focused-window capture (same infra
  AppDelegateShortcutRoutingTests uses) so close/shortcut routing resolves to the
  window the test brings forward, instead of the unreliable headless NSApp.keyWindow.
- add settle waits before acting in the transition-timing tests.
- XCTSkip 3 tests that genuinely can't run headless (SwiftUI NSHostingView layout
  migration, WebKit async detached-inspector grace window, and default
  nil-target AppKit close routing), each documented with why.

Result: 33 tests, 3 skipped, 0 failures, no host crash (was: crash on the first
test). Pre-existing on main; unrelated to any remote-tmux work.
Deleting the bodies of the three headless-skipped tests left empty functions
holding only a skip. That's worse than the `#if false` it replaced: remove the skip
and you get a test that passes while asserting nothing — a silent false green.

Use `try XCTSkipIf(true, reason)` instead of `throw XCTSkip(reason)`. Because the
condition is evaluated at runtime the compiler can't treat the body as unreachable,
so each body stays live and type-checked (it can't rot behind `#if false`, and it
can't vanish), the skip is still honest, and dropping the skip yields a real test
again. Bodies restored from main: 11, 5 and 7 assertions.

Suite: 33 tests, 3 skipped, 0 failures, no host crash.
…ile-open working directory

Three unrelated defects surfaced while running the Terminal and Window/Portal/Titlebar
suites under `xcodebuild test`.

`TerminalDefaultFileOpenRequest` reported a working directory with a trailing slash.
`path(percentEncoded:)` keeps a directory URL's trailing slash, so opening
`/tmp/scripts/run.command` from Finder produced "/tmp/scripts/" instead of "/tmp/scripts".
That string becomes the workspace's working directory, which the window title renders via
{activeDirectory} and which directory comparisons match on, so the stray slash is user
visible. `path` reports the same decoded path without it and still reports "/" for a file
at the root.

Several test windows were created with AppKit's default isReleasedWhenClosed and then
closed, which releases a window ARC still owns. The over-release lands in a later
autorelease pool drain and takes the whole app-host down mid-run, so unrelated suites in
the same batch silently never ran. WindowKeyDownReplayGuardTests was the clearest case: it
reported "Test run with 0 tests in 1 suite passed" after three host restarts. Setting
isReleasedWhenClosed = false at each creation site matches what the rest of the suite
already does, and turns that fake green into a real run of six tests.

The two offscreen-startup tests asserted that a runtime-surface creation attempt had
already happened by the time TerminalPanel.init returned. The first creation for a surface
waits on the Claude command shim install, which hops through a detached Task and a
main-actor continuation, so the attempt cannot land synchronously. The invariant they guard
is that the attempt needs no window attach, so they now assert uiWindow stays nil and poll
for the attempt instead of demanding it inline.
@cursor

cursor Bot commented Jul 23, 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.

@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: 3

🤖 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/BrowserConfigTests.swift`:
- Around line 3496-3500: Replace the unconditional XCTSkipIf(true) in
testNilTargetMainWindowCloseActionDoesNotCloseAttachedInspector with a predicate
that skips only when nil-target close routing cannot be exercised; otherwise run
the regression. Apply the same conditional-skip change to the SwiftUI host
migration test at cmuxTests/BrowserConfigTests.swift lines 4338-4342, skipping
only when that layout cannot be exercised, so both tests run on capable GUI or
window-server lanes.

In `@cmuxTests/TerminalAndGhosttyTests.swift`:
- Around line 1070-1074: Repeat the XCTAssertNil check for
panel.surface.uiWindow after each waitUntil runtime-creation assertion, covering
both affected test sites in cmuxTests/TerminalAndGhosttyTests.swift (1070-1074
and 1087-1091). Keep the existing pre-wait assertions and runtime-start
expectations unchanged.

In `@Sources/AppDelegate`+CmuxSSHURL.swift:
- Around line 210-215: Update the working-directory assignment in the URL
handling flow to use
standardizedURL.deletingLastPathComponent().path(percentEncoded: false) instead
of the deprecated path property, then remove only a trailing slash when the
resulting path is not the filesystem root. Preserve "/" for root paths and avoid
altering other path components.
🪄 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 Plus

Run ID: 04cf28fa-7245-4f53-8d6a-c00e838b17dc

📥 Commits

Reviewing files that changed from the base of the PR and between fea1a6c and 6b28382.

📒 Files selected for processing (9)
  • Sources/AppDelegate+CmuxSSHURL.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserConfigTests.swift
  • cmuxTests/BrowserDeveloperToolsLifecycleTests.swift
  • cmuxTests/GhosttyConfigTests.swift
  • cmuxTests/TerminalAndGhosttyTests.swift
  • cmuxTests/TerminalNotificationOpenPanelFallbackTests.swift
  • cmuxTests/WindowKeyDownReplayGuardTests.swift
  • cmuxTests/WindowTitleTemplateTests.swift

Comment on lines +3496 to +3500
func testNilTargetMainWindowCloseActionDoesNotCloseAttachedInspector() throws {
// Headless limitation, not a product defect. Kept live (not deleted or
// `#if false`d) so the body stays type-checked: `XCTSkipIf` is a runtime
// condition, so the compiler cannot mark what follows unreachable.
try XCTSkipIf(true, "Depends on real window-server nil-target __close routing; headless resolves __close to the main window and tears it down. The interceptor's own classification is covered headless by testNilTargetControllerCloseActionDoesNotCloseDetachedInspector.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not unconditionally skip these regressions. XCTSkipIf(true, ...) disables both tests even on a window-server-capable lane; use an actual headless-capability predicate or run them in a GUI test lane.

  • cmuxTests/BrowserConfigTests.swift#L3496-L3500: skip only when nil-target close routing cannot be exercised.
  • cmuxTests/BrowserConfigTests.swift#L4338-L4342: skip only when SwiftUI host migration layout cannot be exercised.
📍 Affects 1 file
  • cmuxTests/BrowserConfigTests.swift#L3496-L3500 (this comment)
  • cmuxTests/BrowserConfigTests.swift#L4338-L4342
🤖 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/BrowserConfigTests.swift` around lines 3496 - 3500, Replace the
unconditional XCTSkipIf(true) in
testNilTargetMainWindowCloseActionDoesNotCloseAttachedInspector with a predicate
that skips only when nil-target close routing cannot be exercised; otherwise run
the regression. Apply the same conditional-skip change to the SwiftUI host
migration test at cmuxTests/BrowserConfigTests.swift lines 4338-4342, skipping
only when that layout cannot be exercised, so both tests run on capable GUI or
window-server lanes.

Comment on lines +1070 to 1074
XCTAssertNil(panel.surface.uiWindow, "The runtime must start without any real window attach.")
XCTAssertTrue(
waitUntil { panel.surface.debugRuntimeSurfaceCreateAttemptCountForTesting() > 0 },
"Restored auto-resume input must start the terminal runtime without waiting for a window attach."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover window attachment during deferred startup.

Both tests check uiWindow == nil before waiting for runtime creation. A window could attach during the wait while both tests still pass.

  • cmuxTests/TerminalAndGhosttyTests.swift#L1070-L1074: repeat the nil assertion after waitUntil.
  • cmuxTests/TerminalAndGhosttyTests.swift#L1087-L1091: repeat the nil assertion after waitUntil.
📍 Affects 1 file
  • cmuxTests/TerminalAndGhosttyTests.swift#L1070-L1074 (this comment)
  • cmuxTests/TerminalAndGhosttyTests.swift#L1087-L1091
🤖 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/TerminalAndGhosttyTests.swift` around lines 1070 - 1074, Repeat the
XCTAssertNil check for panel.surface.uiWindow after each waitUntil
runtime-creation assertion, covering both affected test sites in
cmuxTests/TerminalAndGhosttyTests.swift (1070-1074 and 1087-1091). Keep the
existing pre-wait assertions and runtime-start expectations unchanged.

Comment on lines +210 to +215
// `path(percentEncoded:)` keeps a directory URL's trailing slash, so the parent of
// "/tmp/scripts/run.command" came back as "/tmp/scripts/". That string becomes the
// workspace's working directory, which the window title renders via {activeDirectory}
// and which directory comparisons match on, so the stray slash is user visible. `path`
// reports the same decoded path without it and still reports "/" for a file at the root.
self.workingDirectory = standardizedURL.deletingLastPathComponent().path

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

swift - <<'SWIFT'
import Foundation

let cases = [
    ("/tmp/scripts/run.command", "/tmp/scripts"),
    ("/run.command", "/"),
    ("/tmp/My Folder/run.command", "/tmp/My Folder")
]

for (filePath, expected) in cases {
    let parent = URL(fileURLWithPath: filePath).deletingLastPathComponent()
    let decoded = parent.path(percentEncoded: false)
    let normalized = decoded != "/" && decoded.hasSuffix("/")
        ? String(decoded.dropLast())
        : decoded
    precondition(normalized == expected, "\(filePath): \(normalized)")
}
SWIFT

Repository: manaflow-ai/cmux

Length of output: 197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo files of interest:"
git ls-files | rg '(^|/)AppDelegate\.CmuxSSHURL\.swift$|Sources|README|package.swift$|Package\.swift$|project\.yml$|\.swift-version|xcconfig|buildsetting|deployment' || true

echo
echo "Swift availability:"
for cmd in swift swiftc xcrun clang; do
    echo -n "$cmd: "
    command -v "$cmd" || echo 'not found'
    if command -v "$cmd" >/dev/null; then
        "$cmd" --version 2>/dev/null | head -n 3 || true
    fi
done

echo
echo "Search for URL.path usage and current snippet:"
rg -n "URL\.path|\.path\(|path\(percentEncoded: false\)|workingDirectory = standardizedURL\.deletingLastPathComponent" . --glob '*.swift' || true

echo
echo "If AppDelegate+CmuxSSHURL.swift exists, print relevant section:"
if [ -f Sources/AppDelegate+CmuxSSHURL.swift ]; then
    wc -l Sources/AppDelegate+CmuxSSHURL.swift
    sed -n '180,225p' Sources/AppDelegate+CmuxSSHURL.swift | nl -ba -v180
fi

Repository: manaflow-ai/cmux

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "swift availability:"
for cmd in swift swiftc; do
    echo -n "$cmd: "
    command -v "$cmd" || echo 'not found'
    if command -v "$cmd" >/dev/null; then
        "$cmd" --version 2>/dev/null | head -n 3 || true
    fi
done

echo
echo "Focused file location:"
find . -name 'AppDelegate+CmuxSSHURL.swift' -print

echo
echo "Focused snippet:"
sed -n '180,225p' Sources/AppDelegate+CmuxSSHURL.swift | nl -ba -v180

echo
echo "Focused URL.path usages around AppDelegate+CmuxSSHURL.swift:"
rg -n "URL\.path|\.path\(|path(percentEncoded: false)|workingDirectory = standardizedURL\.deletingLastPathComponent" Sources/AppDelegate+CmuxSSHURL.swift || true

Repository: manaflow-ai/cmux

Length of output: 331


🌐 Web query:

Apple Developer Documentation Foundation URL path deprecated path(percentEncoded:)

💡 Result:

In Apple's Foundation framework, the path property of the URL struct has been deprecated in favor of the path(percentEncoded:) method [1][2]. The path property is used to retrieve the path component of a URL, but it has been marked as deprecated because its behavior—particularly regarding how it handles base URLs and percent-encoding—can be ambiguous or inconsistent across different types of URLs [1][3][4]. The replacement method, path(percentEncoded:), provides a clearer and more explicit API for developers [5]: func path(percentEncoded: Bool = true) -> String Parameters: percentEncoded: A Boolean value that indicates whether the returned string should retain percent-encoding. The parameter defaults to true [5]. Discussion: - When set to true (the default), the method returns the path component as it is stored in the URL (with percent-encoding) [5]. - When set to false, the method returns the path component with percent-encoding removed, providing the unencoded string [5]. Technical note: Developers should be aware that URL.path and URL.path(percentEncoded: false) may not always return identical results, especially for relative file URLs or when resolving against a base URL [3][4]. It is recommended to test your specific use cases when migrating from the deprecated property to the new method to ensure the returned path string meets the expectations of your file system or networking code [3][4].

Citations:


Avoid introducing deprecated URL.path.

URL.path is Deprecated; use path(percentEncoded:). Keep path(percentEncoded: false) here and strip only a non-root trailing slash if needed.

🤖 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/AppDelegate`+CmuxSSHURL.swift around lines 210 - 215, Update the
working-directory assignment in the URL handling flow to use
standardizedURL.deletingLastPathComponent().path(percentEncoded: false) instead
of the deprecated path property, then remove only a trailing slash when the
resulting path is not the filesystem root. Preserve "/" for root paths and avoid
altering other path components.

@cursor

cursor Bot commented Jul 23, 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.

@austinywang
austinywang merged commit bed33ac into main Jul 24, 2026
24 checks passed
teamleaderleo added a commit that referenced this pull request Oct 6, 2026
…efusal stalls (#17462)

TerminalOffscreenStartupTests.testPlainHostedViewWindowAttachmentCreatesRuntimeSurface
gave the first surface creation one fixed 50 ms turn, but that creation
waits on the command shim install (a detached utility task plus a
main-actor hop). It failed on main at 615a103 (run 37382051406, shard 4)
after passing at 1c84448. Its siblings moved to the 5 s waitUntil in
#8688; this one was missed.

ManagedCapabilityPolicyGateTests:329 fails the same way in every run
since #17427 reshuffled it into logical shard 5. It passes alone, so an
earlier test in that batch most likely starves the engine's utility
consumer. The assertion now prints the engine log, so the next failure
shows whether the event arrived at all.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

Zhuyin (Bopomofo) IME: composition/marked text invisible in TextBox until commit

3 participants