Skip to content

Fix Raycast paste fallback regression#2768

Merged
austinywang merged 4 commits intomainfrom
issue-2762-raycast-paste-regression
Apr 10, 2026
Merged

Fix Raycast paste fallback regression#2768
austinywang merged 4 commits intomainfrom
issue-2762-raycast-paste-regression

Conversation

@austinywang
Copy link
Copy Markdown
Contributor

@austinywang austinywang commented Apr 10, 2026

Summary

  • fall back to alternate plain text when mixed Raycast-style clipboard payloads advertise unusable image types
  • keep real image pastes on the existing image-first path while distinguishing decodable image payloads from rejected ones
  • repair transient terminal focus before command-equivalent paste events only for genuinely broken responder states, without retargeting live terminal responders during pane-transition drift
  • add focused regression coverage for mixed clipboard payloads and command-equivalent focus repair policy

Closes #2762.

Testing

  • Not run locally per repo policy
  • Built tagged Debug app: ./scripts/reload.sh --tag issue-2762-raycast-paste-regression
  • Manual verification on tagged Debug build: ./scripts/reload.sh --tag issue-2762-raycast-paste-regression --launch
  • User-verified that Raycast Clipboard History / Snippets paste now works again without the alert beep

Note

Medium Risk
Touches keyboard focus repair and pasteboard parsing logic, which can affect global shortcut handling and clipboard pastes across the app. Changes are scoped and backed by new targeted unit tests, but could still introduce edge-case regressions in input routing.

Overview
Fixes paste handling for Raycast-style mixed clipboard payloads by distinguishing no decodable image vs rejected/unusable image and falling back to alternate plain text only when appropriate.

Refines terminal focus “key repair” so Cmd-modified key equivalents only retarget focus when the responder state is genuinely broken (e.g., NSWindow is first responder), while keeping existing behavior for non-command key events.

Adds regression tests covering the new command-equivalent repair decision and the mixed clipboard payload plain-text fallback.

Reviewed by Cursor Bugbot for commit 9b25888. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 10, 2026

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 10, 2026 9:53am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

Adds structured image materialization outcomes and a plain-text fallback to paste planning; updates paste planner to act on those outcomes (file, reject, or fallback to text); refactors pasteboard helper and adds focused-terminal key-repair gating plus tests.

Changes

Cohort / File(s) Summary
Pasteboard helper
Sources/GhosttyTerminalView.swift
Add GhosttyPasteboardHelper.ImageFileMaterializationResult enum, implement materializeImageFileURLIfNeeded(...) returning structured outcomes, add fallbackPlainTextContents(from:), and update saveImageFileURLIfNeeded(...) to wrap the result-based flow.
Paste planning
Sources/TerminalImageTransfer.swift
Switch paste planning to call materializeImageFileURLIfNeeded(...); handle .saved.fileURLs, .rejected.reject, and .noDecodableImagePayload → try fallbackPlainTextContents(from:) to return .insertText when available.
Tests — paste behavior
cmuxTests/TerminalAndGhosttyTests.swift
Add testPastePlanFallsBackToAlternatePlainTextWhenImageTypeIsUnusable to verify fallback to plain-text when image payload exists but is unusable.
Keyboard routing & tests
Sources/AppDelegate.swift, cmuxTests/ShortcutAndCommandPaletteTests.swift
Introduce focusedTerminalKeyRepairNeeded(...) and shouldRepairFocusedTerminalCommandEquivalentInputs(...), refactor responderNeedsFocusedTerminalKeyRepair(...) and repairFocusedTerminalKeyboardRoutingIfNeeded(...) gating (command vs plain), update debug logging, and add CommandEquivalentTransientFocusRepairTests.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Planner as TerminalImageTransferPlanner
    participant Helper as GhosttyPasteboardHelper
    participant PB as NSPasteboard
    participant Result as TerminalPasteTarget

    User->>Planner: preparePaste(mode: .paste)
    Planner->>Helper: materializeImageFileURLIfNeeded(from: PB)
    Helper->>PB: read image payloads
    PB-->>Helper: image bytes / no usable image
    Helper-->>Planner: ImageFileMaterializationResult
    alt saved(URL)
        Planner->>Result: .fileURLs([URL])
    else rejectedImagePayload
        Planner->>Result: .reject
    else noDecodableImagePayload
        Planner->>Helper: fallbackPlainTextContents(from: PB)
        Helper->>PB: read plain-text UTIs
        PB-->>Helper: String?
        alt plain-text found
            Planner->>Result: .insertText(string)
        else no text
            Planner->>Result: continue URL handling / .reject
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped upon the clipboard trail,
When TIFFs sputter and pixels fail,
I sniff for words where images choke,
I paste the plain text you invoked —
A tiny rabbit fix, hooray! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Fix Raycast paste fallback regression' directly and clearly summarizes the main change: fixing a regression in Raycast clipboard paste functionality through improved fallback logic.
Linked Issues check ✅ Passed The changes fully address #2762's requirements: distinguishing unusable vs. rejected image payloads [GhosttyTerminalView.swift, TerminalImageTransfer.swift], falling back to alternate plain text when needed [TerminalImageTransfer.swift], repairing command-equivalent focus [AppDelegate.swift], and adding focused test coverage [TerminalAndGhosttyTests.swift, ShortcutAndCommandPaletteTests.swift].
Out of Scope Changes check ✅ Passed All changes are scoped to addressing the Raycast paste regression: pasteboard helper restructuring, image materialization result handling, focus repair for command-equivalents, and related test coverage—no extraneous modifications detected.
Description check ✅ Passed The PR description provides a comprehensive summary of changes, testing methodology, and includes a clear issue reference (#2762).

✏️ 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-2762-raycast-paste-regression

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
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 10, 2026

Greptile Summary

This PR fixes a regression where Raycast-style clipboard payloads — containing both unusable image data (e.g. invalid TIFF) and valid plain text — were silently rejected during paste instead of falling back to the text. The fix adds a fallbackPlainTextContents helper that is called in preparePaste after image materialization fails, preserving the image-first path for real pastes.

Confidence Score: 5/5

Safe to merge — minimal, targeted fix with correct fallback placement and a proper behavioral regression test.

All findings are P2 or below. The fallback logic is sound: it only fires after image materialization genuinely fails, so real image pastes are unaffected. The test exercises the new code path through observable runtime behavior (pasteboard → plan), not implementation shape.

No files require special attention.

Important Files Changed

Filename Overview
Sources/TerminalImageTransfer.swift Adds a plain-text fallback in preparePaste after saveImageFileURLIfNeeded returns nil, correctly handling clipboard payloads with unusable image data alongside valid text.
Sources/GhosttyTerminalView.swift Adds fallbackPlainTextContents as a thin public wrapper around private plainTextContents, exposing it to TerminalImageTransferPlanner; logic is correct and minimal.
cmuxTests/TerminalAndGhosttyTests.swift Adds a regression test that writes invalid TIFF bytes + plain text to a named pasteboard and asserts the plan resolves to insertText, testing observable runtime behavior correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[preparePaste called] --> B{fileURLs non-empty?}
    B -- yes --> Z1[.fileURLs]
    B -- no --> C{stringContents non-nil?}
    C -- yes --> Z2[.insertText from rich/plain text]
    C -- no\nhasImageData=true,\nno HTML/RTF/RTFD --> D{saveImageFileURLIfNeeded\nreturns URL?}
    D -- yes\nreal image --> Z3[.fileURLs imageURL]
    D -- no\ninvalid image data --> E{fallbackPlainTextContents\nnon-nil? NEW}
    E -- yes\nRaycast plain text --> Z4[.insertText fallback text]
    E -- no --> F{URL string on pasteboard?}
    F -- yes --> Z5[.insertText escaped URL]
    F -- no --> Z6[.reject]
Loading

Reviews (1): Last reviewed commit: "Fix Raycast paste fallback regression" | Re-trigger Greptile

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmuxTests/ShortcutAndCommandPaletteTests.swift (1)

63-91: Optional: add the remaining truth-table branch for future-proofing.

Consider adding flags: [.command], responderIsWindow: true, responderHasViableKeyRoutingOwner: true to explicitly lock the “window responder always repairs” behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/ShortcutAndCommandPaletteTests.swift` around lines 63 - 91, Add the
missing truth-table test case that covers flags: [.command], responderIsWindow:
true, responderHasViableKeyRoutingOwner: true to explicitly assert the “window
responder always repairs” behavior; create a new test (e.g.,
testRepairsCommandEquivalentWhenWindowResponderHasViableOwner) that calls
shouldRepairFocusedTerminalCommandEquivalentInputs with those arguments and
XCTAssertTrue the result so the four-way branch is fully covered alongside the
existing tests (testRepairsCommandEquivalentWhenFirstResponderFallsBackToWindow,
testRepairsCommandEquivalentWhenResponderHasNoViableOwner,
testDoesNotRepairCommandEquivalentWhenResponderHasViableOwner).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 1833-1843: The current
shouldRepairFocusedTerminalCommandEquivalentInputs implementation returns false
for Cmd-modified events whenever any viable in-window owner exists, which skips
the hosted view’s responderMatchesPreferredKeyboardFocus check and allows stale
responders to keep routing command equivalents; update the logic in
shouldRepairFocusedTerminalCommandEquivalentInputs (and the corresponding branch
in repairFocusedTerminalKeyboardRoutingIfNeeded(window:event:)) so that when
.command is present you query the focused terminal’s
hostedView.responderMatchesPreferredKeyboardFocus(currentFirstResponder,
window:) (or equivalent preferred keyboard target check) and only skip repair if
that check returns true—i.e., repair unless the hosted view reports the current
responder already matches the focused terminal’s preferred keyboard focus; apply
the same fix to the other occurrence around lines 5729-5744.

---

Nitpick comments:
In `@cmuxTests/ShortcutAndCommandPaletteTests.swift`:
- Around line 63-91: Add the missing truth-table test case that covers flags:
[.command], responderIsWindow: true, responderHasViableKeyRoutingOwner: true to
explicitly assert the “window responder always repairs” behavior; create a new
test (e.g., testRepairsCommandEquivalentWhenWindowResponderHasViableOwner) that
calls shouldRepairFocusedTerminalCommandEquivalentInputs with those arguments
and XCTAssertTrue the result so the four-way branch is fully covered alongside
the existing tests
(testRepairsCommandEquivalentWhenFirstResponderFallsBackToWindow,
testRepairsCommandEquivalentWhenResponderHasNoViableOwner,
testDoesNotRepairCommandEquivalentWhenResponderHasViableOwner).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7edc9608-9ee4-4ee2-95d6-9909685f301e

📥 Commits

Reviewing files that changed from the base of the PR and between 9b4e447 and eabe056.

📒 Files selected for processing (4)
  • Sources/AppDelegate.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/TerminalImageTransfer.swift
  • cmuxTests/ShortcutAndCommandPaletteTests.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/TerminalImageTransfer.swift
  • Sources/GhosttyTerminalView.swift

Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate.swift
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmuxTests/ShortcutAndCommandPaletteTests.swift">

<violation number="1" location="cmuxTests/ShortcutAndCommandPaletteTests.swift:83">
P2: This test now duplicates an existing viable-owner case instead of validating responder-drift behavior, so the regression suite no longer protects the drift-specific command-equivalent repair policy.

(Based on your team's feedback about focused-terminal responder-drift repair policy.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

)
}

func testDoesNotRepairCommandEquivalentWhenLiveResponderDiffersFromSelectedPane() {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 10, 2026

Choose a reason for hiding this comment

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

P2: This test now duplicates an existing viable-owner case instead of validating responder-drift behavior, so the regression suite no longer protects the drift-specific command-equivalent repair policy.

(Based on your team's feedback about focused-terminal responder-drift repair policy.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmuxTests/ShortcutAndCommandPaletteTests.swift, line 83:

<comment>This test now duplicates an existing viable-owner case instead of validating responder-drift behavior, so the regression suite no longer protects the drift-specific command-equivalent repair policy.

(Based on your team's feedback about focused-terminal responder-drift repair policy.) </comment>

<file context>
@@ -76,19 +75,17 @@ final class CommandEquivalentTransientFocusRepairTests: XCTestCase {
 
-    func testRepairsCommandEquivalentWhenResponderDriftsWithinSameWindow() {
-        XCTAssertTrue(
+    func testDoesNotRepairCommandEquivalentWhenLiveResponderDiffersFromSelectedPane() {
+        XCTAssertFalse(
             shouldRepairFocusedTerminalCommandEquivalentInputs(
</file context>
Fix with Cubic

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is kicking off a free cloud agent to fix this issue. This run is complimentary, but you can enable autofix for all future PRs in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9b25888. Configure here.

responderHasViableKeyRoutingOwner: true
)
)
}
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 test masks missing coverage for window+viable-owner case

Low Severity

testDoesNotRepairCommandEquivalentWhenLiveResponderDiffersFromSelectedPane and testDoesNotRepairCommandEquivalentWhenResponderHasViableOwner have identical bodies — same function, same arguments, same assertion. The first test name suggests it may have been intended to cover responderIsWindow: true, responderHasViableKeyRoutingOwner: true, which is the only input combination of shouldRepairFocusedTerminalCommandEquivalentInputs not covered by the test suite.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9b25888. Configure here.

@austinywang austinywang merged commit ebce4c5 into main Apr 10, 2026
21 checks passed
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.

Raycast Clipboard History / Snippets paste silently fails in cmux v0.63.2 (Cmd+V and Ctrl+V both no-op)

1 participant