-
fix(tui): coalesce thinking deltas on `\n` (v0.7.1)
released this
2026-05-24 20:39:55 -07:00 | 250 commits to main since this releaseOperator: "thinking tokens seem to be split by token — each on a
newline, is that correct? We don't want that."Root cause: v0.6.5 wrote each Thinking SSE delta as its own
thinking_log.write(event.content)call. Worldtree emits Thinking
events at token granularity (per-token or per-few-tokens), so EACH
token became its own RichLog line — visually choppy, one short
fragment per visual row. Wrong UX.Fix: coalesce-on-newline
Thinking deltas accumulate in
TuiPresenterState.thinking_chunk_buffer
(new str field). On each Thinking event:- Append delta content to buffer.
- Flush every COMPLETE line (chars before each
\n) as one
thinking_log.write(line) call. - Leave the post-final-
\ntail in the buffer for the next delta.
On any non-thinking event (run close):
- Flush remaining buffer tail (if any) as one final line.
- Write Rule(end).
Empty lines (blank paragraph separators in the model's
\n\nflow)
are skipped — they'd render as no-content RichLog entries which
just add vertical noise. Natural paragraph breaks become single
visible lines; multi-paragraph thinking renders top-to-bottom.Verified live (tier-3 smoke against personal Worldtree)
Defined a
thinky-smokeagent viapython -m ratatoskr.tier3 define,
asked "What is 12 times 13?". Thinking pane rendered with natural
paragraph chunks:── turn N · thinking #1 start ──
Thinking Process:- Analyze the Request: The user wants to know the result of
12 \times 13. - Calculate:
- Method 1: Standard multiplication.
12 \times 10 = 12012 \times 3 = 36120 + 36 = 156 - Method 2:
(10 + 2)(10 + 3) = 100 + 30 + 20 + 6 = 156.
── turn N · thinking #1 end ──
- Method 1: Standard multiplication.
Each line = one natural paragraph or list item. No per-token fragments.
Edge cases noted
- Long-running thinking with NO
\nat all stays buffered until run
close → operator sees nothing until close. Possible follow-up: add
a length-threshold flush (e.g., > 500 chars → flush at the last
space). For now this is acceptable; thinking content typically has
\nbreaks every few sentences. - Empty deltas (
"") are ignored implicitly — no buffer growth, no
flush. \nat the very start of a delta flushes whatever was buffered
before, then leaves the empty post-\ntail (empty string) in the
buffer, which doesn't show up as an empty line because of the
if line:guard.
Contract amendment
docs/contracts/issues/13.contract.md INV-022 amended for v0.7.1
coalesce semantics. Drift-check clean.Tests
265/265 GREEN; ruff clean. Two updated tests:
test_thinking_streams_into_thinking_log→ renamed
test_thinking_coalesces_until_newline: 3 token-shaped deltas
with no\n→ only Rule(start) writes, buffer holds accumulated.- NEW
test_thinking_flushes_on_newline: delta carrying\n→
Rule(start) + accumulated line + clear buffer. test_thinking_closes_to_thinking_log: 2 deltas "a", "b" +
close → Rule(start) + tail-flush "ab" + Rule(end) = 3 writes
(was 4 with per-delta).
Patch bump (v0.7.0 → v0.7.1) — internal presenter routing change;
no public-API or layout change.Downloads