Skip to content

feat: markdown streaming#5799

Open
OEvgeny wants to merge 13 commits intomainfrom
feat/markdown-streaming
Open

feat: markdown streaming#5799
OEvgeny wants to merge 13 commits intomainfrom
feat/markdown-streaming

Conversation

@OEvgeny
Copy link
Copy Markdown
Collaborator

@OEvgeny OEvgeny commented Apr 3, 2026

Fixes #

Changelog Entry

  • 🧪 Added incremental streaming Markdown renderer for livestreaming, in PR #5799, by @OEvgeny

Description

Adds an incremental streaming Markdown renderer that re-parses only the active (trailing) block during livestreaming, instead of re-rendering the entire Markdown document on every chunk. When streaming is not available, the component falls back to the existing full-render path for backward compatibility.

Design

  • createStreamingRenderer is exposed as a static method on renderMarkdown so consumers can opt in to streaming
  • The renderer uses micromark's event-level API to identify top-level block boundaries; committed blocks are frozen in the DOM and only the last (active) block is replaced on each chunk
  • A sentinel Comment node in the wrapper <div> separates committed content from the active zone for efficient DOM reconciliation
  • On finalize(), the entire document is re-parsed to extract link definitions and apply final decorations
  • useStreamingMarkdownWithDefinitions detects append-only updates and sends only the new chunk to the renderer; if the markdown is replaced (not appended), it resets the renderer

Specific Changes

  • Added packages/bundle/src/markdown/createStreamingRenderer.ts incremental streaming renderer using micromark parse/compile events
  • Extracted packages/bundle/src/markdown/private/createDecorate.ts shared link decoration factory (previously inlined in renderMarkdown)
  • Extracted packages/bundle/src/markdown/private/extractDefinitionsFromEvents.ts extracts link definitions from micromark events
  • Simplified packages/bundle/src/markdown/renderMarkdown.ts — delegates decoration to createDecorate, exposes createStreamingRenderer
  • Added packages/component/src/hooks/useStreamingMarkdownWithDefinitions.ts React hook that drives the streaming renderer or falls back to full render
  • Updated packages/component/src/Attachment/Text/private/MarkdownTextContent.tsx uses useStreamingMarkdownWithDefinitions instead of dangerouslySetInnerHTML
  • Updated packages/component/src/hooks/useRenderMarkdownAsHTML.ts extracted RenderMarkdownAsHTMLFn type alias
  • Added __tests__/html2/livestream/html-chunk.html end-to-end test for streaming chunks with image snapshots
  • Added __tests__/html2/livestream/html-chunk.no-streaming.html variant test with streaming support disabled

  • I have added tests and executed them locally
  • I have updated CHANGELOG.md
  • I have updated documentation

Review Checklist

This section is for contributors to review your work.

  • Accessibility reviewed (tab order, content readability, alt text, color contrast)
  • Browser and platform compatibilities reviewed
  • CSS styles reviewed (minimal rules, no z-index)
  • Documents reviewed (docs, samples, live demo)
  • Internationalization reviewed (strings, unit formatting)
  • package.json and package-lock.json reviewed
  • Security reviewed (no data URIs, check for nonce leak)
  • Tests reviewed (coverage, legitimacy)

@OEvgeny OEvgeny marked this pull request as ready for review April 3, 2026 22:50
Copilot AI review requested due to automatic review settings April 3, 2026 22:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an incremental streaming Markdown renderer for livestreaming scenarios, reducing re-render cost by updating only the active trailing block while preserving backward compatibility via a full-render fallback.

Changes:

  • Introduces a createStreamingRenderer implementation in the bundle and exposes it off renderMarkdown.
  • Adds a React hook to drive streaming rendering (with fallback) and wires it into MarkdownTextContent.
  • Adds HTML2 scenarios/tests and supporting stream adapter utilities for livestreaming validation.

Reviewed changes

Copilot reviewed 18 out of 31 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/component/src/hooks/useStreamingMarkdownWithDefinitions.ts New hook that performs incremental streaming markdown rendering and returns extracted link definitions (with fallback path).
packages/component/src/hooks/useRenderMarkdownAsHTML.ts Refactors the return type alias and clarifies that streaming is handled elsewhere for message activity mode.
packages/component/src/Attachment/Text/private/MarkdownTextContent.tsx Switches markdown rendering from dangerouslySetInnerHTML to the streaming hook and updates definition typing.
packages/bundle/src/markdown/renderMarkdown.ts Delegates link decoration creation, and exposes createStreamingRenderer as a property on renderMarkdown.
packages/bundle/src/markdown/private/extractDefinitionsFromEvents.ts Adds helper to extract link definitions from micromark event stream.
packages/bundle/src/markdown/private/createDecorate.ts Extracts shared link decoration logic used by both full and streaming rendering paths.
packages/bundle/src/markdown/createStreamingRenderer.ts Implements incremental DOM-updating renderer using micromark parse/compile events and a sentinel node strategy.
tests/html2/markdown/vnext/streamingRenderer.html Adds a vNext HTML2 test page to validate renderMarkdown.createStreamingRenderer.
tests/html2/markdown/vnext/markdownStreaming.html Adds a vNext HTML2 test page to validate streaming rendering in Web Chat.
tests/html2/livestream/html-chunk.html Adds an end-to-end livestream chunking scenario to validate UI updates across interim/final activities.
tests/html2/livestream/html-chunk.no-streaming.html Adds a variant test page to validate behavior when streaming support is disabled.
tests/assets/esm/adapter/findAllCopilotStudioThoughtEntity.js Adds helper to locate “thought” entities used by streaming test adapters.
tests/assets/esm/adapter/demuxChainOfThought.js Adds a transform to demux chain-of-thought activities into multiple activities for tests.
tests/assets/esm/adapter/createStreamCoalescer.js Adds a coalescer stream to batch interim activities per session for tests.
tests/assets/esm/adapter/LivestreamSessionManager.js Adds session manager for sequencing/concluding livestream sessions in tests.
tests/assets/esm/adapter/LivestreamSession.js Adds session sequencing state for the test livestream manager.
tests/assets/custom-element/event-stream-adapter.ce.html Adds a custom element that feeds streamed activities into Web Chat and/or event consumers for tests.
CHANGELOG.md Adds changelog entry documenting incremental streaming Markdown renderer.
Comments suppressed due to low confidence (2)

packages/component/src/hooks/useStreamingMarkdownWithDefinitions.ts:1

  • In the non-streaming fallback path, definitions state is always initialized to EMPTY_DEFINITIONS and never becomes null/undefined, so definitions ?? fallbackDefinitions will always return EMPTY_DEFINITIONS and never expose fallbackDefinitions. This breaks link-definition extraction when streaming support is unavailable. Consider returning fallbackDefinitions when streamingRenderer is not present (or when hasStreamingSupport is false), and definitions only for the streaming path.
    packages/component/src/hooks/useStreamingMarkdownWithDefinitions.ts:1
  • When markdown is unchanged and isAppendOnly is true, chunk becomes an empty string and the code still calls streamingRenderer.next(chunk, options) in the non-finalize branch. This can trigger unnecessary parsing/DOM work for no-op updates. Consider early-returning when !finalize && !chunk.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

OEvgeny and others added 3 commits April 3, 2026 23:02
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@OEvgeny
Copy link
Copy Markdown
Collaborator Author

OEvgeny commented Apr 3, 2026

The stream-12k.txt file broke precommit stats pipeline 🤣

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.

3 participants