Skip to content

markdown: an inline render no longer shrinks for a frame as it changes - #454

Merged
foxnne merged 1 commit into
mainfrom
markdown/inline-no-pin
Oct 16, 2026
Merged

foxnne merged 1 commit into
mainfrom
markdown/inline-no-pin

Conversation

@foxnne

@foxnne foxnne commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

A chat streaming a reply in (fizzyedit/chat) jittered: the conversation dropped by a line
and came back, many times a turn. The cause is in the markdown service's inline render
(drawInline, which a chat bubble uses).

renderTopLevel pins a block's emitted height to its height-table entry. That keeps a
virtualized preview still while blocks come back from spacers. drawInline passes no
viewport, so nothing is virtualized and every block is laid out every frame, but the pin
still applied. A streamed reply changes its text on every chunk, and the chat's type-in
changes it on every frame. Each change re-parses, which clears the table:

  1. Frame A: the edited block has no entry and is laid out at its real height. If its text
    layout is new (a paragraph that just started), the measurement is filed .deferred,
    which keeps the estimate.
  2. Frame B: the block is pinnable (.deferred), so the pin emits the estimate, which is a
    line or so short.
  3. Frame C: real again.

The caller sees H, then H minus a line, then H. With the chat following the bottom, the whole
conversation moved down by that line and back.

The fix gates both pins on virtualize, so a render with no viewport emits what it lays out.
The preview pane, which always has a viewport, is unchanged.

How it was seen: fizzy from main (Debug, macOS) in a sandbox profile, with this markdown dylib
in place of the bundled one. A temporary driver in the chat plugin streamed 8 scripted turns,
in 4 to 40 byte chunks every 20 to 140 ms. A per-frame log recorded each message row's rect,
min size and the scroll position.

over 20 s of streaming before after
frames the conversation moved back down 15 0
content-height changes that were shrinks 22 of 78 0 of 36
frames a message's row was a frame behind its own size 75 35 (only growth, one frame late, as dvui lays out)

Example, one reply's row height across three frames with the same text: 98.1, then 80.0, then
98.1 before; 98.1 throughout after.

Test: the headless integration test's inline-render check now streams a reply in 9 bytes a
frame and fails if the height ever drops. Without the fix it fails ("shrank from 36.4 to
19.6 at byte 18"); with it, zig build test-integration passes 53/53 steps, and so do
zig build test and scripts/check-fmt.sh. check-web, test-sdk-version and the full
app build were left to CI: no SDK or build file changes.

No plan step; a fix found while measuring the chat. The chat's own resize fixes are
fizzyedit/chat#14 and don't depend on this.

🤖 Generated with Claude Code

`renderTopLevel` pinned a block's emitted height to the height table even with no viewport,
where every block is laid out every frame and a pin has nothing to hide. Each edit re-parses,
which leaves the edited block with its estimate (biased low) or the deferred height its first
frame filed, and the next frame's pin emitted that in place of its real height. Pins now apply
only to a virtualized render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne marked this pull request as ready for review October 16, 2026 15:24
@foxnne
foxnne merged commit 0dfdb01 into main Oct 16, 2026
11 checks passed
@foxnne
foxnne deleted the markdown/inline-no-pin branch October 16, 2026 15:40
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