Skip to content

fix(ui): improve clipboard reliability across terminals - #320

Merged
lavaman131 merged 4 commits into
mainfrom
flora131/bug/copy-paste
Mar 3, 2026
Merged

fix(ui): improve clipboard reliability across terminals#320
lavaman131 merged 4 commits into
mainfrom
flora131/bug/copy-paste

Conversation

@flora131

@flora131 flora131 commented Mar 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Introduces a robust, platform-aware clipboard adapter that improves both copy and paste reliability across different terminal emulators using OSC 52 escape sequences with native command fallbacks.

Key Changes

Core Implementation

  • New clipboard adapter module (src/ui/utils/clipboard.ts)
    • Implements Strategy pattern for clipboard read/write operations
    • Dual-mode operation: OSC 52 + native OS commands for maximum compatibility
    • Platform-specific support: macOS (osascript/pbcopy/pbpaste), Linux X11 (xclip/xsel), Linux Wayland (wl-copy/wl-paste), Windows (PowerShell)
    • TMux/screen passthrough support for remote sessions
    • Graceful fallback chaining when primary method fails

UI Improvements

  • Enhanced keyboard shortcuts: Added Cmd+C/Cmd+V support for macOS users (in addition to Ctrl+C/Ctrl+V)
  • Improved paste handling: Fallback to native clipboard read when bracketed paste provides empty content
  • Unified clipboard handling: Consistent behavior for textarea selection and mouse-drag selection
  • Better documentation: Enhanced comments about mouse tracking and native terminal selection

Testing

  • Comprehensive unit tests (clipboard.test.ts - 143 lines)
    • 11 test cases covering OSC 52, platform-specific commands, and fallback behavior
    • Validates lazy strategy resolution and multi-call reusability
    • Mocks for Bun.which and Bun.spawnSync to test native command paths
    • All tests passing ✅

Technical Details

The adapter uses a dual-strategy approach:

  1. OSC 52 escape sequences: Works over SSH/tmux and modern terminals (iTerm2, Kitty, Alacritty, WezTerm, Ghostty)
  2. Native OS commands: Platform-specific clipboard access for maximum reliability

Both strategies are attempted for copy operations, with automatic fallback if one fails.

Platform Support

Platform Copy Paste Commands
macOS osascript, pbcopy, pbpaste + OSC 52
Linux (X11) xclip, xsel + OSC 52
Linux (Wayland) wl-copy, wl-paste + OSC 52
Windows PowerShell clipboard cmdlets + OSC 52

User-Facing Changes

Copy Behavior

  • Programmatic copy (mouse-drag selection): Auto-copies on release using best available method
  • Keyboard copy (Ctrl+C or Cmd+C): Works with both textarea and mouse selections
  • Native terminal selection: Hold Shift (Linux/Windows) or Option (macOS/iTerm2) while clicking to use terminal's native selection

Paste Behavior

  • Bracketed paste: Primary method for modern terminals
  • Keyboard paste (Ctrl+V or Cmd+V): Manual paste with native clipboard fallback
  • Empty paste protection: Falls back to reading native clipboard when bracketed paste is empty

Files Changed

  • src/ui/utils/clipboard.ts — New clipboard adapter (173 lines)
  • src/ui/utils/clipboard.test.ts — Unit tests (143 lines)
  • src/ui/chat.tsx — Integration of clipboard adapter with enhanced keyboard shortcuts
  • src/ui/index.ts — Improved mouse mode documentation

Backwards Compatibility

No breaking changes — Fully backwards compatible with existing clipboard behavior. The adapter transparently enhances reliability without changing the user-facing API.

Testing Instructions

  1. Test copy: Select text with mouse, verify auto-copy works
  2. Test keyboard copy: Select text, press Ctrl+C (or Cmd+C on macOS)
  3. Test paste: Press Ctrl+V (or Cmd+V on macOS), verify clipboard content is pasted
  4. Test on different terminals: iTerm2, Kitty, Alacritty, Terminal.app, VS Code integrated terminal
  5. Test over SSH/tmux: Verify OSC 52 passthrough works correctly

Add a clipboard adapter that uses OSC 52 with native command fallbacks and Apple Terminal preference for more consistent copy behavior. Route selection copy paths through the adapter and handle Cmd+C like Ctrl+C when text is selected.

Assistant-model: openai/gpt-5.3-codex
Copilot AI review requested due to automatic review settings March 3, 2026 05:20
@claude

claude Bot commented Mar 3, 2026

Copy link
Copy Markdown

PR Review: fix(ui): improve clipboard copy reliability across terminals

Thanks for this PR! The clipboard adapter implementation is well-structured and addresses real cross-terminal compatibility issues. Here's my review:

✅ Strengths

  1. Clean Architecture: The Strategy pattern is appropriate here and well-implemented. The ClipboardWriteStrategy interface with Osc52Strategy and NativeCommandStrategy implementations is clean and extensible.

  2. Good Platform Coverage: Detection of pbcopy, wl-copy, xclip, and xsel covers the major clipboard tools across macOS and Linux.

  3. Robust Fallback Chain: The fallback mechanism (primary → secondary) ensures clipboard operations succeed even when the primary method fails at runtime.

  4. Lazy Initialization: Strategy resolution is deferred until first copy, which is good for performance.

  5. Well-documented: JSDoc comments explain the design decisions and rationale clearly.

  6. Solid Test Coverage: Tests cover OSC 52 path, native fallback, Apple Terminal preference, lazy initialization, and empty string handling.


🔧 Suggestions

1. Clipboard ref initialization pattern (chat.tsx:2258-2262)

The current pattern works but could be cleaner using a lazy ref pattern:

// Current
const clipboardRef = useRef<ClipboardAdapter | null>(null);
if (!clipboardRef.current) {
  clipboardRef.current = createClipboardAdapter(renderer);
}
const clipboard = clipboardRef.current;

// Suggested (idiomatic lazy ref)
const clipboard = useMemo(() => createClipboardAdapter(renderer), [renderer]);

Since renderer is stable, useMemo is cleaner and avoids the conditional in render.

2. Comment mentions VS Code but doesn't detect it (clipboard.ts:108-110)

The comment for shouldPreferNativeClipboard() mentions VS Code terminal having OSC 52 issues, but only Apple Terminal is checked:

// Comment says: "macOS Terminal.app, VS Code terminal"
// But code only checks:
return termProgram === "apple_terminal";

Consider either:

  • Adding VS Code detection (vscode in TERM_PROGRAM)
  • Or updating the comment to match the actual behavior

3. Minor: Extra blank lines added

There are two extra blank lines that look accidental:

  • chat.tsx:2309 (after toggleVerbose)
  • chat.tsx:6579 (before "Skip other keyboard handling")

🧪 Test Coverage Gaps

Consider adding tests for:

  1. Both strategies failing: What happens when both OSC 52 and native fallback fail? The code returns false, but a test would document this behavior.

  2. Linux-specific paths: The test file only tests macOS (pbcopy). Consider adding platform-conditional tests for xclip/xsel/wl-copy detection logic.


⚠️ Minor Concerns

1. Bun.spawnSync stdin typing (clipboard.ts:82-83)

stdin: new TextEncoder().encode(text),

Verify this works correctly with Bun's spawnSync. The Bun docs show stdin accepting a Blob, TypedArray, or string. Uint8Array should work, but it's worth confirming in the tests.

2. Error swallowing (clipboard.ts:89-90)

The catch block silently returns false. This is probably fine for a clipboard utility, but consider whether logging (even at debug level) would help diagnose issues in the field.


📝 Cmd+C Handling (chat.tsx:6240)

Good addition! Handling event.meta alongside event.ctrl for the "c" key ensures macOS users can use Cmd+C naturally. The existing selection-detection logic correctly routes this to copy instead of interrupt/exit.


Summary

This is a solid PR that improves clipboard reliability. The code is well-structured, documented, and tested. The suggestions above are minor polish items rather than blockers.

Recommendation: ✅ Approve with minor suggestions

Copilot AI left a comment

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.

Pull request overview

This PR introduces a platform-aware clipboard adapter to improve copy reliability in the TUI across terminals by preferring OSC 52 when available and falling back to native clipboard commands when needed, then routes selection/copy paths through that adapter.

Changes:

  • Added ClipboardAdapter implementation with OSC 52 primary strategy and native-command fallbacks (pbcopy/wl-copy/xclip/xsel), including an Apple Terminal preference.
  • Updated ChatApp copy-on-selection and explicit copy handling to use the adapter; expanded key handling to treat Cmd+C similarly to Ctrl+C.
  • Added Bun tests for strategy resolution and fallback behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/ui/utils/clipboard.ts New clipboard adapter with OSC 52 + native command strategy selection and fallback chaining.
src/ui/utils/clipboard.test.ts New tests covering adapter behavior and resolution logic (currently includes OS-dependent assumptions).
src/ui/index.ts Updated renderer/mouse tracking comment to better describe selection/copy behavior.
src/ui/chat.tsx Routes selection copy and Ctrl/Cmd+C copy paths through the adapter and adjusts key handling.
Comments suppressed due to low confidence (1)

src/ui/chat.tsx:6246

  • This change makes Cmd+C (event.meta) go through the same interrupt/exit path as Ctrl+C whenever there is no selection. The PR description only mentions treating Cmd+C like Ctrl+C when text is selected; as written, a habitual Cmd+C can now abort/exit even when nothing is selected. Consider gating the event.meta handling to only the copy-with-selection branch (and otherwise ignore/meta-pass-through), while keeping Ctrl+C semantics unchanged.
        // Ctrl+C / Cmd+C handling must work everywhere (even in dialogs) for double-press exit
        if ((event.ctrl || event.meta) && event.name === "c") {
          const textarea = textareaRef.current;
          // If textarea or renderer has selection and no dialog is active, copy instead of interrupt/exit
          const hasRendererSelection = !!renderer.getSelection()?.getSelectedText();
          if (!activeQuestion && !showModelSelector && (textarea?.hasSelection() || hasRendererSelection)) {
            void handleCopy();

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ui/utils/clipboard.test.ts Outdated
});

afterEach(() => {
process.env.TERM_PROGRAM = originalTermProgram;

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

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

In afterEach, assigning process.env.TERM_PROGRAM = originalTermProgram will set the env var to the string "undefined" when it was originally unset (common in Node/Bun). This can leak state into later tests. Restore by delete process.env.TERM_PROGRAM when originalTermProgram === undefined, otherwise set it back to the original value (same pattern used in src/utils/detect.test.ts).

Suggested change
process.env.TERM_PROGRAM = originalTermProgram;
if (originalTermProgram === undefined) {
delete process.env.TERM_PROGRAM;
} else {
process.env.TERM_PROGRAM = originalTermProgram;
}

Copilot uses AI. Check for mistakes.
Comment thread src/ui/utils/clipboard.test.ts Outdated
Comment on lines +66 to +82
test("falls back to native clipboard when OSC 52 write fails", () => {
const renderer = makeMockRenderer({ osc52Supported: true, copyResult: false });
const whichSpy = spyOn(Bun, "which").mockImplementation((cmd: string) => {
if (cmd === "pbcopy") return "/usr/bin/pbcopy" as ReturnType<typeof Bun.which>;
return null as ReturnType<typeof Bun.which>;
});
const spawnSpy = spyOn(Bun, "spawnSync").mockReturnValue({
success: true,
} as ReturnType<typeof Bun.spawnSync>);

const adapter = createClipboardAdapter(renderer);
const result = adapter.copy("hello");

expect(result).toBe(true);
expect(renderer.copyToClipboardOSC52).toHaveBeenCalledWith("hello");
expect(spawnSpy).toHaveBeenCalled();

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

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

This test hard-codes the macOS-only pbcopy native command. CI runs on ubuntu-latest, where detectNativeClipboardCommand() never checks pbcopy, so the native strategy will be null and spawnSync won't be called—making this test fail/flaky across platforms. Adjust the mock to return a native command that matches process.platform (e.g. wl-copy/xclip on linux), or refactor detectNativeClipboardCommand/platform detection to be injectable so the test can force a deterministic command.

Copilot uses AI. Check for mistakes.
Comment thread src/ui/utils/clipboard.test.ts Outdated
Comment on lines +87 to +104
test("prefers native clipboard on Apple Terminal even if OSC 52 is reported", () => {
process.env.TERM_PROGRAM = "Apple_Terminal";

const renderer = makeMockRenderer({ osc52Supported: true, copyResult: true });
const whichSpy = spyOn(Bun, "which").mockImplementation((cmd: string) => {
if (cmd === "pbcopy") return "/usr/bin/pbcopy" as ReturnType<typeof Bun.which>;
return null as ReturnType<typeof Bun.which>;
});
const spawnSpy = spyOn(Bun, "spawnSync").mockReturnValue({
success: true,
} as ReturnType<typeof Bun.spawnSync>);

const adapter = createClipboardAdapter(renderer);
const result = adapter.copy("hello");

expect(result).toBe(true);
expect(spawnSpy).toHaveBeenCalled();
expect((renderer.copyToClipboardOSC52 as ReturnType<typeof mock>).mock.calls.length).toBe(0);

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

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

Same issue as above: this test assumes pbcopy is the native clipboard command even though the suite runs on Linux in CI. On non-darwin platforms detectNativeClipboardCommand() won’t select pbcopy, so spawnSync will not be invoked and the expectations will fail. Make the command mocked by Bun.which conditional on process.platform (linux: wl-copy/xclip/xsel, darwin: pbcopy) or inject platform/command resolution so the test can force the native strategy.

Copilot uses AI. Check for mistakes.
Comment thread src/ui/utils/clipboard.test.ts Outdated
Comment on lines +114 to +126
const adapter = createClipboardAdapter(renderer);

// On macOS (our test platform), this will attempt pbcopy as the native fallback.
// The copy result depends on platform availability, but OSC 52 should NOT be the
// primary path. We verify by checking that the adapter was created successfully
// and doesn't throw.
const result = adapter.copy("hello");
// On macOS with pbcopy available, this should succeed via native fallback
if (process.platform === "darwin") {
expect(result).toBe(true);
}
// Regardless of platform, the adapter should not throw
expect(typeof result).toBe("boolean");

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

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

This test name claims "does not call OSC 52 as primary", but it never asserts whether copyToClipboardOSC52 was called and it relies on whatever native tools happen to exist on the host. To actually validate the resolution logic deterministically, mock Bun.which/Bun.spawnSync to force a native strategy on the current platform, then assert that renderer.copyToClipboardOSC52 is not called (or only called as fallback) when osc52Supported is false.

Suggested change
const adapter = createClipboardAdapter(renderer);
// On macOS (our test platform), this will attempt pbcopy as the native fallback.
// The copy result depends on platform availability, but OSC 52 should NOT be the
// primary path. We verify by checking that the adapter was created successfully
// and doesn't throw.
const result = adapter.copy("hello");
// On macOS with pbcopy available, this should succeed via native fallback
if (process.platform === "darwin") {
expect(result).toBe(true);
}
// Regardless of platform, the adapter should not throw
expect(typeof result).toBe("boolean");
// Force native clipboard strategy resolution deterministically by mocking
// Bun.which / Bun.spawnSync, so the test does not depend on host tools.
const whichSpy = spyOn(Bun, "which").mockReturnValue("/usr/bin/fake-clipboard" as any);
const spawnSpy = spyOn(Bun, "spawnSync").mockReturnValue({
success: true,
exitCode: 0,
stdout: new TextEncoder().encode(""),
stderr: new TextEncoder().encode(""),
} as any);
const adapter = createClipboardAdapter(renderer);
const result = adapter.copy("hello");
// Native path should be used as the primary strategy when OSC 52 is not supported.
expect(result).toBe(true);
expect(whichSpy.mock.calls.length).toBeGreaterThan(0);
expect(spawnSpy.mock.calls.length).toBeGreaterThan(0);
// OSC 52 must not be used as the primary copy mechanism in this scenario.
expect(
(renderer.copyToClipboardOSC52 as ReturnType<typeof mock>).mock.calls.length
).toBe(0);
whichSpy.mockRestore();
spawnSpy.mockRestore();

Copilot uses AI. Check for mistakes.
Rely on renderer OSC 52 capability checks for the primary copy path and keep a narrow pbcopy fallback for macOS. This removes custom multi-platform strategy resolution while preserving Apple Terminal reliability via native copy first.

Assistant-model: openai/gpt-5.3-codex
@claude

claude Bot commented Mar 3, 2026

Copy link
Copy Markdown

PR Review: fix(ui): improve clipboard copy reliability across terminals

Thanks for this PR! The Strategy pattern approach for clipboard operations is well-designed and the lazy initialization is a nice touch. Here's my feedback:


✅ Strengths

  1. Clean architecture: The Strategy pattern with ClipboardAdapter provides a nice abstraction over different clipboard methods
  2. Lazy strategy resolution: Only checking OSC 52 support on first copy is efficient
  3. Good test coverage: The test file covers OSC 52 scenarios, fallback behavior, and reusability well
  4. Apple Terminal handling: Smart to detect and prefer pbcopy for Terminal.app given its OSC 52 inconsistencies
  5. Cmd+C support: Good addition for macOS users (line 6240 in chat.tsx)

⚠️ Issues to Address

1. PR Description vs Implementation Mismatch (High)

The PR description claims support for:

  • Linux (Wayland): wl-copy
  • Linux (X11): xclip or xsel

However, only pbcopy (macOS) is implemented in clipboard.ts. The implementation only has:

function canUsePbcopy(): boolean {
  return process.platform === "darwin" && Bun.which("pbcopy") !== null;
}

Suggestion: Either update the PR description to accurately reflect the macOS-only native fallback, or implement the Linux clipboard commands as described.

2. React Initialization Pattern (Medium)

In chat.tsx (lines 2255-2260):

const clipboardRef = useRef<ClipboardAdapter | null>(null);
if (!clipboardRef.current) {
  clipboardRef.current = createClipboardAdapter(renderer);
}
const clipboard = clipboardRef.current;

This pattern works but is unconventional. Consider using useMemo for clearer intent:

const clipboard = useMemo(() => createClipboardAdapter(renderer), [renderer]);

Or if you need ref stability across re-renders without re-creation:

const clipboardRef = useRef<ClipboardAdapter>();
useEffect(() => {
  clipboardRef.current = createClipboardAdapter(renderer);
}, [renderer]);

3. Extra Blank Lines (Low)

Minor style issue - there are extra blank lines added:

  • chat.tsx line 2309 (after toggleVerbose)
  • chat.tsx line 6579 (after showModelSelector check)

These should be removed for consistency.

4. Missing Test Coverage (Medium)

  • No test for the Cmd+C (event.meta && event.name === "c") handling added in chat.tsx
  • No integration test for Linux scenarios (though this depends on resolving issue add agent instructions #1)
  • Consider adding a test for when Bun.spawnSync throws an exception (not just when it returns success: false)

💡 Suggestions (Optional)

  1. Add debug logging: When clipboard operations fail silently, it's hard to troubleshoot. Consider adding optional debug logging (respecting the verbose mode flag).

  2. Consider empty string handling: While the test shows empty string doesn't throw, consider whether copying an empty string should be a no-op that returns true immediately, saving a subprocess spawn or OSC 52 write.

  3. Type safety in tests: The as unknown as CliRenderer cast in tests could hide type mismatches. Consider creating a proper mock type that extends Partial<CliRenderer>.


Summary

This is a solid improvement to clipboard handling. The main blocker is the discrepancy between documentation (claiming Linux support) and implementation (only macOS). Once that's resolved and the minor issues addressed, this looks good to merge.

Verdict: Request changes for documentation/implementation alignment, approve after updates.

@claude

claude Bot commented Mar 3, 2026

Copy link
Copy Markdown

PR Review: fix(ui): improve clipboard copy reliability across terminals

Overall, this is a well-structured PR with clean implementation and excellent test coverage. The Strategy pattern is applied appropriately, and the code leverages OpenTUI's existing APIs rather than reinventing the wheel.


✅ Strengths

Code Quality

  • Clean separation of concerns with the new clipboard.ts module (90 lines, focused responsibility)
  • Good use of the Strategy pattern with lazy initialization
  • Excellent JSDoc documentation with usage examples
  • Proper leveraging of OpenTUI's isOsc52Supported / copyToClipboardOSC52 APIs

Test Coverage

  • Comprehensive unit tests (176 lines, 11 test cases)
  • Tests cover: OSC 52 support/non-support, fallback behavior, Apple Terminal special case, lazy strategy resolution, edge cases
  • Good use of Bun's test mocking APIs (spyOn, mock)

Design Decisions

  • Smart handling of Apple Terminal.app's OSC 52 inconsistencies by preferring pbcopy
  • Graceful fallback chain: OSC 52 → native command
  • Proper caching of isOsc52Supported() result to avoid repeated checks

🔧 Minor Suggestions

1. Extra blank lines in chat.tsx
There are a couple of unnecessary blank lines added:

  • Line 2309 (after toggleVerbose)
  • Line 6576 (before the skip comment)

Consider removing these to keep formatting consistent.

2. Test assertion style
The tests use mock.calls.length for call count assertions:

expect((renderer.isOsc52Supported as ReturnType<typeof mock>).mock.calls.length).toBe(1);

Consider using toHaveBeenCalledTimes() for cleaner assertions:

expect(renderer.isOsc52Supported).toHaveBeenCalledTimes(1);

3. Consider testing the pbcopy-fails-then-OSC52 fallback path
In the preferPbcopy branch (lines 73-77 in clipboard.ts), there's a fallback to OSC 52 when pbcopy fails. Adding a test for this specific path would improve coverage:

test("falls back to OSC 52 when pbcopy fails on Apple Terminal", () => {
  process.env.TERM_PROGRAM = "Apple_Terminal";
  setPlatform("darwin");
  // ... mock pbcopy to fail, OSC 52 to succeed
});

🔒 Security

No concerns - the implementation only writes to clipboard (no reads), and Bun.spawnSync uses a hardcoded command (pbcopy) with no shell injection vectors.


⚡ Performance

Good implementation:

  • Lazy strategy resolution (deferred until first copy() call)
  • Support check is cached for the adapter lifetime
  • No unnecessary allocations in the hot path

📝 Notes

  • The adapter currently only supports pbcopy as a native fallback (macOS-only). If Linux/Windows native clipboard support is desired in the future, xclip/xsel (Linux) or clip.exe (Windows) could be added. The current approach is reasonable for an MVP.

  • The useRef + null check pattern for initializing the clipboard adapter in chat.tsx works correctly, though useMemo could also be considered for this pattern. The current approach is valid since createClipboardAdapter is synchronous.


Verdict

LGTM 👍 - This PR improves clipboard reliability across terminals with clean, well-tested code. The minor suggestions above are non-blocking.

@claude

claude Bot commented Mar 3, 2026

Copy link
Copy Markdown

PR Review: Clipboard Adapter Implementation

Thanks for this PR! The platform-aware clipboard adapter is a solid improvement for cross-terminal compatibility. Here is my review:


Strengths

  1. Well-structured code - Clean Strategy pattern implementation with clear separation of concerns
  2. Comprehensive platform support - Covers darwin, linux (X11 + Wayland), and Windows
  3. OSC 52 + native fallback - Smart dual-write approach for maximum compatibility
  4. tmux/screen passthrough - Good handling of terminal multiplexers (line 64-65)
  5. Good test foundation - Tests cover the core happy paths

Security Concern

clipboard.ts:78-80 - AppleScript string escaping may be incomplete

The escaping only handles backslashes and double quotes, but AppleScript has additional special characters that could cause issues (e.g., \r, \n, tabs).

Recommendation: Consider making pbcopy the primary method for darwin instead of osascript. pbcopy safely handles arbitrary binary data via stdin and does not require escaping.


Potential Issues

1. PR description vs implementation mismatch

The PR summary states: "Special handling for Apple Terminal.app (prefers pbcopy over OSC 52 due to observed inconsistencies)"

But the code at lines 77-82 actually prefers osascript over pbcopy (line 112-114 is the fallback). Could you clarify the intended behavior?

2. chat.tsx:6214-6223 - Changed paste behavior

When bracketed paste contains empty content, this now reads from the clipboard. This changes existing behavior - if a user intentionally pastes empty content, the app now inserts existing clipboard contents instead. Is this intentional?

3. Synchronous clipboard reads could block UI

readText() uses Bun.spawnSync which blocks the event loop. For large clipboard contents or slow clipboard managers, this could cause UI jank. Consider adding a timeout to spawnSync or making this async in the future.

4. clipboard.ts:167-170 - Redundant code

The if (text === undefined) return undefined; check is redundant. Can be simplified to just return nativeReadMethod ? nativeReadMethod() : undefined;


Test Coverage Gaps

The tests are a good start but could be expanded:

  • Windows platform clipboard operations
  • Linux xclip/xsel fallback paths
  • Error handling when commands throw
  • Special characters (newlines, unicode, null bytes)
  • Ctrl+V keyboard handler integration

Minor Suggestions

  1. Extra blank lines - There are unnecessary blank lines added at chat.tsx:2307 and chat.tsx:6601
  2. Consider adding isAvailable() method - Could be useful for UI to know if clipboard operations will work before attempting them
  3. Document the copy() return semantics - Currently returns true if either OSC52 OR native succeeds. Worth documenting this explicitly.

Summary

Aspect Rating
Code Quality Good
Security Needs attention (osascript escaping)
Test Coverage Adequate, could be expanded
Documentation Good

Overall this is a solid improvement. The main item to address is the osascript string escaping security concern - I would recommend using pbcopy as the primary darwin method instead.

@claude claude Bot changed the title fix(ui): improve clipboard copy reliability across terminals fix(ui): improve clipboard reliability across terminals Mar 3, 2026
@lavaman131
lavaman131 merged commit a924f1d into main Mar 3, 2026
7 checks passed
@lavaman131
lavaman131 deleted the flora131/bug/copy-paste branch March 3, 2026 09:58
lavaman131 pushed a commit that referenced this pull request Mar 26, 2026
* fix(ui): improve clipboard copy reliability across terminals

Add a clipboard adapter that uses OSC 52 with native command fallbacks and Apple Terminal preference for more consistent copy behavior. Route selection copy paths through the adapter and handle Cmd+C like Ctrl+C when text is selected.

Assistant-model: openai/gpt-5.3-codex

* refactor(ui): simplify clipboard adapter around OpenTUI APIs

Rely on renderer OSC 52 capability checks for the primary copy path and keep a narrow pbcopy fallback for macOS. This removes custom multi-platform strategy resolution while preserving Apple Terminal reliability via native copy first.

Assistant-model: openai/gpt-5.3-codex

* fix CI errors

* fix(ui): improve paste fallback with native clipboard reads

Assistant-model: openai/gpt-5.3-codex
lavaman131 pushed a commit that referenced this pull request Mar 27, 2026
* fix(ui): improve clipboard copy reliability across terminals

Add a clipboard adapter that uses OSC 52 with native command fallbacks and Apple Terminal preference for more consistent copy behavior. Route selection copy paths through the adapter and handle Cmd+C like Ctrl+C when text is selected.

Assistant-model: openai/gpt-5.3-codex

* refactor(ui): simplify clipboard adapter around OpenTUI APIs

Rely on renderer OSC 52 capability checks for the primary copy path and keep a narrow pbcopy fallback for macOS. This removes custom multi-platform strategy resolution while preserving Apple Terminal reliability via native copy first.

Assistant-model: openai/gpt-5.3-codex

* fix CI errors

* fix(ui): improve paste fallback with native clipboard reads

Assistant-model: openai/gpt-5.3-codex
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.

3 participants