You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Thanks for re-filing. I reproduced this on 2.0.20, and it still happens on the latest dev (52ba7d6).
Steps:
The simulated model replies with three one-line paragraphs separated by blank lines.
I drag the mouse from the start of the first paragraph to the end of the third. The TUI shows "Copied to clipboard".
I check what was actually copied by decoding the clipboard write (OSC 52) that the TUI sends to the terminal.
Result: Alpha paragraph one.\nBravo paragraph two.\nCharlie paragraph three., so the \n\n between paragraphs became \n. The recording and screenshot below show the selection, and the script below reproduces it and prints the copied text next to the expected text.
The cause appears to be in opentui's multi-block selection: it joins only screen rows that contain text, so the empty row between two paragraph blocks is dropped. This matches your note that it's different from #16521 / opentui#1103, which fixed empty lines inside a single text buffer. Related: #44056 (the same report on v1), #16521, #25623.
recording.mp4
Reproduction script (drive.ts)
import{Effect}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constanswer="Alpha paragraph one.\n\nBravo paragraph two.\n\nCharlie paragraph three."exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tui: {recording: true,pointerOverlay: true,viewport: {cols: 120,rows: 40}},},({ ui, llm, tui })=>Effect.gen(function*(){yield*llm.queue(Llm.text(answer))yield*ui.submit("Write three short paragraphs separated by blank lines")yield*ui.waitFor("Charlie paragraph three.")yield*Effect.sleep(800)constframe=yield*ui.capture()constlines: string[]=(frameasany).lines.map((l: any)=>l.spans.map((s: any)=>s.text).join(""))constya=lines.findIndex((l)=>l.includes("Alpha paragraph one."))constyc=lines.findIndex((l)=>l.includes("Charlie paragraph three."))constxa=lines[ya].indexOf("Alpha")constxc=lines[yc].indexOf("three.")+"three.".lengthyield*Effect.log(`alpha at ${xa},${ya}; charlie end at ${xc},${yc}`)yield*ui.screenshot("before-select")yield*ui.mouse({action: "move",x: xa,y: ya})yield*ui.mouse({action: "down",x: xa,y: ya,button: "left"})for(lety=ya;y<=yc;y++)yield*ui.mouse({action: "move",x: y===yc ? xc : xa+10, y })yield*ui.mouse({action: "up",x: xc,y: yc,button: "left"})yield*Effect.sleep(500)yield*ui.screenshot("after-select")yield*Effect.sleep(1000)// Decode the OSC 52 clipboard write that the TUI sent to the terminal.consttimeline=tui.recording!.timelineconstraw=yield*Effect.promise(()=>Bun.file(timeline).text())constcopied: string[]=[]for(constlineofraw.split("\n")){if(!line.includes('"output"'))continueconstdata=Buffer.from(JSON.parse(line).data,"base64").toString("latin1")for(constmofdata.matchAll(/\x1b\]52;[^;]*;([^\x07\x1b]*)/g))copied.push(Buffer.from(m[1],"base64").toString("utf8"))}constclipboard=copied.at(-1)??"(no OSC 52 write found)"yield*Effect.log(`copied text: ${JSON.stringify(clipboard)}`)yield*Effect.log(`expected: ${JSON.stringify(answer)}`)yield*Effect.log(clipboard===answer ? "OK: blank lines preserved" : "BUG: copied text differs (blank lines dropped)")}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
I'd like to work on this — the mouse-selection copy path collapses blank lines, while messages_copy copies part.text verbatim. I'll fix the selection extraction and cover it with a test.
Looked closer and the blank lines are lost in opentui's getSelectedText(): paragraph gaps are layout margins, not characters, so the selection text never contains them. That needs a fix in opentui (or a source-text mapping for transcript selections), not in the TUI copy handler here. Unclaiming so someone can take it upstream.
Activity
opencode-agent commentedon Oct 14, 2026
Thanks for re-filing. I reproduced this on 2.0.20, and it still happens on the latest
dev(52ba7d6).Steps:
Result:
Alpha paragraph one.\nBravo paragraph two.\nCharlie paragraph three., so the\n\nbetween paragraphs became\n. The recording and screenshot below show the selection, and the script below reproduces it and prints the copied text next to the expected text.The cause appears to be in opentui's multi-block selection: it joins only screen rows that contain text, so the empty row between two paragraph blocks is dropped. This matches your note that it's different from #16521 / opentui#1103, which fixed empty lines inside a single text buffer. Related: #44056 (the same report on v1), #16521, #25623.
recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.Finn763 commentedon Oct 19, 2026
I'd like to work on this — the mouse-selection copy path collapses blank lines, while
messages_copycopiespart.textverbatim. I'll fix the selection extraction and cover it with a test.Finn763 commentedon Oct 19, 2026
Looked closer and the blank lines are lost in opentui's
getSelectedText(): paragraph gaps are layout margins, not characters, so the selection text never contains them. That needs a fix in opentui (or a source-text mapping for transcript selections), not in the TUI copy handler here. Unclaiming so someone can take it upstream.