Skip to content

fix(web): paste terminal text once and clear the shortcut on release - #19366

Merged
juliusmarminge merged 3 commits into
mainfrom
t3/fix-13346
Oct 19, 2026
Merged

juliusmarminge merged 3 commits into
mainfrom
t3/fix-13346

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

A terminal shortcut can send text twice when its clipboard read resolves before Chromium’s native paste event. Keep one gesture’s sent text so the matching native duplicate is consumed, while native-first paste still cancels a late read.

End the gesture on shortcut or modifier release and blur. This also handles macOS omitting V keyup while Command is held, so a later identical menu paste still works. Native fallback after a denied read and paste encoding are preserved.

Fixes #13346. Maintainer takeover of @t3dotgg’s #19366, retaining the original commits and @r4iju’s earlier design contribution.

Verification:

  • Existing surface suite: 77 → 81 passing. Replaced one combined test with five focused parameterized cases covering both arrival orders and cleanup after V release, blur, and Meta release without V release.
  • Clipboard-first test fails with base production code; Meta-release test fails on the original PR and passes after cleanup.
  • Web typecheck, targeted lint, formatting and diff checks passed.
  • Independent Claude Opus 5.5 and Codex GPT-6 Sol whole-PR reviews: one clean round; optional cosmetic suggestions left out.
  • Headless Linux Chromium, actual app terminal and PTY: controlled clipboard-read-first / native-paste event order echoes the text twice on the actual base and once on head. A separately simulated Mac modifier sequence accepts the later identical menu paste. Test fonts are explicitly loaded for both captures.
  • This controls event order with synthetic keyboard/clipboard events; it does not verify native OS clipboard timing. Real macOS, Windows, iOS, Android and Electron desktop were not verified.

Before (actual base, two sends):

Before: one paste appears twice in the terminal

After (one send):

After: one paste appears once in the terminal

Before interaction recording · After interaction recording

Implemented with Codex GPT-6.1 Sol; regression tests with Codex GPT-6 Astra; reviewed with Claude Opus 5.5 and Codex GPT-6 Sol through T3 Code.

The paste shortcut races navigator.clipboard.readText() against the
browser's native paste event. The token only covered the native event
landing first. When Chromium routes the paste through its menu
(installed web apps, Electron), the read resolves first and the native
paste that follows was also sent.

The read now records the text it sent on the shortcut's gesture, and
onPaste drops the one matching native paste. The gesture ends on the
shortcut key's keyup, the next shortcut, blur, or that paste, so a later
Edit > Paste of the same text still lands. Design from r4iju's #13357
and the #13346 triage.

Fixes #13346

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Oct 14, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 14, 2026
@github-actions github-actions Bot added the size:S 10-29 changed lines (additions + deletions). label Oct 14, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at b4e75c1

Macroscope's review found this PR approvable — This is a narrowly scoped terminal paste bug fix that prevents duplicate delivery while preserving fallback and encoding behavior. The production change is localized and covered by focused tests, with no schema, infrastructure, sensitive-area, product-default, or static-analysis configuration impact.

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

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: d507a749-2683-4730-a42c-64ffa68568da
📥 Commits

Reviewing files that changed from the base of the PR and between 4f9cb7a and b4e75c1.

📒 Files selected for processing: 2
  • apps/web/src/terminal/ghostty/surface.test.ts
  • apps/web/src/terminal/ghostty/surface.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

📝 Walkthrough

The terminal surface now tracks text delivered by a clipboard-read paste shortcut and suppresses a matching native paste from the same gesture. Tests cover both event arrival orders and verify that key release or blur clears the gesture.

Changes: Terminal paste deduplication

Layer / File(s) Summary
Track and deduplicate shortcut paste
apps/web/src/terminal/ghostty/surface.ts, apps/web/src/terminal/ghostty/surface.test.ts
The surface records clipboard-read text for the active shortcut, clears the gesture on key release or blur, and drops a matching native paste. Tests cover both event orders and confirm that a later native paste is accepted after gesture cleanup.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: stienswout

Merge Risk: ⚪ Minimal · up to b4e75

The change stops terminal text from being pasted twice when the clipboard read finishes before the native paste event. It ends the paste gesture on key release or blur so a later identical paste still works. No concrete merge-blocking risk was found. Real-browser timing after key release is untested, but it is not shown to cause a problem.

Pre-merge checks: Passed 4
Check name Status Explanation
Linked Issues check Passed Issue #13346 requires deduplication when the asynchronous clipboard read arrives before the native paste. surface.ts records the sent clipboard text and suppresses one matching native paste. The cod…
Out of Scope Changes check Passed The changes are limited to paste gesture handling in apps/web/src/terminal/ghostty/surface.ts and related regression tests in surface.test.ts. These changes directly support issue #13346. No unrel…
Title check Passed The title clearly and concisely describes the main change: preventing duplicate terminal paste delivery and clearing the shortcut state on release.
Description check Passed The description explains the problem, implementation, scope, linked issues, verification results, limitations, and test evidence. It does not use the template headings explicitly or include a direct a…
Full details: Linked Issues check

Explanation

Issue #13346 requires deduplication when the asynchronous clipboard read arrives before the native paste. surface.ts records the sent clipboard text and suppresses one matching native paste. The code preserves native-first fallback and clears the gesture on key release, modifier release, and blur. surface.test.ts covers both arrival orders and later identical pastes after cleanup.

Full details: Out of Scope Changes check

Explanation

The changes are limited to paste gesture handling in apps/web/src/terminal/ghostty/surface.ts and related regression tests in surface.test.ts. These changes directly support issue #13346. No unrelated change is shown.

Full details: Description check

Explanation

The description explains the problem, implementation, scope, linked issues, verification results, limitations, and test evidence. It does not use the template headings explicitly or include a direct approval comment, but it provides the required information in a mostly complete form.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB +7 B (+0.1%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB +7 B (+0.6%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.9 KiB 20.9 KiB 0 B (0.0%) 29.3 KiB ✅
Codex Live turn messages 2 2 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB −7 B (−0.1%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB −7 B (−0.6%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.2 KiB 21.2 KiB 0 B (0.0%) 29.3 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 40d43a1 · PR result: 4f9cb7a · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@juliusmarminge juliusmarminge self-assigned this Oct 19, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Taking over this one. I’ll keep your commits and push the maintainer rebuild on this branch.

@juliusmarminge juliusmarminge changed the title fix(web): terminal paste no longer lands twice in Chromium fix(web): paste terminal text once and clear the shortcut on release Oct 19, 2026
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Oct 19, 2026
@juliusmarminge
juliusmarminge merged commit 9f52dc2 into main Oct 19, 2026
30 of 31 checks passed
@juliusmarminge
juliusmarminge deleted the t3/fix-13346 branch October 19, 2026 05:10
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 19, 2026
## What's Changed
* fix(mobile): sheets close with an X and confirm with a checkmark by @juliusmarminge in pingdotgg/t3code#21319
* fix(tailscale): a slow tailscale status no longer hides Tailscale in Settings by @SunkenInTime in pingdotgg/t3code#21057
* chore(deps): web and mobile share React 19.3 from the catalog by @juliusmarminge in pingdotgg/t3code#21376
* fix(server): migrate legacy thread pull request links by @Yash-Singh1 in pingdotgg/t3code#19129
* fix(server): a failed Codex turn no longer traps the queue behind its subagents by @juliusmarminge in pingdotgg/t3code#21366
* feat(server): count which product features people use by @juliusmarminge in pingdotgg/t3code#15921
* feat(server): count which T3 MCP tools agents use by @juliusmarminge in pingdotgg/t3code#15913
* perf(web): HTML renders drop the loading skeleton by @maria-rcks in pingdotgg/t3code#21480
* fix(mobile): sent steer and queued messages are marked like desktop by @flamboh in pingdotgg/t3code#21417
* fix(server): keep queued subagent status in sync by @Yash-Singh1 in pingdotgg/t3code#21487
* perf(web): virtualize large changed-file trees by @Yash-Singh1 in pingdotgg/t3code#21482
* fix(pull-requests): distinguish skipped checks with a slash icon by @Yash-Singh1 in pingdotgg/t3code#21498
* fix(mobile): crash report shows the store build number by @mohamed-akef in pingdotgg/t3code#16052
* fix(files): load PDFs in browser previews by @Yash-Singh1 in pingdotgg/t3code#21095
* fix(server): device-host SSH tunnels exit when the server dies by @kcazin in pingdotgg/t3code#17448
* fix(web): Open in → Finder in the file preview opened the file in its default app instead of revealing it by @jamesvillarrubia in pingdotgg/t3code#16955
* fix(web): project actions no longer reuse the right-panel terminal by @TonybynMp4 in pingdotgg/t3code#18340
* fix(web): the device screen keeps focus when typing or returning to the window by @haydenkz in pingdotgg/t3code#14534
* fix(server): keep browser input responsive during slow navigation by @josephv123 in pingdotgg/t3code#16691
* fix(grok): a Homebrew-installed Grok updates through brew and is compared against the cask by @Mnigos in pingdotgg/t3code#18772
* fix(antigravity): read usage from each instance data directory by @fixfon in pingdotgg/t3code#17769
* fix(server): reconnecting no longer reopens a closed terminal by @Gigioxx in pingdotgg/t3code#18444
* fix(shared): preview addresses containing another URL open instead of searching by @maxwellyoung in pingdotgg/t3code#17943
* fix(server): ACP Registry loads on networks with slow IPv4 connects by @Gigioxx in pingdotgg/t3code#15054
* fix(preview): keep desktop-rendered tabs out of idle cleanup by @TDanks2000 in pingdotgg/t3code#18147
* fix(server): auto-delivered messages queue between provider turns by @Gigioxx in pingdotgg/t3code#15691
* fix(server): OpenCode 2 threads keep T3's tools after an idle project is evicted by @voltcrash in pingdotgg/t3code#16796
* fix(server): install the advertised version for pnpm-global provider updates by @RaphaelFakhri in pingdotgg/t3code#14295
* fix(web): ignore right-clicks in the flat Device viewport by @samaluk in pingdotgg/t3code#17020
* fix(web): "No project" picker shows its full label on the new-thread screen by @flamboh in pingdotgg/t3code#21504
* fix(web): up-arrow recall skips automation and agent messages by @maria-rcks in pingdotgg/t3code#21510
* fix(desktop): reattach a browser tab whose debugger Chromium dropped by @tobymarks in pingdotgg/t3code#17525
* fix(web): Integrations settings show one Browser section, not two by @RakshithBhat03 in pingdotgg/t3code#18264
* fix(web): hide the empty reset badge on unstarted limit windows by @akash3444 in pingdotgg/t3code#18579
* fix(cli): browser setup installs missing fonts by @adrilasar in pingdotgg/t3code#17570
* fix(web,mobile): queued messages no longer say "From queue" once sent by @flamboh in pingdotgg/t3code#21474
* fix(web): refresh embedded assets in markdown file previews by @Saikrishna1876 in pingdotgg/t3code#18647
* fix(web,mobile): keep reporting client activity after an interrupted report by @skyhong2002 in pingdotgg/t3code#15738
* fix(web): paste terminal text once and clear the shortcut on release by @t3dotgg in pingdotgg/t3code#19366
* fix(server): fall back when checkpoint fsync is unsupported by @mdshzb04 in pingdotgg/t3code#14411
* fix(preview): browser tabs no longer go blank when the tab list reorders by @mkiera in pingdotgg/t3code#13799
* fix(server): terminals no longer inherit the launching tmux session by @UzEE in pingdotgg/t3code#13359

## New Contributors
* @mohamed-akef made their first contribution in pingdotgg/t3code#16052
* @haydenkz made their first contribution in pingdotgg/t3code#14534
* @RaphaelFakhri made their first contribution in pingdotgg/t3code#14295
* @samaluk made their first contribution in pingdotgg/t3code#17020
* @tobymarks made their first contribution in pingdotgg/t3code#17525
* @adrilasar made their first contribution in pingdotgg/t3code#17570
* @skyhong2002 made their first contribution in pingdotgg/t3code#15738
* @mkiera made their first contribution in pingdotgg/t3code#13799

**Full Changelog**: pingdotgg/t3code@v0.1.1-nightly.20261018.3335...v0.1.1-nightly.20261019.3348

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.1-nightly.20261019.3348
github-actions Bot added a commit to davidvanderklay/t3code-flake that referenced this pull request Oct 19, 2026
## What's Changed
* fix(mobile): sheets close with an X and confirm with a checkmark by @juliusmarminge in pingdotgg/t3code#21319
* fix(tailscale): a slow tailscale status no longer hides Tailscale in Settings by @SunkenInTime in pingdotgg/t3code#21057
* chore(deps): web and mobile share React 19.3 from the catalog by @juliusmarminge in pingdotgg/t3code#21376
* fix(server): migrate legacy thread pull request links by @Yash-Singh1 in pingdotgg/t3code#19129
* fix(server): a failed Codex turn no longer traps the queue behind its subagents by @juliusmarminge in pingdotgg/t3code#21366
* feat(server): count which product features people use by @juliusmarminge in pingdotgg/t3code#15921
* feat(server): count which T3 MCP tools agents use by @juliusmarminge in pingdotgg/t3code#15913
* perf(web): HTML renders drop the loading skeleton by @maria-rcks in pingdotgg/t3code#21480
* fix(mobile): sent steer and queued messages are marked like desktop by @flamboh in pingdotgg/t3code#21417
* fix(server): keep queued subagent status in sync by @Yash-Singh1 in pingdotgg/t3code#21487
* perf(web): virtualize large changed-file trees by @Yash-Singh1 in pingdotgg/t3code#21482
* fix(pull-requests): distinguish skipped checks with a slash icon by @Yash-Singh1 in pingdotgg/t3code#21498
* fix(mobile): crash report shows the store build number by @mohamed-akef in pingdotgg/t3code#16052
* fix(files): load PDFs in browser previews by @Yash-Singh1 in pingdotgg/t3code#21095
* fix(server): device-host SSH tunnels exit when the server dies by @kcazin in pingdotgg/t3code#17448
* fix(web): Open in → Finder in the file preview opened the file in its default app instead of revealing it by @jamesvillarrubia in pingdotgg/t3code#16955
* fix(web): project actions no longer reuse the right-panel terminal by @TonybynMp4 in pingdotgg/t3code#18340
* fix(web): the device screen keeps focus when typing or returning to the window by @haydenkz in pingdotgg/t3code#14534
* fix(server): keep browser input responsive during slow navigation by @josephv123 in pingdotgg/t3code#16691
* fix(grok): a Homebrew-installed Grok updates through brew and is compared against the cask by @Mnigos in pingdotgg/t3code#18772
* fix(antigravity): read usage from each instance data directory by @fixfon in pingdotgg/t3code#17769
* fix(server): reconnecting no longer reopens a closed terminal by @Gigioxx in pingdotgg/t3code#18444
* fix(shared): preview addresses containing another URL open instead of searching by @maxwellyoung in pingdotgg/t3code#17943
* fix(server): ACP Registry loads on networks with slow IPv4 connects by @Gigioxx in pingdotgg/t3code#15054
* fix(preview): keep desktop-rendered tabs out of idle cleanup by @TDanks2000 in pingdotgg/t3code#18147
* fix(server): auto-delivered messages queue between provider turns by @Gigioxx in pingdotgg/t3code#15691
* fix(server): OpenCode 2 threads keep T3's tools after an idle project is evicted by @voltcrash in pingdotgg/t3code#16796
* fix(server): install the advertised version for pnpm-global provider updates by @RaphaelFakhri in pingdotgg/t3code#14295
* fix(web): ignore right-clicks in the flat Device viewport by @samaluk in pingdotgg/t3code#17020
* fix(web): "No project" picker shows its full label on the new-thread screen by @flamboh in pingdotgg/t3code#21504
* fix(web): up-arrow recall skips automation and agent messages by @maria-rcks in pingdotgg/t3code#21510
* fix(desktop): reattach a browser tab whose debugger Chromium dropped by @tobymarks in pingdotgg/t3code#17525
* fix(web): Integrations settings show one Browser section, not two by @RakshithBhat03 in pingdotgg/t3code#18264
* fix(web): hide the empty reset badge on unstarted limit windows by @akash3444 in pingdotgg/t3code#18579
* fix(cli): browser setup installs missing fonts by @adrilasar in pingdotgg/t3code#17570
* fix(web,mobile): queued messages no longer say "From queue" once sent by @flamboh in pingdotgg/t3code#21474
* fix(web): refresh embedded assets in markdown file previews by @Saikrishna1876 in pingdotgg/t3code#18647
* fix(web,mobile): keep reporting client activity after an interrupted report by @skyhong2002 in pingdotgg/t3code#15738
* fix(web): paste terminal text once and clear the shortcut on release by @t3dotgg in pingdotgg/t3code#19366
* fix(server): fall back when checkpoint fsync is unsupported by @mdshzb04 in pingdotgg/t3code#14411
* fix(preview): browser tabs no longer go blank when the tab list reorders by @mkiera in pingdotgg/t3code#13799
* fix(server): terminals no longer inherit the launching tmux session by @UzEE in pingdotgg/t3code#13359

## New Contributors
* @mohamed-akef made their first contribution in pingdotgg/t3code#16052
* @haydenkz made their first contribution in pingdotgg/t3code#14534
* @RaphaelFakhri made their first contribution in pingdotgg/t3code#14295
* @samaluk made their first contribution in pingdotgg/t3code#17020
* @tobymarks made their first contribution in pingdotgg/t3code#17525
* @adrilasar made their first contribution in pingdotgg/t3code#17570
* @skyhong2002 made their first contribution in pingdotgg/t3code#15738
* @mkiera made their first contribution in pingdotgg/t3code#13799

**Full Changelog**: pingdotgg/t3code@v0.1.1-nightly.20261018.3335...v0.1.1-nightly.20261019.3348

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.1-nightly.20261019.3348
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Terminal paste lands twice in Chromium browsers (inverse of the #8457 race)

2 participants