Skip to content

tui: a pasted block loses all its line breaks when the terminal sends CR newlines #1104

Description

@edwin-zvs

Terminals routinely deliver the line breaks inside a bracketed paste as \r, not \n — that is what a tty expects for Enter, and it is what tmux's own paste-buffer sends unless given -r. The Playbook's paste path inserts the payload verbatim (crates/cli/src/app.rs ~line 6786 @ ad47152); nothing in the client, the protocol, or the daemon normalizes \r / \r\n to \n.

The result is a pasted block that renders as one long line, because the TUI's renderer paints \r as nothing.

Repro

Paste this into a Playbook from a terminal that sends CR for newlines (e.g. tmux paste-buffer -p, no -r):

## Plan

- alpha
  - beta
    - gamma

```sh
	echo tab-indented
  • task one
  • task two

Rendered:

Plan- alpha - beta - gammashecho tab-indented- [ ] task one- [x] task two


Saved document (after `C-x C-s`):

'## Plan\r\r- alpha\r - beta\r - gamma\r\rsh\r\techo tab-indented\r\r\r- [ ] task one\r- [x] task two\r'


The same paste with real `\n` (`paste-buffer -p -r`) renders perfectly — nested list, fence, and checklists all intact — so the payload is the only difference.

## Why it matters beyond rendering

Block splitting is newline-based, so a CR document is **one block**:

stored: '# Title\r\r- one\r- two\r'
block b0:27 '# Title\r\r- one\r- two\r'


That single block is the whole shimmer/selection-Run granularity for the document, and the agent receives the CR text verbatim on Run.

The web UI is unaffected: its paste goes through `document.execCommand("insertText", …)`, which normalizes CRLF to LF. Pasting `- one\r\n- two\r\n  - nested\r\n` there produces the correct `- one\n- two\n  - nested\n`.

## Expected

Normalize `\r\n` and lone `\r` to `\n` when a paste enters the Playbook (and ideally defensively on the daemon write path, so no client can store lone CRs in a Markdown document).

## Acceptance criteria

- [ ] A multi-line paste whose newlines arrive as CR produces the same document as one whose newlines arrive as LF.
- [ ] Documents stored by the daemon contain no lone `\r`.
- [ ] Regression test over a CR and a CRLF paste payload.

Found during a hands-on UX audit of the Playbook in the TUI and web UI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions