Skip to content

fix(terminal): restore copying from Claude Code’s fullscreen TUI - #9949

Open
callmemorgan wants to merge 16 commits into
pingdotgg:mainfrom
callmemorgan:t3code/fix-neo-fullscreen-copy
Open

fix(terminal): restore copying from Claude Code’s fullscreen TUI#9949
callmemorgan wants to merge 16 commits into
pingdotgg:mainfrom
callmemorgan:t3code/fix-neo-fullscreen-copy

Conversation

@callmemorgan

@callmemorgan callmemorgan commented Sep 5, 2026

Copy link
Copy Markdown

I had a number of frustrating situations (old terminal sessions in particular) where copying out of the t3code terminal didn't work with the Claude Code fullscreen tui enabled. Claude Code’s copy-on-select sends OSC 52 clipboard writes, which t3code wasn’t handling. Digging into Claude Code's copy-escape mode (hold shift when selecting text) there were also instances where t3code would hand mouse movement back to the Claude Code instance, even if a drag was still ongoing. This should make the default copy-on-select behavior in Claude Code's fullscreen tui work more reliably, and also improve the reliability of the drag-to-copy-with-shift-pressed fallback option Claude Code exposes.

Text below this line written by an LLM:

What Changed

Fixed copying from fullscreen terminal applications:

  • Preserve desktop/web native selection during redraws and keep Shift-drag active through pointer release. Clear completed selections when typing, pasting, or scrolling sends input to the application.
  • Handle OSC 52 clipboard writes on desktop, web, iOS, and Android using a shared parser. Ignore history, background output, and stale pending requests; keep writes ordered without building a backlog.
  • Offer a Copy or Clear action on plain-HTTP web connections where copying requires a user click.

Why

Claude Code’s fullscreen redraws could erase a selection mid-drag. Releasing Shift before the trackpad button could hand the remaining drag back to Claude. Its copy-on-select OSC 52 path was unsupported.

Both Shift-drag/Cmd+C and Claude’s own copy were verified in T3 desktop with Claude Code 2.1.261 on the contributor’s physical MacBook Neo trackpad. A separate Chrome test over LAN HTTP verified Copy and Clear against the macOS clipboard, including leaving it unchanged before the click.

The latest fixes pass 180 focused web/shared tests and the web/mobile/client-runtime typechecks. Clipboard output reaches each client before the display buffer is trimmed; tests cover 600 KiB copies and verify that no extra RPC subscription is created. Browser testing covers an available async clipboard API that rejects the write, followed by a successful user-initiated copy. Captured startup output from all six installed providers (Codex, Claude, Cursor, Grok, OpenCode, and Antigravity) passed selection/copy/input checks through the terminal renderer. These were startup-screen checks, not full model turns.

Native iOS verification passed on one iPhone 17e simulator running iOS 26.5 on the MacBook Neo: exact Unicode clipboard text, empty-payload clearing, ignored background output, fresh copying after foregrounding, and no history replay after leaving and reopening the terminal. This caught and fixed Android-only AppState focus/blur subscriptions being registered on iOS. Android has not been tested in an emulator.

UI Changes

Terminal selection gestures are demonstrated below. Plain-HTTP connections now show a Copy/Clear prompt when the browser requires a click.

fullscreen-terminal-copy.webm

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Implemented with GPT-6 Astra in T3 Code on Morgan’s behalf, with simplification by Fable 5.1.


Note

Medium Risk
Large changes to terminal pointer handling, clipboard timing, and input routing in a core user-facing surface; regressions could affect selection, copy/paste, or mouse reporting in fullscreen apps.

Overview
Adds OSC 52 clipboard support end-to-end so fullscreen TUIs (e.g. Claude Code copy-on-select) can push text to the system clipboard on web, desktop, iOS, and Android.

A shared @t3tools/client-runtime/terminal-clipboard module parses live PTY output for OSC 52, ignores history/replay and ineligible chunks, and serializes native writes so only the newest pending copy runs. terminalEnvironment.observeAttach taps the same attach stream before buffer retention—no second RPC—feeding writeClipboard / resetClipboard on Ghostty and a mobile useTerminalClipboard session tied to route focus and app state. Web shows a Copy/Clear toast when navigator.clipboard is blocked and falls back to a gesture-based copy.

Ghostty selection and input are reworked: drags bind to their pointer (including Shift fullscreen drags), autoscroll and capture loss are handled cleanly, and committed user input goes through sendUserInput, which clears a finished selection before sending to the PTY while modifier keys, protocol replies, and native copy keep the highlight. Session output sync no longer clears selection on every chunk; keyboard shortcuts in the drawer use the surface input path instead of direct atom writes.

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

Note

Restore terminal clipboard copying from fullscreen TUI via OSC 52 and gesture-based selection

  • Adds a stateful OSC 52 clipboard parser, serialized native write queue, and eligibility-gated session in packages/client-runtime/src/terminalClipboard.ts; only live, focused, visible terminal output triggers a clipboard write, and snapshot/replay history is excluded
  • Refactors GhosttyTerminalSurface in apps/web/src/terminal/ghostty/surface.ts to track selection as an owned pointer drag with centralized teardown, clearing stale selections on input, output eviction, visibility/focus/window-blur changes, and disposal
  • Adds GhosttyTerminalCore.selectionPosition and wraps native point/grid-ref conversions in finally blocks in apps/web/src/terminal/ghostty/core.ts to fix memory leaks on thrown conversions
  • Integrates clipboard forwarding in web TerminalViewport and mobile useTerminalClipboard, with toast/action fallback when browser blocks automatic writes and Expo Clipboard support on mobile
  • Adds observeAttach to the terminal atom factory in packages/client-runtime/src/state/terminal.ts so consumers can tap live attach stream events before retention without a second RPC subscription
  • Risk: GhosttyTerminalSurface constructor now reads the active element to initialize focus; onClipboardWrite and observeClipboard are new surface options that hosts must wire up or clipboard output is silently dropped. pasteFromClipboard, onKeyDown, onPaste, onCompositionEnd, onInput, and onWheel now route through sendUserInput, which clears non-dragging selections before delivery — callers relying on direct onData invocation for these paths are affected

Macroscope summarized 8339d2e.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T19:48:28.735281Z 8339d2e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 5, 2026
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/terminal/ghostty/clipboard.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The change spans shared terminal infrastructure plus web, desktop, and mobile clipboard, pointer, and input behavior, creating a broad production blast radius beyond a self-contained fix. It also adds a line-level lint suppression in the shared clipboard parser, warranting human review.

You can add or adjust custom eligibility rules. Learn more.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8fba6168d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/terminal/ghostty/clipboard.ts Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/terminal/ghostty/surface.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69ae8f4a91

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/terminal/ghostty/clipboard.ts Outdated
Comment thread packages/client-runtime/src/terminalClipboard.ts Outdated
Comment thread apps/mobile/src/features/terminal/terminalClipboard.ts Outdated
@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Sep 5, 2026

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

Stale Bugbot comment from a previous run.

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bf45b6852

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f34fad32e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/terminal/ghostty/surface.ts Outdated

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

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

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f34fad3. Configure here.

Comment thread apps/mobile/src/features/terminal/useTerminalClipboard.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09986610de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
@callmemorgan

Copy link
Copy Markdown
Author

Addressed the current findings from a separate Sonnet review in ec4f980:

  • Retire cached selection coordinates when the core's tracked endpoints move during scrollback eviction.
  • Finish native selection when left is released during a chorded mouse gesture, and clear unmoved selections on pointer cancellation.
  • Clear stale application/link ownership after pointer capture is lost.
  • Preserve selection for the remaining modifier key names and skip full-grid selection clearing when no selection exists.

168 focused web/core tests and the web typecheck pass. The six captured provider startup regressions also pass again; these check selection, redraw, copying, and subsequent input, not full model turns.

I rechecked the review's HTTP fallback claims against the current helper in Chrome and Playwright WebKit. Both copied exact text and cleared the native macOS clipboard from a button click without a DOM selection. System-message parsing and combined q selectors were already addressed. The existing Ctrl+C policy and explicit-click authorization for fallback toasts remain unchanged.

Native iOS verification is now building on the Neo with two build jobs and one simulator. No simulator pass is claimed yet.

Written by GPT-6 Astra in T3 Code on Morgan's behalf.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec4f980f22

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/terminal/ghostty/surface.ts
@callmemorgan

Copy link
Copy Markdown
Author

Pushed 4f20dde and 81e2610. Fable 5.1’s simplification consolidates the web/mobile clipboard lifecycle and moves parser/session tests beside the shared implementation. The follow-up fixes isolate failing stream observers, free selection-coordinate allocations on failure, preserve focus acquired during terminal setup, and complete application mouse release on capture loss.

Native iOS testing found that AppState focus/blur subscriptions are Android-only. Those subscriptions are now conditional. On the Neo’s iPhone 17e simulator running iOS 26.5, the terminal opened without the previous error overlay. Live PTY output through the isolated T3 server copied exact Unicode text and cleared the native iOS clipboard. Background output and history replay after leaving/reopening the terminal did not change it; fresh copying worked after returning.

All 180 focused tests, web/mobile/shared-runtime typechecks, and six provider startup-capture regressions passed. Targeted lint reported no errors. The provider checks cover selection, redraw, copying, and subsequent input, not full model turns. Android was not run in an emulator. The simulator, stream, Metro, and isolated backend are shut down again.

Written by GPT-6 Astra in T3 Code on Morgan’s behalf.

callmemorgan and others added 11 commits September 5, 2026 12:41
Keep native selection through output redraws and Shift release, including the
final pointer position. Track drag ownership separately from the selected range
and stop autoscroll on release, cancellation, capture loss and deactivation.

Retire completed selections when typing, pasting or other committed input resumes,
so a later Ctrl+C can interrupt. Preserve copy gestures, modifiers and terminal
protocol replies. Prevent older copy completions from clearing a newer selection.

Verified with focused tests using the real Ghostty core and a desktop check on the
MacBook Neo.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Observe bounded OSC 52 writes in the shared desktop/web terminal. Keep stream
framing separate from payload retention and copy eligibility, including chunked
escapes, wrapped payloads and control-string exits. Never replay historical copies;
revoke pending copies on focus or visibility loss while retaining parser framing.

Use the Clipboard API without stealing focus or inserting denial errors into a
fullscreen application. Clipboard queries remain unsupported.

Verified 128 focused tests, web typecheck and the macOS clipboard in the isolated
desktop client on the MacBook Neo.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Preserve a leading U+FEFF when decoding clipboard text, including a payload
containing only that character.

Serialize OSC clipboard writes across terminal instances and retain only the
newest pending copy. Recheck its terminal generation and focus before writing,
so a delayed request cannot survive blur, hiding, reset or disposal. Consume
clipboard denial without blocking subsequent copies or writing errors to the TUI.

Verified regression failures against the previous writer, 138 focused tests,
web typecheck and targeted lint (existing React warnings only).

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Retire completed native selections before forwarding wheel mouse reports or
alternate-screen arrow keys. Preserve active drags, local scrollback selection
and fractional wheel movements that do not send input.

Both wheel regressions failed before the fix. Verified 143 focused tests,
web typecheck and targeted lint.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Share the existing OSC parser and bounded clipboard writer through client-runtime.
Observe live terminal subscription chunks on mobile and write through Expo Clipboard.
This supports both native renderers without parsing their replayed initialBuffer.

Only the focused terminal route in the active app may copy. Ignore initial history,
reconnect/reset replays and background output; invalidate incomplete and queued copies
on navigation, app deactivation and terminal changes. Clipboard reads remain disabled.

Verified 171 focused tests, mobile/web/client-runtime typechecks and targeted lint.
Six installed provider startup captures passed Ghostty selection/copy/input smoke tests.
Simulator verification is pending installation of Xcode on the test host.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Forward an empty decoded payload so applications can clear the clipboard.
Keep malformed payloads and clipboard queries distinct from valid empty text.
The shared parser applies the behavior to web, desktop and mobile.

Verified the focused clipboard tests, including exact empty and BOM payloads.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Offer a Copy or Clear action when the browser has no async clipboard API.
The click supplies the user gesture required for a clipboard copy event,
without moving terminal focus or changing the clipboard before confirmation.
Reuse one prompt per terminal and dismiss it when that terminal is disposed.

Verified 138 focused web tests and the web typecheck. A real Chrome client
on the MacBook Neo copied text and cleared the macOS clipboard over LAN HTTP;
its clipboard remained unchanged until each action was clicked.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Accept q when a selector list also includes the system clipboard c.
Secondary-only requests remain local. The three combined-selector tests
failed before the change; all 50 clipboard tests now pass.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Return written, failed, or skipped from the serialized clipboard writer.
Offer the existing Copy/Clear prompt after an eligible failure, including
browsers that expose the API but require a fresh user gesture. Ignore results
from superseded requests and requests that lost terminal focus.

Verified 142 web tests, five mobile clipboard tests, web/client-runtime
typechecks, and targeted lint. A real Chrome client with async rejection
forced copied text and cleared the macOS clipboard through the prompt.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Observe raw events from the existing terminal attachment before the 512 KiB
output window and React batching can discard them. Scope observers to the full
environment and attach request, and remove them when the route loses focus.
The mobile clipboard session still ignores history and background output.

The large single-write regression failed on the previous implementation.
All 29 focused mobile/state tests pass, including a real subscription test
for complete delivery, retained-buffer limits, isolation and unsubscription.
Mobile, web and shared typechecks pass. The web clipboard fallback also passes
through the updated attach path in a real Chrome client on the MacBook Neo.
Native simulator verification remains pending Xcode installation.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Feed OSC 52 from the existing raw terminal attachment on web and desktop.
Keep display writes/resynchronization separate from clipboard input so a
512 KiB retention reset neither loses a large copy nor replays one.
Session resets still invalidate incomplete and queued clipboard requests.

Verified 143 focused web tests and the web typecheck. A real Chrome client
on the MacBook Neo copied 600 KiB to the macOS clipboard and then cleared it.
All six captured provider startup selection/copy/input smoke tests pass again.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
callmemorgan and others added 5 commits September 5, 2026 12:41
Use the stable attachment atom as the focus-effect dependency. Keep the
latest target in a layout-updated ref, so equivalent input objects preserve
an ongoing copy while an actual attachment change starts a new session.

A React re-render probe failed on the previous code and passes with this
change. It covers a split OSC across equivalent input objects and rejection
of a partial copy after changing attachments. Mobile typecheck and lint pass.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Include the provider instance in clipboard observer registration and refresh it when the provider changes. Remove the duplicate toast import introduced by merging main, and verify observer isolation between providers.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Check selection endpoints against the core after output so scrollback eviction cannot reuse stale row coordinates. Finish native drags when the left button releases, clear cancelled single-cell selections, and release stale application and link ownership on capture loss.

Preserve selection for all modifier key names and avoid repainting the whole grid when there is no selection to clear.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Release both WASM allocations if converting a tracked selection point throws.
A fault-injection test checks cleanup through the real runtime.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Consolidate web and mobile clipboard eligibility and replay handling, and move
parser and session tests alongside the shared implementation. Reuse the attach
family key and a single web attach input so clipboard observation stays on the
same stream. Isolate observer failures without dropping terminal output.

Preserve focus acquired during asynchronous setup, keep the first pointer's
ownership, and send a matching application mouse release on capture loss.
Register AppState focus events only on Android; iOS does not support them.

Verified with 180 focused tests, six provider startup captures, scoped typechecks,
and native iOS clipboard text, clear, background, and reattachment checks.

🤖 Generated with [T3 Code](https://t3.codes)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
@callmemorgan
callmemorgan force-pushed the t3code/fix-neo-fullscreen-copy branch from 81e2610 to 8339d2e Compare September 5, 2026 19:42
@callmemorgan

Copy link
Copy Markdown
Author

Rebased onto upstream main at f8b4c46; the new head is 8339d2e. The rebase was conflict-free, and git range-diff confirms all 16 commit patches are unchanged. All 180 focused terminal/clipboard tests passed again on the rebased head. The previously verified terminal code is unchanged, so the simulator stayed shut down.

Written by GPT-6 Astra in T3 Code on Morgan’s behalf.

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

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant