Skip to content

feat(apollo-react): add the Wind chat history and settings panels - #1397

Open
BenGSchulz wants to merge 2 commits into
feat/wind-chat-first-run-suggestionsfrom
feat/wind-chat-history-settings
Open

BenGSchulz wants to merge 2 commits into
feat/wind-chat-first-run-suggestionsfrom
feat/wind-chat-history-settings

Conversation

@BenGSchulz

@BenGSchulz BenGSchulz commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Seventh PR of the Wind chat renderer stack (MST-16568). It replaces the history and settings placeholders with the real panels, consumed from the service as Material does, and fixes a few service and Material issues found on the way.

It has two commits, so the host-visible fixes read as release notes on their own:

  1. fix(apollo-react): fix chat history and settings behaviour hosts rely on: see Service and Material fixes below. Its message is written as release notes.
  2. feat(apollo-react): add the Wind chat history and settings panels: everything else.

History: a popover in both modes, as Material's

  • An apollo-wind Popover under the header's history button (wind/common/panel-popover.tsx), sized as Material's: up to 568 by 441 in a panel, 16px in from its sides; 342 wide and as tall as the area under the header in FullScreen, where the conversation column makes room for it while it is open. It opens from the button's start and is held inside the panel, landing against the trailing edge as Material's does (Material's runs past the panel's edge in Embedded; Wind's stays inside).
  • Not modal: a modal popover made the page inert to the pointer and held focus, so a host's own dialog opened from a rename or delete pre-hook could not be clicked (checked in the browser with a <dialog> from the playground's pre-hooks). Focus starts in the search; Escape, a click elsewhere or focus leaving close it; clicks and focus in another open dialog, and clicks on the history button (which toggles it), leave it open. Focus returns to the history button unless a click put it elsewhere. Its open and close animation is off under reduced motion.
  • The list:
    • Search: by name on the client, or, with paginatedHistory, a debounced HistorySearch (400 ms) with a skeleton until the host's setHistory.
    • Date groups: Today, Yesterday, Last 7 days, Last 30 days, N months ago, in Material's order.
    • Loading more: HistoryLoadMore within 50px of the end, or as soon as the list has its size when the first page does not fill it; a spinner until the host answers, the scroll position kept when appendOlderHistoryItems lands, and none after done. The service keeps whether the host has more, so a list mounted after its last page (or a mode switch) does not ask again.
    • Results follow the search box: closing the history clears it, and a paginated host whose list was filtered is sent an empty HistorySearch (both renderers, through the shared hook). The skeleton shows only for a non-empty query, so a host that ignores the empty search leaves none behind.
    • Deleting the open conversation starts a new chat whether or not the history is showing (the subscription lives in the always-mounted list state).
    • Opening a conversation calls openConversation and closes the history; the open one is marked (aria-current).
    • Rename: inline field, Enter or blur renames through the RenameConversation pre-hook, Escape cancels without closing the history (and the blur that follows cannot rename); empty or unchanged names, a declining or failing pre-hook cancel. Hidden with disabledFeatures.renameChat.
    • Delete through the DeleteConversation pre-hook (no confirmation, as in Material); a declining or failing pre-hook keeps it.
    • Row actions show on hover or focus within the row, and always where nothing hovers (touch).
    • Empty and no-result states.

Settings: a slide-in panel, as Material's

  • Material's surface behaviour: it slides in from the right over 300 ms, over the conversation in Embedded and beside it in FullScreen (a 404px place that widens as it opens, so the column narrows as Material's does). Reduced motion turns the slide off.
  • Styled as the history popover (background, border, radius, shadow, title and X), so the two read as one family. In Embedded it covers the area below the header, 16px in from the sides and bottom, rather than the whole panel; in FullScreen it takes the history popover's footprint.
  • Closing: its X, the header's settings button, or Escape anywhere in the chat (not elsewhere on the page), always through the ToggleSettings pre-hook. The X and Escape run the pre-hook and then close explicitly, once at a time, so a held Escape or a second press during the pre-hook cannot reopen them; Escape is a React handler on the layout root, after Wind's own handlers, and ignores key repeat and IME composition. A focused button's tooltip takes the first Escape. Focus moves to the X on open and back to the settings button on close.
  • Embedded: the conversation under the panel is inert while it is open, standing in for Material's focus trap there. FullScreen does not trap focus, as in Material.
  • The panel stays mounted, so the settingsRenderer container is the same element for the chat's life: emptied on close and handed to the renderer again on each opening, as in Material.

Service and Material fixes (the fix commit)

  • newChat() clears activeConversationId. Before, the history kept marking the previous conversation as open after a new chat, and clicking it did nothing (both renderers). DOCS.md updated.
  • With paginatedHistory, closing the history after a search emits HistorySearch with an empty searchText (see above). DOCS.md updated.
  • appendOlderHistoryItems([]) stops the load-more indicator. DOCS.md already told hosts to call it with an empty page on error to stop loading, but the spinner kept running; this also caused the endless indicator in the paginated story.
  • Material's settings close button runs the ToggleSettings pre-hook (it ran ToggleHistory).
  • Material's settings content wraps instead of scrolling sideways (it inherited the panel's nowrap).

Hoisted into src/chat/headless (Material switched in this PR)

Helper What it carries Material change
history/history-groups.ts: groupHistory, getHistoryBucket, filterHistory Date buckets, group and item order, client search chat-history.tsx
hooks/use-chat-history.ts: useChatHistory, useHistoryGroupTitle The service's history, search (client or debounced HistorySearch), the search reset on close, load more near the end, scroll kept on append, the skeleton, spinner, empty and no-result flags, localized group titles chat-history.tsx keeps its popover and markup
hooks/use-history-item.ts: useHistoryItem The open conversation, opening, the rename flow with its pre-hook and double-commit guard, delete with its pre-hook, a new chat when the open one is deleted chat-history-item.tsx keeps its hover tracking and markup
hooks/use-settings-renderer.ts: useSettingsRenderer The settingsRenderer lifecycle chat-settings.tsx

Hooks and pure helpers only; nothing new in apollo-wind. The chat state gains settingsAnchorElement beside historyAnchorElement (focus return for the settings). One new string: autopilot-chat.history.loading-more ("Loading more chats", the load-more spinner's label).

Not matched on purpose

  • Embedded settings leave the header visible and usable instead of covering the whole panel, so Tab reaches the header as well as the panel; the rest of the chat is inert.
  • Settings also close on Escape (Material's only close from their buttons), and the slide respects reduced motion.
  • History items are a native button for the name with sibling rename and delete buttons, shown on hover or focus within the row. Material's item is a role="button" element containing the buttons, with aria-pressed, and tracks the pointer on the document.
  • The header's history and settings buttons carry aria-expanded; the history button also aria-haspopup="dialog".
  • The history popover is not modal (Material's is), so host dialogs from pre-hooks work.

Gaps for W-10

  • Search hard-codes its clear button's label ("Clear"), so it is off here (Material has no clear button either).
  • EmptyState is a page-level block (xl heading); the panel uses plain text for its empty states.
  • PopoverContent does not pass forceMount to its portal, so popover content cannot stay mounted while closed. The history keeps its list state outside the content instead.
  • The months-ago group title is the existing Material string "{count} months ago" (so "1 months ago"); it needs a plural form in the catalog. Left as is here.

Storybook

pnpm storybook:design, then Apollo React / Chat / Playground (Embedded, Full Screen, Compare renderers):

  • Chat history: conversations in every date bucket, history open. Search, open a conversation (the host loads a transcript after 400 ms), rename (turn disableRenameChat off) and delete. With preHooks on, every pre-hook confirms in a native <dialog> on the host page: confirm a delete with the history open. Press Escape while renaming, then outside the field.
  • Paginated chat history: 40 conversations in pages of 10. Scroll to the end for the next pages until the last; close and reopen, then scroll again (no spinner). In a tall FullScreen window the first page does not fill the list and the next ones load on their own. Type to search, then close: the list is reset.
  • Settings: enables and opens the settings. The renderer mounts a React root, as CAS does, and counts openings; close (X, header button, Escape) and reopen to see the same container refilled. In Embedded, the conversation is unreachable by Tab while the panel is open.
  • The playground's host answers OpenConversation, RenameConversation and DeleteConversation, and seeds the bucketed history. The Material maintenance stories keep their fixtures.

Tests

  • vitest --run src/chat src/material/components/ap-chat src/material/components/ap-tool-call: 61 files, 358 tests pass (the fix commit on its own: 55 files, 312 tests). New tests cover:
    • grouping: buckets relative to the given time (across midnight) and their boundaries, group and item order, client search;
    • the list hook: client filtering and flags, the debounced host search and its skeleton, the search reset on close and unmount (an empty HistorySearch once, only for a filtered host, no skeleton), load more once at a time until done, a short first page loading more, a host done before mount not asked again, the scroll position on append, no load more without pagination, a new chat when the open conversation is deleted;
    • the item hook: opening, rename with trimming, the double-commit guard, no rename on the blur after Escape, a declining or failing pre-hook, no rename while closed, delete with a declining or failing pre-hook;
    • the settings renderer: same container on each opening, emptied on close, a failing renderer;
    • the service: newChat clearing the active conversation, an empty page stopping the load-more indicator;
    • Wind history: the popover in both modes, groups, search, empty and no-result states, opening and aria-current, Escape and focus return, rename and delete (Escape in the field keeps the history open), a host dialog from the delete pre-hook keeping it usable, a click elsewhere closing it without moving focus, the header button closing it, renameChat, paginated search and load more, the search reset on close, the host's done state across openings;
    • Wind settings: the stable container, the Embedded slide-in with focus and inert, the ToggleSettings pre-hook from the X and the header button, Escape inside the chat only with focus return, a held or repeated Escape during the pre-hook closing once, IME composition, the FullScreen place, disabled settings;
    • Material regression tests on the shared hooks: groups and search, opening, rename and delete, load more, the search reset on close, the settings container, the ToggleSettings pre-hook, the wrapping settings content;
    • the header parity test now clicks each action with the panels closed, so each action's events stand alone.
  • tsc --noEmit -p packages/apollo-react: clean.
  • biome check on touched files: no new diagnostics.

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: 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 (this PR)
  8. R-8 CSS delivery and docs: feat(apollo-react): document the Wind chat setup and its delivery #1399

Jira: MST-16568

🤖 Generated with Claude Code

@github-actions
github-actions Bot temporarily deployed to preview/apollo-vertex October 16, 2026 20:33 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-docs October 16, 2026 20:33 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-design October 16, 2026 20:34 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-landing October 16, 2026 20:34 Destroyed
@github-actions

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-history-settings branch from a6c779e to 89177cd Compare October 16, 2026 21:14
@github-actions
github-actions Bot temporarily deployed to preview/apollo-landing October 16, 2026 21:14 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-docs October 16, 2026 21:14 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-design October 16, 2026 21:14 Destroyed
@github-actions
github-actions Bot temporarily deployed to preview/apollo-vertex October 16, 2026 21:14 Destroyed
BenGSchulz and others added 2 commits October 16, 2026 15:39
- newChat() now clears activeConversationId. Before, the history kept
  marking the previous conversation as open after a new chat, and
  clicking it did nothing. Hosts that read activeConversationId after
  newChat now get null.
- With paginatedHistory, closing the history after a search clears the
  search box and emits HistorySearch with an empty searchText, so the
  host returns its unfiltered list: the list always follows the box.
  Answer it like any other search. It is sent only when the host's list
  was filtered, and no skeleton waits for it.
- appendOlderHistoryItems([]) now stops the load-more indicator. The
  documented way to stop loading on error (an empty page with done)
  left the spinner running.
- Material: the settings close button runs the ToggleSettings pre-hook;
  it ran ToggleHistory.
- Material: the settings content wraps instead of scrolling sideways
  (it inherited the panel's nowrap).

MST-16568

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the Wind chat's history and settings placeholders. The history
opens as a popover under the header's history button in both modes, as
Material's does, with search, date groups, the host's paginated
loading, opening, rename and delete. It is not modal, so a host's own
dialog opened from a pre-hook stays usable. The settings slide in from
the right as Material's do: over the conversation in Embedded, beside
it in FullScreen, closing from their X, the header button or Escape
through the ToggleSettings pre-hook. The settings panel stays mounted,
so the settingsRenderer container is the same element for the chat's
life, emptied on close and rendered into on each opening.

Hoist the history grouping and search, the list state (debounced
HistorySearch, HistoryLoadMore near the end or when the first page does
not fill the list, scroll kept on append, the search reset on close, a
new chat when the open conversation is deleted), the per-item open,
rename and delete flows, and the settingsRenderer lifecycle into
src/chat/headless, and switch Material's history and settings to them.
The service keeps whether the host has more history, so a list mounted
after its last page does not ask again.

MST-16568

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

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 57.6% 80.5% (352/437) 8.04 MB 31.71 MB +58.3 KB
@uipath/apollo-ui-icons — — 2.86 MB 6.97 MB ±0
@uipath/apollo-vertex — — — — —
@uipath/apollo-wind — — 785.2 KB 4.55 MB +16 B
@uipath/ap-chat 85.8% — 40.37 MB 45.59 MB +3.4 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 marked this pull request as ready for review October 16, 2026 23:33
Copilot AI balanced review requested due to automatic review settings October 16, 2026 23:33

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 16, 2026

This branch was successfully deployed

4 active deployments
preview/apollo-docs — 1c25bf5c Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-landing — 1c25bf5c Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-design — 1c25bf5c Deployed Oct 16, 2026 by github-actions[bot]
preview/apollo-vertex — 1c25bf5c 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