Skip to content

fix: forward Ctrl+V to PTY for non-text clipboard paste#18

Merged
jesse23 merged 8 commits intomainfrom
jesse_paste_fix
Mar 26, 2026
Merged

fix: forward Ctrl+V to PTY for non-text clipboard paste#18
jesse23 merged 8 commits intomainfrom
jesse_paste_fix

Conversation

@jesse23
Copy link
Copy Markdown
Owner

@jesse23 jesse23 commented Mar 26, 2026

Summary

  • Fixes image paste in webtty — ghostty-web swallows Ctrl+V keydown without forwarding \x16 to the PTY (unlike xterm.js). When clipboard has no text/plain, its paste handler also silently drops the event. TUI apps like opencode detect Ctrl+V from the PTY to trigger their native OS clipboard read (osascript / powershell / wl-paste). Fix: capture-phase paste listener sends \x16 to PTY when clipboard has no text — matching the xterm.js behaviour that makes ttyd work.
  • Adds ADR 014 documenting root cause, fix, and ghostty-web upstream contribution path (the real fix is in InputHandler.handleKeyDown).
  • Speeds up test suite from ~21s to ~2.8s — replaces Bun.sleep(800/2500) fixed waits with waitForPrompt/waitForContent polling helpers; uses /bin/sh instead of user's shell to avoid zsh startup cost.

Commits

  • fix: forward Ctrl+V to PTY for non-text clipboard pastesrc/client/index.ts + ADR 014 + spec
  • docs: add upstream contribution section to DECSCUSR ADR — how to fix ghostty-web for cursor style
  • test: replace fixed sleeps with prompt/content polling — test suite speedup

Ghostty-web upstream

The root cause in ghostty-web is InputHandler.handleKeyDown returning early on Ctrl+V without emitting \x16 to onDataCallback. ADR 014 documents the exact one-function fix and contribution checklist for coder/ghostty-web.

Copy link
Copy Markdown

Copilot AI left a comment

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 aims to improve client terminal input handling by forwarding Ctrl+V to the PTY when the clipboard paste contains no text/plain (enabling non-text clipboard flows for TUIs), while also updating docs/ADRs and speeding up tests by replacing fixed sleeps with polling.

Changes:

  • Add a capture-phase paste handler in the browser client to send \x16 to the PTY when the clipboard has no text/plain.
  • Add ADR 014 (image/non-text paste) and ADR 013 (cursor style/DECSCUSR) plus client spec updates.
  • Replace fixed Bun.sleep(...) waits in tests with waitForPrompt / waitForContent / waitForData polling helpers; force /bin/sh to reduce startup time.

Reviewed changes

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

Show a summary per file
File Description
src/client/index.ts Adds paste interception + introduces cursor-style config wiring and DECSCUSR hook call.
src/server/websocket.test.ts Replaces fixed sleeps with prompt/content polling and forces /bin/sh for faster tests.
src/pty/index.test.ts Replaces fixed sleeps with polling helper and forces /bin/sh.
docs/specs/client.md Updates client spec for new config keys and documents new features as done.
docs/adrs/014.client.image-paste.md New ADR documenting the non-text paste issue and workaround/upstream fix.
docs/adrs/013.client.cursor-style.md New ADR documenting DECSCUSR cursor-shape workaround and upstream path.

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

Comment thread src/client/index.ts
Comment thread src/pty/index.test.ts Outdated
Comment thread docs/specs/client.md
Comment thread docs/adrs/013.client.cursor-style.md
Comment thread src/client/index.ts
Copy link
Copy Markdown
Owner Author

@jesse23 jesse23 left a comment

Choose a reason for hiding this comment

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

Thank you for the review. Addressed all 5 comments:

Fixed in ceb204f:

  • cursor.ts missing / applyDecscusr import broken (comments on src/client/index.ts:3 and docs/adrs/013.client.cursor-style.md:20): src/client/cursor.ts was accidentally deleted by git clean -fd during branch history rebuild. Restored from e576532 on main. Build passes cleanly.

  • waitForData(received, '$') fragile on root (comment on src/pty/index.test.ts): Replaced with a sentinel — test now writes echo __ready__ first and waits for __ready__ in output. Deterministic regardless of $ vs # prompt.

Not a regression from this PR:

  • cursorStyle/cursorStyleBlink missing from /api/config (comments on src/client/index.ts:52 and docs/specs/client.md:59): Both keys are already returned by GET /api/config — they were added in e576532 on main (src/server/routes.ts). This PR does not touch the config endpoint. The spec accurately reflects the current server implementation.

jesse23 and others added 7 commits March 26, 2026 16:13
ghostty-web swallows Ctrl+V keydown without sending \x16 to the PTY. When
clipboard contains no text/plain, also silently drops the paste event.
TUI apps (e.g. opencode) read images via native OS clipboard API on Ctrl+V.

Intercept via capture-phase paste listener: when no text/plain in clipboard,
block ghostty-web's handler and send \x16 to PTY directly — matching
xterm.js behaviour that makes ttyd work. See ADR 014.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Bun.sleep(800/2500) in PTY and WebSocket tests caused 7s+ of unnecessary
wait time. Replace with waitForData/waitForPrompt/waitForContent helpers
that poll on actual shell output. Also use /bin/sh instead of $SHELL to
avoid zsh/oh-my-zsh 2s startup cost per test. Suite: 21s -> 2.8s.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…el for prompt detection

git clean -fd during branch rebuild removed src/client/cursor.ts which was
committed on main (e576532). Restore it. Also replace waitForData(received, '$')
prompt detection with a sentinel echo to be robust on root shells where
the prompt is '#' not '$'.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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


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

Comment thread src/client/index.ts
Comment thread docs/adrs/015.ordering-system.pricing-storage.md
@jesse23 jesse23 merged commit f7bcca8 into main Mar 26, 2026
7 checks passed
@jesse23 jesse23 deleted the jesse_paste_fix branch March 26, 2026 20:52
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.

2 participants