Skip to content

Where the Markdown stream stopped

 3 min A Glow streaming experiment accumulated boundary rules and tests, but needed continuation support from the renderer beneath it.
Published

Sep 12, 2025

I wanted to pipe arriving model output into Glow and see formatted text before the producer finished. I submitted a streaming implementation with a configurable buffer around its Markdown renderer.

The idea was to collect enough text to find a safe boundary, render that chunk, and continue. A line of ordinary prose looked manageable. Fences, tables, frontmatter, and whitespace made the word “safe” progressively more complicated.

A chunk was not a document

The new flow package tracked accumulated bytes, whether it was inside a fence, where a fence had closed, and whether frontmatter had been resolved. It offered aggressive, fully buffered, and windowed modes through --flow.

Those modes traded waiting against the amount of context available to the renderer. Calling a complete-document renderer repeatedly on partial documents meant the caller had to predict where document boundaries would not change the output.

One field, pendingBlank, held rendered blank lines until later output clarified whether they belonged between chunks or at the end. That state existed because whitespace decisions were being made by one layer and reconciled by another after rendering.

Fences made the limitation more visible. Buffering an entire code block could protect its structure, but it also meant a long block stopped appearing progressively. Streaming the interior required the renderer to understand that the next chunk continued the same block. A buffer outside the renderer could not supply that understanding merely by choosing a better byte count.

Reference-style links created another kind of future dependency: the definition needed to render an earlier reference might arrive near the end of the document. A finite window could not promise to see every possible definition in an arbitrarily long stream.

The tests had their own boundary

The patch grew well beyond the buffer itself, with extensive tests and documentation around it.

I spent time debugging tests whose input names ended in .txt instead of .md. Glow treated them differently, including automatic fence wrapping. The harness had varied more than the streaming behavior it was meant to compare.

A maintainer then tested a shell producer with delays between Markdown lines. Small flow values still mishandled syntax highlighting and a heading. The failure was visible in the exact use case the feature existed to serve, despite the extensive local test surface.

I also kept a separate development tool for generating Markdown shapes, simulating delivery modes, and inspecting rendered bytes and whitespace. That helped make disagreements concrete. It exposed differences in the rendered bytes, including whitespace that was difficult to distinguish by eye.

What became of it

The maintainers declined the approach. They had lower-level work planned that they expected would make streaming simpler, without a promised timeline. I agreed with the decision and said plainly that the version in the PR was still broken. I had obtained useful line-buffered behavior in other versions, but had not found an approach I was fully satisfied with. bat was adequate for my immediate use in the meantime.

The failure located the missing boundary. The renderer needed to carry information about the structure currently being rendered. The outer stream could then manage delivery and buffering without reconstructing that structure from fragments of input and finished output.

The PR closed without merging.