Guide

Streaming

Render replies as they arrive, and put your own views between messages.

SDK on GitHub ↗

Appending text

Create a MarkdownStream, append text as it arrives, and take a snapshot whenever you want to render. Call finish() when the reply is complete. You can't append after that.

swift
var stream = MarkdownStream()
stream.append("# Hi\n\nStill typing…")
let partial = stream.snapshot()

stream.append(" Done.\n")
stream.finish()
let final = stream.snapshot()

For a chat transcript, MarkdownRenderSession wraps the stream, parses and prepares on a worker, and publishes useful prepared prefixes while input continues. It runs one preparation at a time and combines queued input into the next job. Appending more text does not discard a useful completed prefix. See Getting started.

What readers see

Completed regions retain their block identities and prepared content as more text arrives. The unfinished tail can change layout as words and syntax arrive. Streaming handles half-finished Markdown:

  • An open code block renders as code and is highlighted as it grows.
  • Supported equations typeset children inside unfinished boxes, fractions, roots, and matrices.
  • Supported Mermaid statements draw inside open containers, including subgraphs, sequence frames, and class bodies.
  • An unfinished list, table, or HTML block isn't split apart early.

These drafts come from the math and diagram semantic parsers. The renderer does not add closing syntax to authored source. If a trailing token has no new drawable unit, the existing native artifact can remain visible while pending source text advances beside it. Copy Source retains the authored input. Closing or finishing a block selects strict parsing; malformed or unsupported finished input keeps its source fallback.

Native diagram pages preserve their viewport as content grows. Selection follows labels when they can be mapped unambiguously to the new page. Replacing the logical document clears its diagram camera and selection.

Display and copy the same prefix

session.presentation groups a prepared snapshot, semantic snapshot, diff, and copy provider from one published revision. Keep a display configuration in your host model. For each presentation, update it with configuration = configuration.bindingSourceAccess(to: presentation) and render presentation.preparedSnapshot with that configuration. Retain the returned configuration between publications so completed view boundaries and caches remain reusable.

The presentation's copy provider retains the exact source prefix it represents, even after another append or reset. The session's own configuration preserves the existing accepted-source copy behavior and may therefore include queued source. Use the presentation's provider when copy must match what the reader sees.

Use presentation.documentEpoch as the document view's .id when your host switches between documents. It remains stable through appends and finish. Reset, replacement, and cancellation revoke obsolete publication work; cancel() suspends rendering, and a later append resumes the accepted source.

Custom math and diagram renderers keep their existing behavior. Adopt MarkdownStreamingMathRenderer or MarkdownProgressiveMermaidRenderer to return typed progressive outcomes. Preparation and bitmap decoding happen before view evaluation; resizing reuses prepared artifacts.

Native content around the viewport

On macOS, read-only StreamingMarkdownView mounts native content around the host's scroll viewport while retaining the full source, prepared snapshot and stable offscreen destinations. It does not add a vertical scroller. Find and fragment navigation materialize their source targets. Known offscreen heights remain scroll-space estimates across prepared revisions until current native geometry replaces them. Stream completion does not discard that scroll-space history.

Host-boundary views stay mounted. Rendered editing keeps complete native input geometry. SDK code-collapse, diagram and selection state follow source identity. For custom styles with additional state that must remain mounted, use .offscreenContent(.retained). The automatic lifecycle on other platforms is unchanged.

Inserting your own views

To put your own view between messages, such as a tool result or an attachment, call appendHostBoundary(id:) before it. snapshot().items lists Markdown blocks and your boundaries in order. MarkdownDocumentView and StreamingMarkdownView take a closure that renders each boundary. By default it renders nothing.

Resizing

Changing the width reuses prepared text measurements without parsing the document again. MarkdownDiagnosticsCounters tracks width-change relayouts separately from parsing and preparation.

Streaming design document

Native captureOpen original ↗
Native document capture