Skip to content

Typing echo is delayed ~150 ms in synchronized-output TUIs through ConPTY #1753

Description

@cmoron

Environment

  • Rio for Windows using ConPTY
  • WSL shell running inside Rio
  • Codex CLI 0.145.0 as a real-world reproducer

The issue was not reproduced on macOS, where ConPTY is not involved.

Summary

Typing in TUIs using synchronized output can be displayed roughly 150–200 ms
late when Rio runs through ConPTY.

Keyboard input reaches the application immediately and the application emits
its updated frame quickly, but Rio buffers the frame until its 150 ms
synchronized-update timeout expires.

Plain applications such as cat are not affected.

Reproduction

Run these commands in a WSL shell inside Rio for Windows (Ctrl-C to stop):

# Control: immediate echo without synchronized output.
while IFS= read -rsn1 key; do
  printf '\rWithout 2026: %s' "$key"
done

# Reproduction: one write containing BSU, content and ESU.
while IFS= read -rsn1 key; do
  printf '\e[?2026h\rWith 2026: %s\e[?2026l' "$key"
done

Expected: both loops update immediately.

Actual: the second loop displays each isolated key roughly 150–200 ms late.

Codex is a concrete real-world reproducer. Its TUI depends on Ratatui and
Crossterm, and its draw path explicitly wraps frames in
sync_update.

Investigation

Instrumentation showed:

  • input reached the application in about 1–2 ms;
  • the application emitted its updated frame after about 20 ms;
  • the visible update took about 206 ms;
  • cat remained immediate in the same Rio window.

ConPTY was observed returning the synchronized-update markers together, ahead
of the frame content:

chunk 1: ESC[?2026h ESC[?2026l
chunk 2: frame content

Rio arms a 150 ms timeout when parsing ESC[?2026h. When the matching
ESC[?2026l is parsed inline through the normal CSI dispatch path, the timeout
is not cleared. The following frame content is therefore treated as belonging
to a synchronized update and remains buffered until the timeout expires.

Proposed fix

Clear the synchronized-update timeout when dispatching ?2026l, mirroring the
existing ?2026h behavior.

When replaying a synchronized buffer that contains a new BSU after the current
ESU, explicitly re-arm the timeout for that remaining update.

Regression tests cover:

  • an inline BSU+ESU pair followed by frame content;
  • an ESU followed by a new BSU;
  • the existing split-chunk ESU path.

AI assistance: the diagnosis and fix were developed with Claude Code using
Fable 5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions