Skip to content

feat(apollo-react): add the Wind chat message list - #1372

Open
BenGSchulz wants to merge 1 commit into
feat/wind-chat-shellfrom
feat/wind-chat-message-list
Open

BenGSchulz wants to merge 1 commit into
feat/wind-chat-shellfrom
feat/wind-chat-message-list

Conversation

@BenGSchulz

@BenGSchulz BenGSchulz commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Second PR of the Wind chat renderer stack (MST-16563): the message list, on apollo-wind's MessageScroller.

  • Rows: one MessageScrollerItem per message group with the group's first message id as messageId. User turns are scrollAnchors. Grouping follows Material: consecutive messages sharing a groupId.
  • Surfaces: the user's message is in a Bubble, honouring Material's --custom-autopilot-chat-user-message-bg-color. The assistant's is plain prose. There are no avatars. Attachments render as Attachment cards with the file name and a type icon, as Material shows them (no type text). A message of files alone shows its attachment row by itself, with no empty bubble and no placeholder text.
  • Bodies go through resolveMessageRenderer:
    • injected component renderers render inline;
    • injected render(container) renderers get a DOM slot, cleaned up before each re-render and on unmount;
    • Wind's default is a minimal streaming text renderer until the markdown renderer (R-3) replaces it.
  • Actions (MessageActions):
    • copy, feedback and the message's own actions (dispatched through the shared pre-hook then publish handler); showInOverflow ones go in the "More" menu;
    • copy keeps the primitive's "Copied" tick: MessageActionCopy writes the group's copy text (built from the rendered group, no per-bar lookup), and the hoisted useChatMessages Copy handler writes the same text for Material parity, so Wind does two identical writes by decision;
    • always shown on the latest reply, on hover or focus elsewhere, hidden while streaming;
    • the bar is in flow below the message (mt-1 -mb-7), so its space is reserved and hover does not shift layout;
    • feedback toggles report aria-pressed; the rating can be switched after it is given (not cleared), each change publishing the same Feedback payload with the new value. A deviation from Material, by decision.
  • Scroll: useChatScrollAdapter, described below, runs in a small leaf component so scroll-state changes do not re-render the transcript; ChatMessage is memoised. The jump button reuses the "Scroll to bottom" string. Older pages are prepended with the scroll position preserved, through the viewport's preserveScrollOnPrepend; the adapter skips the end jump for the conversation replacement that prependOlderMessages publishes.
  • Accessibility: the content has role="log" with aria-live="off", and a LiveRegion announces "New response" once per settled reply, so streamed chunks are not read out.
  • Rows are message groups only: the scroller tells a new turn from a replaced row by counting its rows, so transient rows (a loading row, an empty state) made it scroll back to the first user turn. The thinking indicator now sits inside the last group, and the older-messages dots and empty state are overlays.
  • Spacing parity: groups are messageSpacing apart with Material's trailing margins (14px after a request, 30px after a response, -16px for a lone disableActions message), messages inside a group touch, and messageGroupGap separates a message's own parts; the action bar sits in flow under the message (mt-1 -mb-7). Compact mode is 32px between turns against Material's 21px, since the in-flow bar needs room.
  • Latest-reply detection: Wind derives it from the groups it renders (the last group holding an assistant message) and passes isLatestReply down, so no per-bar lookup. Material's useIsLastAssistantMessage now recomputes from getConversation() on mount and on every Response, so rating an older reply no longer demotes the latest one, and a message without a groupId no longer counts as part of the latest reply.
  • Layout parity: 24px gutters, Material's header height, the config's messageSpacing and messageGroupGap, and the user-message inset (useMessageInset). The viewport is overflow-x: hidden with overscroll-behavior-x: none.
  • Loading:
    • ThinkingIndicator rotates the service's loading messages (setDefaultLoadingMessages, overridden by setLoadingMessage);
    • LoadingDots at the top while older messages load;
    • a Skeleton during the initial load.
  • Auto-scroll state lives in the chat root's chat-scroll-provider.tsx for both renderers: its service subscriptions (Request, NewChat, OpenConversation, SetConversation, ModeChange, ToggleAutoScroll and the load-more flags) moved from the container effect into a mount effect, so a toggle sent before the lazily loaded renderer mounts, or across a layout remount, holds. Wind reads useChatScroll().autoScroll. Under Wind the container effect still returns early, since nothing sets those refs, so it adds no listeners or observers.
  • Older pages: loading and has-more state come from the loading provider (hasMoreMessages added, from ShouldShowLoadingMoreMessages), so a remount does not ask again, and edge changes are ignored until the viewport's first settled layout, so FullScreen does not ask for older messages at mount.

Hoisted into src/chat/headless

Helper What it carries Material change
hooks/use-chat-messages.ts: useChatMessages Transcript state from the Request intercept, Response, NewChat and SetConversation; the Feedback and Copy effects; setHasMessages chat-message.tsx uses it, keeping its own suggestions subscription
messages/message-groups.ts: groupChatMessages, withoutFakeStream, getMessageCopyText Grouping, restoring without replaying fake streams, copy text via useChatMessages
hooks/use-message-actions.ts: useDefaultMessageActions, useIsLastAssistantMessage, useMessageActionHandler Built-in actions per role and rating, latest-reply detection, pre-hook then publish { group, message, action } chat-actions.tsx, chat-actions-list.tsx
hooks/use-injected-message-renderer.ts The DOM-slot render and cleanup effect chat-message-content.tsx WidgetContainer
utils/file-type.ts: getAttachmentFileType The friendly file type utils/file-to-icon.ts
hooks/use-message-inset.ts: useMessageInset The user message's leading inset (width-based, maximum in FullScreen) chat-message-content.tsx MessageBox
hooks/use-chat-scroll-adapter.ts: useChatScrollAdapter New. Request, NewChat and OpenConversation call scrollToEnd (instant while streaming, else smooth). SetConversation (except after prependOlderMessages) and ModeChange call it instantly after render. Arriving at the top, after the layout has settled, publishes ConversationLoadMore once per arrival when paginated, nothing is loading and the host has more. Material-agnostic, so C-4 can adopt it none (C-4 is deferred)
providers/chat-scroll-provider.tsx Auto-scroll state for both renderers, now subscribed from mount service subscriptions moved out of the container effect
providers/loading-provider.tsx hasMoreMessages none (additive)
hooks/use-loading-messages.ts, hooks/use-settled-replies.ts New. Loading-message state; settled-reply detection, seeded from getConversation() on mount and on SetConversation so restored replies are never announced none yet (Material's loading text keeps its fade logic; candidate for R-6)

Storybook

pnpm storybook:design, then Apollo React / Chat / Playground. Compare renderers puts Wind and Material side by side. Try these scenario values:

  • Short conversation and Streaming reply: grouping, streaming text, actions appearing once settled.
  • Message actions: custom actions (lucide icons in Wind), a "Share" action in the "More" menu (showInOverflow), and disabled actions.
  • Long history with pagination: scroll to the top for older pages, with the position kept.
  • Attachments: file cards.
  • Loading message: ThinkingIndicator with the custom message.

Copy and thumbs publish copy and feedback to the Actions panel; the thumb then shows pressed and disabled. The new autoScroll control applies toggleAutoScroll, at mount and on change; a new turn switches following back on, as in Material.

Tests

  • vitest --run src/chat src/material/components/ap-chat: 31 files, 185 tests pass. New tests cover:
    • the scroll adapter: smooth vs instant, after-render jumps, no jump after a prepend, load-more once per arrival, none before the layout settles, none after a remount once the host has nothing older, no request while loading, after the last page or when not paginated;
    • the scroll provider: the auto-scroll toggle before a scroll container exists, and following switched back on by a new turn, new chat or opened conversation;
    • latest-reply detection: groupless transcripts, kept after an older reply is rated, group membership; and in the Wind list, the latest reply's actions staying visible after an older reply is rated;
    • custom actions: dispatch, a declining pre-hook, showInOverflow actions kept out of the bar (opening the menu itself is checked in Storybook: under apollo-react's tests apollo-wind's built overflow trigger does not open);
    • attachments shown by name with a type icon, and a files-only message without a bubble;
    • the playground applying an initial autoScroll of false;
    • grouping and copy text;
    • settled replies: once, at stream end, on stop, not for restored conversations, including a restored reply re-sent when rated;
    • the list: items per group and anchors, bubble vs prose, streaming text, injected component and DOM renderers with cleanup, one announcement, loading rows, hidden actions while streaming and with disableActions;
    • copy and feedback events with aria-pressed, the "Copied" tick, one Copy event and two identical clipboard writes;
    • a Wind vs Material feedback payload parity test.
  • Scroll-engine behaviour (following during a stream, release on user intent, re-arming, preserving position on prepend, button visibility) is covered by the vendored primitive's own suite in apollo-wind, not repeated here.
  • tsc --noEmit -p packages/apollo-react: clean.
  • biome check on touched files: clean apart from four useExhaustiveDependencies warnings already present in untouched parts of chat-scroll-provider.tsx.
  • ApChat.registration.test.ts has a 30s timeout: its cold import of both renderers takes about 4.7s locally, close to the 5s default.
  • Scroll tests on a layout model in the style of the primitive's own suite: opens at the end and follows, anchors a new turn and never jumps back to an earlier one, stays put after release and re-arms from the button, rows stay message groups. The jump-back test fails on the previous list, and the prepend-keeps-position test fails without the adapter fix.
  • New strings, in en.json only (the l10n sync fills the other catalogs, as for earlier chat features): message.list-label, message.response-received, message.actions.copied, message.actions.feedback, message.loading-older.

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 (this PR)
  3. R-3 markdown renderer: feat(apollo-react): add the Wind chat markdown renderer #1375
  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-16563

🤖 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, 10:28:09 AM
apollo-docs Ready Preview · Logs Oct 15, 2026, 10:28:09 AM
apollo-landing Ready Preview · Logs Oct 15, 2026, 10:28:09 AM
apollo-vertex Ready Preview · Logs Oct 15, 2026, 10:28:09 AM

@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 force-pushed the feat/wind-chat-message-list branch 4 times, most recently from ac44243 to 3b44300 Compare October 15, 2026 17:19
@BenGSchulz
BenGSchulz marked this pull request as ready for review October 15, 2026 17:34
@BenGSchulz
BenGSchulz added this pull request to stack #1374 October 15, 2026 18:42
@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 53.9% 93.7% (325/347) 7.99 MB 31.44 MB +5.4 KB
@uipath/apollo-ui-icons — — 2.86 MB 6.97 MB ±0
@uipath/apollo-vertex — — — — —
@uipath/apollo-wind — — 785.2 KB 4.55 MB +11 B
@uipath/ap-chat 85.8% — 40.37 MB 45.58 MB +1.1 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.

Copilot AI balanced review requested due to automatic review settings October 16, 2026 16:10
@BenGSchulz
BenGSchulz force-pushed the feat/wind-chat-message-list branch from 3b44300 to 69b7fe5 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
@github-actions github-actions Bot added the size:XXL 1,000+ changed lines. label Oct 16, 2026
Replace the Wind message placeholder with a list on apollo-wind's
MessageScroller: one item per message group with a stable messageId,
user turns as scroll anchors, the user's message in a Bubble and the
assistant's as plain prose. Bodies dispatch through
resolveMessageRenderer: injected component renderers inline, injected
render() renderers in a DOM slot, and a minimal streaming text renderer
as the Wind default until the markdown renderer lands. MessageActions
carries copy, feedback (aria-pressed; the rating can be changed, each
change publishing Feedback again) and the message's own actions, shown
on the latest reply and on hover elsewhere. A live region announces each
settled reply; the log itself stays quiet so streamed chunks are not
read out. ThinkingIndicator shows inside the last group while waiting,
LoadingDots and the empty state are overlays, and a skeleton covers the
initial load: the scroller's rows are message groups only, because it
tells a new turn from a replaced row by counting rows, and transient
rows made it scroll back to an earlier turn. Spacing, gutters and the
user-message inset follow Material's, and the viewport cannot move
sideways.

Hoisted into chat/headless and adopted by Material where it had them:
- useChatMessages, groupChatMessages, withoutFakeStream,
  getMessageCopyText: the transcript and the feedback and copy effects
- useDefaultMessageActions, useIsLastAssistantMessage,
  useMessageActionHandler: built-in actions, latest-reply visibility,
  pre-hooked publishing
- useInjectedMessageRenderer: the DOM slot lifecycle
- getAttachmentFileType: the friendly file type
- useMessageInset: the user message's leading inset
New, Wind-only consumers for now:
- useChatScrollAdapter and useChatAutoScroll: service scroll events onto
  any scroll engine's commands, and ConversationLoadMore once per
  arrival at the top when paginated
- useLoadingMessages and useSettledReplies

Adds five chat strings to en.json (the l10n sync fills the rest) and an
autoScroll control to the playground; Wind's scenario message actions
use lucide names.

MST-16563

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BenGSchulz
BenGSchulz force-pushed the feat/wind-chat-message-list branch from 69b7fe5 to 86c91a9 Compare October 16, 2026 19:52
Copilot AI balanced review requested due to automatic review settings 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 — 86c91a9e Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-docs — 86c91a9e Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-landing — 86c91a9e Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-design — 86c91a9e 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