Skip to content

feat(apollo-react): add the Wind chat markdown renderer - #1375

Open
BenGSchulz wants to merge 1 commit into
feat/wind-chat-message-listfrom
feat/wind-chat-markdown-renderer
Open

BenGSchulz wants to merge 1 commit into
feat/wind-chat-message-listfrom
feat/wind-chat-markdown-renderer

Conversation

@BenGSchulz

@BenGSchulz BenGSchulz commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Third PR of the Wind chat renderer stack (MST-16564): message bodies render as markdown on apollo-wind's Markdown primitive, replacing R-2's plain-text stand-in as the Wind DEFAULT_MESSAGE_RENDERER.

  • Source: contentParts become citation markup through contentPartsToMarkdown, and citationPlugin and resourceTokenPlugin run as extra remarkPlugins. Their custom nodes map through components.

  • Streaming: same as Material, through the hoisted useStreamedMessageContent:

    • chunks are applied at most every 50ms;
    • incomplete [[resource-token: text is held back until it closes;
    • fakeStream messages are revealed ten characters every 50ms;
    • StopResponse completes them.

    Only the renderer re-renders per chunk. The primitive's caret and aria-busy show only while the message streams or fake-streams. Material shows no caret; Wind keeps it by decision.

  • Citation URLs: only http:, https: and relative URLs are opened (PDFs keep #page=N). javascript:, data:, blob:, vbscript: and anything else are refused. The citation-click pre-hook still runs with the unchanged citation so a host can handle it, and pills and source items are always buttons, never anchors with the raw href. The check lives in the hoisted getCitationUrl, so it covers Material too, which opened any scheme before.

  • Citations: apollo-wind Citation pills (title, plus the page for PDFs) with a hover and focus preview.

    • While a pill is active, the text it supports is highlighted by Material's DOM walker, now shared.
    • Clicking runs the citation-click pre-hook with the same payload as Material, then opens the URL (PDFs at #page=N) unless the hook declines.
  • Sources: under the last reply of a group, an apollo-wind Sources collapsible lists the group's citations once each. It is hidden while streaming or while the host sets shouldWaitForMoreMessages. Items run the same pre-hook. The trigger reads "Sources", as in Material, without a count. Intended difference: Material shows the first two sources and a "Show more" button; Wind lists them all once expanded.

  • Resource tokens: a minimal inline chip with the resource's icon (lucide name, via ServiceIcon) and name.

  • Code blocks: the primitive's CodeBlock with codeWrap on. renderCodeActions adds:

    • Expand, a large dialog with the code;
    • for html blocks, Preview (opt-in, see below), which opens the dialog on a sandbox="allow-scripts" iframe (never allow-same-origin) and can switch between code and preview. Preview is disabled while the message streams.

    The dialog is an apollo-wind Dialog. Focus returns to the button that opened it, since neither button is a DialogTrigger.

  • HTML preview document: the snippet gets a no-referrer meta and a CSP (default-src 'none'; img-src data: https:; font-src data: https:; style-src 'unsafe-inline'; script-src 'unsafe-inline') after its doctype, through the hoisted buildHtmlPreviewDocument. Inline styles, scripts and data/https images run; external scripts, stylesheets and network requests do not. Material's preview gets the no-referrer meta only. The iframe renders only while the block is previewable and not streaming, else the dialog shows the code.

  • Cited-range highlight: not activated while the message streams, and removed before new content renders, because the walker moves React-owned text nodes. The CSS Custom Highlight API is the post-parity replacement.

  • Overlay node: modal dialogs portal to a node outside the chat root that carries the chat's theme class and density, at z-index 1300 (MUI's modal level). It sits in the host's portalContainer when one is given, otherwise in document.body, and is removed on unmount. A modal therefore covers the page, as Material's does, instead of being confined to the panel's stacking context. Without a host container only dialogs use it; menus, tooltips and hover cards keep the in-root boundary from R-1. With a host container every Wind overlay goes there, as before. Material's portalContainer handling is unchanged.

  • HTML preview is gated by disabledFeatures.htmlPreview, which the service disables by default, so it is opt-in as in Material.

  • Math and images: math is MathML via the primitive's KaTeX, with singleDollarTextMath: false. Images stay links (the primitive's default images="none").

  • Not here: fenced ```math blocks are W-9 (MST-16784).

Hoisted into src/chat/headless

Helper What it carries Material change
markdown/incomplete-tokens.ts: INCOMPLETE_TOKEN_PATTERNS, extractRenderableContent Moved verbatim from markdown.tsx imported from here
hooks/use-streamed-message-content.ts: useStreamedMessageContent Material's content state machine, moved verbatim: initial content from contentParts, fake stream, StopResponse, the 50ms chunk queue with token buffering. Returns { content, fakeStreaming } markdown.tsx uses it (178 lines removed)
markdown/citation-highlight.ts: highlightCitedRange The CHAT_CITATION_START tree walker; returns the cleanup citation.tsx uses it
markdown/citations.ts: citationFromMarker, getCitationUrl, getGroupCitations The pre-hook payload from a marker, the URL to open, the group's de-duplicated sources citation.tsx, chat-sources.tsx
hooks/use-citations.ts: useOpenCitation, useMessageSources Pre-hook then window.open; the group's sources, followed through Response, SendChunk and StopResponse citation.tsx, chat-sources.tsx

One small behaviour change in Material: a citation with neither url nor download_url no longer opens undefined in a new tab.

Storybook

Run pnpm storybook:design and open Apollo React / Chat / Playground. Compare renderers shows Wind and Material on the same content:

  • Markdown corpus: the corpus now also has task lists, a table with <br> line breaks and a literal $, and a quote with a nested list. A shorter reply streams afterwards.

  • Citations:

    • URL citations;
    • a reply with PDF citations: page numbers, a range spanning bold text, four sources;
    • a streamed reply with citations.

    Hover a pill to see the preview and the highlighted range (not while the reply streams). Click a pill or a source to run the pre-hook, and turn on preHooks to see it ask.

  • Code and HTML preview: TypeScript, Python, Bash and a plain block with inline code, then a streamed HTML block.

  • Resource tokens: inline tokens, tokens in a list, and tokens split across streamed chunks. Wind gets lucide icon names.

  • New story HTML preview: Compare with disableHtmlPreview off and the code scenario. Preview is in the code block header in Wind and inline in Material. The Wind dialog covers both panels in both themes.

  • Clear chat: a host-side button beside the panels (in a host bar in FullScreen). It cancels everything the story still has scheduled (scenario steps, streams, auto replies) and calls newChat() on every chat in the story, without a remount. It is a button because args cannot trigger an action.

Tests

  • vitest --run src/chat src/material/components/ap-chat: 36 files, 210 tests pass. New tests cover:
    • token buffering;
    • citation helpers: the pre-hook payload, PDF page URLs, de-duplication;
    • the highlighter: paints and restores the DOM;
    • useStreamedMessageContent: content parts, a token split across chunks, the 50ms queue flushed on done, fake stream reveal;
    • useOpenCitation: payload, declining hook, and the URL rule (http, a relative PDF with a page, and a refused javascript: URL that still calls the hook);
    • useMessageSources following streamed parts;
    • the Wind renderer: element mapping, images as links, resource chips with buffering, caret only while streaming, preview gating with sandbox and disabled-while-streaming, highlighting on hover, the sources list and pre-hook;
    • a Wind vs Material citation-click payload parity test;
    • overlay placement: dialog in a themed node on the body, menu still in the root, focus trapped and restored, node removed on unmount, and everything in the host container when one is given;
    • Clear chat stopping a running stream in both renderers without a remount.
  • tsc --noEmit: clean.
  • biome check on touched files: one pre-existing noExplicitAny warning in Material markdown.tsx (the code renderer's props).
  • lingui extract still stops at the known autopilot-chat.error.multiple-files duplicate. A source scan shows none of the ids used here has conflicting messages.
  • New strings, in en.json only: message.html-preview, message.dialog-close, message.footnote-back, message.task-done, message.task-open. Everything else reuses Material's ids with Material's source messages.

Stack

  1. R-1 Wind shell: feat(apollo-react): add the Wind chat renderer shell #1371
  2. R-2 message list: feat(apollo-react): add the Wind chat message list #1372
  3. R-3 markdown renderer: this PR
  4. R-4 tool-call and tree renderers: feat(apollo-react): add the Wind chat tool-call and tree renderers #1376
  5. R-5 composer: feat(apollo-react): add the Wind chat composer #1378
  6. R-6 first run, suggestions and thinking state: feat(apollo-react): add the Wind chat first run, suggestions and thinking state #1396
  7. R-7 history and settings: feat(apollo-react): add the Wind chat history and settings panels #1397
  8. R-8 CSS delivery and docs: feat(apollo-react): document the Wind chat setup and its delivery #1399

Jira: MST-16564

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Apollo Coded App preview deployments are ready.

Project Status Preview Updated (PT)
apollo-design Ready Preview · Logs Oct 15, 2026, 02:02:42 PM
apollo-docs Ready Preview · Logs Oct 15, 2026, 02:02:42 PM
apollo-landing Ready Preview · Logs Oct 15, 2026, 02:02:42 PM
apollo-vertex Ready Preview · Logs Oct 15, 2026, 02:02:42 PM

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Dependency License Review

  • ✅ 1947 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 2 package(s) excluded (see details below)
License distribution
License Packages
MIT 1713
ISC 91
Apache-2.0 56
BSD-3-Clause 28
BSD-2-Clause 23
BlueOak-1.0.0 8
MPL-2.0 4
MIT-0 3
CC0-1.0 3
MIT OR Apache-2.0 2
(MIT OR Apache-2.0) 2
Unlicense 2
LGPL-3.0-or-later 1
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
Unknown 1
Artistic-2.0 1
(WTFPL OR MIT) 1
(BSD-2-Clause OR MIT OR Apache-2.0) 1
CC-BY-3.0 1
0BSD 1
(MIT OR CC0-1.0) 1
MIT AND ISC 1
Excluded packages
Package Version License Reason
@img/sharp-libvips-linux-x64 1.3.4 LGPL-3.0-or-later LGPL pre-built binary, not linked
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json

@BenGSchulz
BenGSchulz added this pull request to stack #1374 October 15, 2026 19:04
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

📊 Coverage + size by package

Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.

Package Coverage New-line coverage Packed (gzip) Unpacked vs main
@uipath/apollo-core — — 42.02 MB 50.17 MB ±0
@uipath/apollo-react 55.6% 93.5% (304/325) 8.01 MB 31.51 MB +20.1 KB
@uipath/apollo-ui-icons — — 2.86 MB 6.97 MB ±0
@uipath/apollo-vertex — — — — —
@uipath/apollo-wind — — 785.2 KB 4.55 MB −8 B
@uipath/ap-chat 85.8% — 40.37 MB 45.58 MB +1.6 KB

"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.

@BenGSchulz
BenGSchulz force-pushed the feat/wind-chat-markdown-renderer branch from 5b84536 to 23d4aff Compare October 15, 2026 19:42
Copilot AI balanced review requested due to automatic review settings October 15, 2026 19:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the size:XXL 1,000+ changed lines. label Oct 15, 2026
@BenGSchulz
BenGSchulz force-pushed the feat/wind-chat-markdown-renderer branch from 23d4aff to 679e00e Compare October 15, 2026 20:53
Copilot AI balanced review requested due to automatic review settings October 15, 2026 20:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@BenGSchulz
BenGSchulz marked this pull request as ready for review October 15, 2026 21:32
Copilot AI balanced review requested due to automatic review settings October 16, 2026 16:10
@BenGSchulz
BenGSchulz force-pushed the feat/wind-chat-markdown-renderer branch from 679e00e to ef4e143 Compare October 16, 2026 16:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions
github-actions Bot temporarily deployed to preview/apollo-vertex October 16, 2026 16:11 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-landing October 16, 2026 16:11 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-design October 16, 2026 16:11 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-docs October 16, 2026 16:11 Destroyed
Render message bodies in the Wind chat with the apollo-wind Markdown
primitive: citations as Citation pills with hover preview, cited-range
highlighting and the citation-click pre-hook, resource tokens as inline
chips, a Sources list under replies, and code blocks with expand and an
opt-in sandboxed HTML preview.

Hoist the renderer-agnostic markdown logic into src/chat/headless and
switch Material to it: streamed content with chunk scheduling,
incomplete-token buffering and fake streams, the cited-range
highlighter, citation opening and the group sources.

MST-16564

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 16, 2026 19:52
@BenGSchulz
BenGSchulz force-pushed the feat/wind-chat-markdown-renderer branch from ef4e143 to ef15b14 Compare October 16, 2026 19:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch was successfully deployed

4 active deployments
preview/apollo-vertex — ef15b14e Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-docs — ef15b14e Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-landing — ef15b14e Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-design — ef15b14e Deployed Oct 16, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg:apollo-react size:XXL 1,000+ changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants