Skip to content

Hold unfinished inline markup while a message streams - #13

Merged
arpan404 merged 1 commit into
mainfrom
ace/stream-hold-unfinished-markup
Nov 1, 2026
Merged

arpan404 merged 1 commit into
mainfrom
ace/stream-hold-unfinished-markup

Conversation

@arpan404

@arpan404 arpan404 commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Problem

An open message (finalized: false) rendered as it stood shows markup that isn't finished yet as literal text, then takes it back once the markup closes:

  • `rm -R shows a backtick, then becomes a code span;
  • [the notes](https://exa shows brackets and a linked URL, then collapses to the notes;
  • **bold, $x^2, <https://… show their markers;
  • a table header shows as a | a | b | paragraph until its delimiter row lands;
  • a lone 1, -- or `` shows as text before it becomes a list item, a rule or a fence;
  • a closing fence's backticks show as code for a moment.

In ace this reads as the transcript flickering while an answer streams.

Change

web/stream-hold.js (pure, no DOM) finds where unfinished markup starts on the open line. MarkitRenderer.render shows the text before it while finalized is false, and the rest once the markup closes, the line ends or the message finishes.

  • Shown text never shrinks, so every update stays an append to the worker stream.
  • Each update scans only the lines completed since the last update and the open line.
  • Finalized renders are unchanged.
  • No Rust or wasm changes.

Evidence

The "never take back shown text" property, measured over 246 real agent answers streamed through the inline engine:

chunk size before after
3 characters 1,848 updates took text back (164 answers) 8 (1 answer)
17 characters 1,182 (153 answers) 1 (1 answer)

The remaining cases are footnotes. A definition's ↩ backlink sits after its growing text, and a [^1] reference can't resolve until its definition arrives at the end.

Tests

  • web/tests/node/stream-hold.test.mjs streams an answer one character at a time and in uneven chunks, and fails if any update takes back text. It also checks that held text appears when its line ends, that finished messages show everything, and that code in an open fence streams as it arrives. It fails on main.
  • node --test tests/node/*.test.mjs passes.
  • Playwright (shared, renderer, inline, code, github, react specs): one clean run of 40/40. Other runs on a heavily loaded machine had one to four flaky failures, a different set each time; the same specs passed on rerun.

An open message rendered as it stood showed markup that was not finished
yet as literal text: a backtick waiting for its pair, `[text](url` before
the `)`, `**` before its closer, a table header before its delimiter row.
A moment later the markup closed and those characters were taken back, so
the line flickered as it streamed.

`StreamHold` finds where unfinished markup starts on the open line and the
renderer shows the text before it, then the rest once the markup closes,
the line ends or the message is finalized. Shown text never shrinks. Each
update scans only the lines completed since the last one and the open line.

Across 246 real agent answers streamed three characters at a time, text
taken back on screen falls from 1,848 updates (164 answers) to 8, all in
one answer that defines footnotes.

Co-Authored-By: ace <bot@acehq.dev>
@arpan404
arpan404 merged commit 16c3169 into main Nov 1, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant