Type reasoning into the window at the typewriter's pace
Todo2026-11-03jp
Reasoning in the window (style.reasoning.window) appears a whole row at a time, where inline reasoning types out a character at a time, and the rows popping in read worse than the typewriter.
Both paths hold back the line being written until it wraps (handle_paragraph_chunk in crates/jp_cli/src/render/chat.rs). Inline, the finished line goes to the printer as a typewriter task, written a character at a time style.typewriter.text_delay apart (3ms by default, and code_delay for code). In the window, ChatRenderer::emit pushes it through LineSink::push as one row, and the region draws it whole on its next redraw. RFD 120 chose this in point 7 of its Rendering path ("No typewriter delay").
Proposal
Reveal each window row at the typewriter's pace, so the window types the way inline reasoning does.
LineSinktakes a row together with its per-character delay, and the renderer passes the delayemitalready receives. The tool and MCP startup windows keep pushing rows without one, and draw them whole.- The worker keeps a reveal position per region. Rows before it are drawn whole, the row at it is cut to the columns revealed so far with
truncate_row, which keeps escape sequences whole, and rows after it are not drawn yet. - The position follows elapsed time, so a late tick catches up instead of slowing the reveal. While a row is being revealed,
RegionStack::tick_afterasks for a frame every 16ms or so rather than the status row'sinterval;MIN_INTERVALallows down to 10ms. - Releasing the window drops the rows not revealed yet, so the answer does not wait for the preview. Inline, it queues behind the typing.
- No new configuration:
style.typewriterpaces the window as it paces the transcript, andtext_delay = "0s"draws rows whole again.
The change updates point 7 of RFD 120's Rendering path to match.
At the rate Anthropic streamed Opus's reasoning in RFD 120's manual check (about 80 characters a second), a row completes about once a second and then types out in a quarter of a second at the default delay, which is the rhythm inline reasoning has.
Open questions
- How far the reveal may fall behind. Reasoning that streams faster than the reveal, 333 characters a second at the default delay, leaves it further behind for as long as it streams.
style.typewriter.max_latencybounds the lag inline when it is set, and could bound the window the same way. Its default of0ssets no bound; capping the backlog at the window's height, and revealing anything older at once, keeps the window on current reasoning. - Repaint cost. The window repaints once per finished row, and RFD 120's manual check saw no flicker at that rate. A reveal repaints every frame. Repainting only the row being revealed and the status row keeps frames small. Synchronized output (
CSI ? 2026 handCSI ? 2026 l) would make each frame atomic, and Ghostty supports it, but Ghostty has open reports of it corrupting the screen around incremental cursor-positioned updates, so it needs testing there before it is relied on.
Alternative
Show the line being written as it arrives: the renderer pushes the held-back line as an open row that the next chunk replaces, committed when it wraps. The window then shows reasoning a line sooner, but at the provider's pace rather than the typewriter's: in RFD 120's manual check, Anthropic sent reasoning about ten characters at a time, eight times a second. The open row also shows what holding the line back hides, such as its last word jumping to the next row once it stops fitting. It composes with the proposal, which would reveal the open row as it grows.
References
- Ghostty on synchronized output: https://ghostty.org/docs/help/synchronized-output
- The open report: https://github.com/ghostty-org/ghostty/discussions/12062