@flamingo-stack/openframe-frontend-core 0.0.685-snapshot.20260929164409 → 0.0.685

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/{chunk-DL67S7H4.cjs → chunk-5LJZPJHX.cjs} +59 -59
  2. package/dist/{chunk-DL67S7H4.cjs.map → chunk-5LJZPJHX.cjs.map} +1 -1
  3. package/dist/{chunk-IZ3WKG62.js → chunk-65KTXRBY.js} +6 -6
  4. package/dist/{chunk-IBKMIJ7Y.js → chunk-7XF63THA.js} +6 -6
  5. package/dist/{chunk-SI77HF4U.cjs → chunk-A3CSQB4A.cjs} +805 -1247
  6. package/dist/chunk-A3CSQB4A.cjs.map +1 -0
  7. package/dist/{chunk-5MUR43QD.cjs → chunk-B25CF3PO.cjs} +30 -30
  8. package/dist/{chunk-5MUR43QD.cjs.map → chunk-B25CF3PO.cjs.map} +1 -1
  9. package/dist/{chunk-32KBPEF7.js → chunk-B32YLOYQ.js} +4 -4
  10. package/dist/{chunk-OBLED7JY.cjs → chunk-B5N3AHPG.cjs} +81 -81
  11. package/dist/{chunk-OBLED7JY.cjs.map → chunk-B5N3AHPG.cjs.map} +1 -1
  12. package/dist/{chunk-QSRUYHTW.js → chunk-BZ2XOEHW.js} +3 -3
  13. package/dist/{chunk-XATA4OVO.cjs → chunk-C3B36OYZ.cjs} +3 -3
  14. package/dist/{chunk-XATA4OVO.cjs.map → chunk-C3B36OYZ.cjs.map} +1 -1
  15. package/dist/{chunk-V26TKJ4Z.cjs → chunk-CMQXPD5C.cjs} +28 -28
  16. package/dist/{chunk-V26TKJ4Z.cjs.map → chunk-CMQXPD5C.cjs.map} +1 -1
  17. package/dist/{chunk-QNWQQ4RT.cjs → chunk-DQOQ3KTP.cjs} +73 -78
  18. package/dist/chunk-DQOQ3KTP.cjs.map +1 -0
  19. package/dist/{chunk-5HVGOIAE.cjs → chunk-DWXWEGGO.cjs} +22 -22
  20. package/dist/{chunk-5HVGOIAE.cjs.map → chunk-DWXWEGGO.cjs.map} +1 -1
  21. package/dist/{chunk-6CMH53MP.cjs → chunk-EFBMNVSH.cjs} +15 -15
  22. package/dist/{chunk-6CMH53MP.cjs.map → chunk-EFBMNVSH.cjs.map} +1 -1
  23. package/dist/{chunk-DDFYKF3B.js → chunk-GHLGXFNU.js} +3 -3
  24. package/dist/{chunk-VDV7K5TD.js → chunk-GXV6UEVY.js} +3 -3
  25. package/dist/{chunk-AEAFQTVR.cjs → chunk-HMXUOLZR.cjs} +20 -20
  26. package/dist/{chunk-AEAFQTVR.cjs.map → chunk-HMXUOLZR.cjs.map} +1 -1
  27. package/dist/{chunk-2OD2PPNO.cjs → chunk-K2HZG4RM.cjs} +32 -32
  28. package/dist/{chunk-2OD2PPNO.cjs.map → chunk-K2HZG4RM.cjs.map} +1 -1
  29. package/dist/{chunk-JNDHXL2D.cjs → chunk-M4HYIXP6.cjs} +383 -384
  30. package/dist/chunk-M4HYIXP6.cjs.map +1 -0
  31. package/dist/{chunk-KTWJKCUB.js → chunk-N4Z6X5BV.js} +5 -5
  32. package/dist/{chunk-T2RHI676.js → chunk-O7LOZW4R.js} +5 -5
  33. package/dist/{chunk-ZHYYGIAI.cjs → chunk-P2ASEPA2.cjs} +6 -6
  34. package/dist/{chunk-ZHYYGIAI.cjs.map → chunk-P2ASEPA2.cjs.map} +1 -1
  35. package/dist/{chunk-CHRDLXXX.js → chunk-PSQKD6ZN.js} +3 -3
  36. package/dist/{chunk-GSJR33UP.cjs → chunk-QHLJSB2J.cjs} +12 -12
  37. package/dist/{chunk-GSJR33UP.cjs.map → chunk-QHLJSB2J.cjs.map} +1 -1
  38. package/dist/{chunk-CFL3EW4V.cjs → chunk-T4DUTP5C.cjs} +5 -5
  39. package/dist/{chunk-CFL3EW4V.cjs.map → chunk-T4DUTP5C.cjs.map} +1 -1
  40. package/dist/{chunk-22RSJ57Z.js → chunk-T6KWNJDW.js} +465 -470
  41. package/dist/chunk-T6KWNJDW.js.map +1 -0
  42. package/dist/{chunk-2FGFB2HY.cjs → chunk-TJI5V5BR.cjs} +7 -7
  43. package/dist/{chunk-2FGFB2HY.cjs.map → chunk-TJI5V5BR.cjs.map} +1 -1
  44. package/dist/{chunk-HLEXYTOE.js → chunk-TUG7IT6H.js} +2 -2
  45. package/dist/{chunk-DWCVA6XT.cjs → chunk-U3IXG7TR.cjs} +33 -33
  46. package/dist/{chunk-DWCVA6XT.cjs.map → chunk-U3IXG7TR.cjs.map} +1 -1
  47. package/dist/{chunk-WJ5XN4AL.js → chunk-UT333EOT.js} +4 -4
  48. package/dist/{chunk-OTV4W3YD.js → chunk-VDPCK437.js} +5 -6
  49. package/dist/{chunk-OTV4W3YD.js.map → chunk-VDPCK437.js.map} +1 -1
  50. package/dist/{chunk-5GK7TU2Q.js → chunk-WJK2EETW.js} +2 -2
  51. package/dist/{chunk-YPXXXX6R.js → chunk-XGX6JSEU.js} +1644 -2086
  52. package/dist/chunk-XGX6JSEU.js.map +1 -0
  53. package/dist/{chunk-XODE4YTG.js → chunk-XW7MUIW4.js} +3 -3
  54. package/dist/{chunk-N74MQ4HQ.js → chunk-Y7ZYOQNB.js} +3 -3
  55. package/dist/{chunk-2NUB5EMG.cjs → chunk-YCTVLN47.cjs} +10 -10
  56. package/dist/{chunk-2NUB5EMG.cjs.map → chunk-YCTVLN47.cjs.map} +1 -1
  57. package/dist/{chunk-TILC52ZF.js → chunk-ZF4Z36G4.js} +7 -7
  58. package/dist/components/case-studies/index.cjs +16 -16
  59. package/dist/components/case-studies/index.js +4 -4
  60. package/dist/components/chat/ai-assistant-info.d.ts.map +1 -1
  61. package/dist/components/chat/approval-request-message.d.ts.map +1 -1
  62. package/dist/components/chat/chat-input.d.ts.map +1 -1
  63. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  64. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  65. package/dist/components/chat/error-message-display.d.ts.map +1 -1
  66. package/dist/components/chat/index.cjs +3 -21
  67. package/dist/components/chat/index.cjs.map +1 -1
  68. package/dist/components/chat/index.d.ts +0 -6
  69. package/dist/components/chat/index.d.ts.map +1 -1
  70. package/dist/components/chat/index.js +2 -20
  71. package/dist/components/chat/msp-organization-card.d.ts +1 -7
  72. package/dist/components/chat/msp-organization-card.d.ts.map +1 -1
  73. package/dist/components/chat/types/chat.types.d.ts +0 -13
  74. package/dist/components/chat/types/chat.types.d.ts.map +1 -1
  75. package/dist/components/chat/types/component.types.d.ts +1 -20
  76. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  77. package/dist/components/contact/index.cjs +5 -5
  78. package/dist/components/contact/index.js +4 -4
  79. package/dist/components/docs/index.cjs +8 -8
  80. package/dist/components/docs/index.js +7 -7
  81. package/dist/components/embeds/index.cjs +5 -5
  82. package/dist/components/embeds/index.js +4 -4
  83. package/dist/components/faq/index.cjs +5 -5
  84. package/dist/components/faq/index.js +4 -4
  85. package/dist/components/features/index.cjs +4 -4
  86. package/dist/components/features/index.js +3 -3
  87. package/dist/components/help-center-pages/index.cjs +91 -91
  88. package/dist/components/help-center-pages/index.js +15 -15
  89. package/dist/components/index.cjs +55 -73
  90. package/dist/components/index.cjs.map +1 -1
  91. package/dist/components/index.js +15 -33
  92. package/dist/components/index.js.map +1 -1
  93. package/dist/components/meeting-scheduler/index.cjs +41 -41
  94. package/dist/components/meeting-scheduler/index.js +3 -3
  95. package/dist/components/navigation/index.cjs +7 -7
  96. package/dist/components/navigation/index.js +6 -6
  97. package/dist/components/onboarding-guides/index.cjs +7 -7
  98. package/dist/components/onboarding-guides/index.js +6 -6
  99. package/dist/components/related-content/index.cjs +3 -3
  100. package/dist/components/related-content/index.js +2 -2
  101. package/dist/components/tickets/index.cjs +8 -8
  102. package/dist/components/tickets/index.js +7 -7
  103. package/dist/components/ui/avatar-stack.d.ts +1 -5
  104. package/dist/components/ui/avatar-stack.d.ts.map +1 -1
  105. package/dist/components/ui/index.cjs +4 -22
  106. package/dist/components/ui/index.cjs.map +1 -1
  107. package/dist/components/ui/index.js +3 -21
  108. package/dist/components/ui/reopen-ticket-request-modal.d.ts +1 -4
  109. package/dist/components/ui/reopen-ticket-request-modal.d.ts.map +1 -1
  110. package/dist/index.cjs +9 -27
  111. package/dist/index.cjs.map +1 -1
  112. package/dist/index.js +7 -25
  113. package/dist/index.js.map +1 -1
  114. package/package.json +1 -1
  115. package/src/components/chat/ai-assistant-info.tsx +1 -7
  116. package/src/components/chat/approval-request-message.tsx +1 -6
  117. package/src/components/chat/chat-input.tsx +1 -26
  118. package/src/components/chat/chat-message-enhanced.tsx +142 -180
  119. package/src/components/chat/chat-message-list.tsx +164 -169
  120. package/src/components/chat/error-message-display.tsx +3 -21
  121. package/src/components/chat/index.ts +0 -6
  122. package/src/components/chat/msp-organization-card.tsx +0 -35
  123. package/src/components/chat/types/chat.types.ts +0 -15
  124. package/src/components/chat/types/component.types.ts +0 -20
  125. package/src/components/ui/avatar-stack.tsx +5 -15
  126. package/src/components/ui/reopen-ticket-request-modal.tsx +2 -6
  127. package/src/styles/ods-colors.css +0 -10
  128. package/src/styles/ods-effects.css +0 -13
  129. package/dist/chunk-22RSJ57Z.js.map +0 -1
  130. package/dist/chunk-JNDHXL2D.cjs.map +0 -1
  131. package/dist/chunk-QNWQQ4RT.cjs.map +0 -1
  132. package/dist/chunk-SI77HF4U.cjs.map +0 -1
  133. package/dist/chunk-YPXXXX6R.js.map +0 -1
  134. package/dist/components/chat/chat-appearance-context.d.ts +0 -9
  135. package/dist/components/chat/chat-appearance-context.d.ts.map +0 -1
  136. package/dist/components/chat/chat-nav-sidebar.d.ts +0 -50
  137. package/dist/components/chat/chat-nav-sidebar.d.ts.map +0 -1
  138. package/dist/components/chat/chat-top-navigation.d.ts +0 -29
  139. package/dist/components/chat/chat-top-navigation.d.ts.map +0 -1
  140. package/dist/components/chat/onboarding-carousel.d.ts +0 -29
  141. package/dist/components/chat/onboarding-carousel.d.ts.map +0 -1
  142. package/dist/components/chat/system-event-message.d.ts +0 -14
  143. package/dist/components/chat/system-event-message.d.ts.map +0 -1
  144. package/src/components/chat/__tests__/chat-appearance.test.tsx +0 -79
  145. package/src/components/chat/__tests__/onboarding-carousel.test.tsx +0 -56
  146. package/src/components/chat/chat-appearance-context.tsx +0 -15
  147. package/src/components/chat/chat-nav-sidebar.tsx +0 -163
  148. package/src/components/chat/chat-top-navigation.tsx +0 -109
  149. package/src/components/chat/onboarding-carousel.tsx +0 -162
  150. package/src/components/chat/system-event-message.tsx +0 -54
  151. package/src/components/ui/__tests__/avatar-stack.test.tsx +0 -25
  152. package/src/stories/FaeLayoutV2.stories.tsx +0 -150
  153. package/src/stories/FaeOnboardingV2.stories.tsx +0 -68
  154. package/src/stories/FaeThreadV2.stories.tsx +0 -192
  155. package/src/stories/fae-brand-decorator.tsx +0 -28
  156. /package/dist/{chunk-IZ3WKG62.js.map → chunk-65KTXRBY.js.map} +0 -0
  157. /package/dist/{chunk-IBKMIJ7Y.js.map → chunk-7XF63THA.js.map} +0 -0
  158. /package/dist/{chunk-32KBPEF7.js.map → chunk-B32YLOYQ.js.map} +0 -0
  159. /package/dist/{chunk-QSRUYHTW.js.map → chunk-BZ2XOEHW.js.map} +0 -0
  160. /package/dist/{chunk-DDFYKF3B.js.map → chunk-GHLGXFNU.js.map} +0 -0
  161. /package/dist/{chunk-VDV7K5TD.js.map → chunk-GXV6UEVY.js.map} +0 -0
  162. /package/dist/{chunk-KTWJKCUB.js.map → chunk-N4Z6X5BV.js.map} +0 -0
  163. /package/dist/{chunk-T2RHI676.js.map → chunk-O7LOZW4R.js.map} +0 -0
  164. /package/dist/{chunk-CHRDLXXX.js.map → chunk-PSQKD6ZN.js.map} +0 -0
  165. /package/dist/{chunk-HLEXYTOE.js.map → chunk-TUG7IT6H.js.map} +0 -0
  166. /package/dist/{chunk-WJ5XN4AL.js.map → chunk-UT333EOT.js.map} +0 -0
  167. /package/dist/{chunk-5GK7TU2Q.js.map → chunk-WJK2EETW.js.map} +0 -0
  168. /package/dist/{chunk-XODE4YTG.js.map → chunk-XW7MUIW4.js.map} +0 -0
  169. /package/dist/{chunk-N74MQ4HQ.js.map → chunk-Y7ZYOQNB.js.map} +0 -0
  170. /package/dist/{chunk-TILC52ZF.js.map → chunk-ZF4Z36G4.js.map} +0 -0
@@ -14,12 +14,10 @@ import { useStickToBottom } from 'use-stick-to-bottom';
14
14
  import { cn } from '../../utils/cn';
15
15
  import { DotsLoaderIcon, Arrow02DownIcon } from '../icons-v2-generated';
16
16
  import { OverlayOpenRegistryProvider } from '../ui/overlay-open-registry';
17
- import { ChatAppearanceContext } from './chat-appearance-context';
18
17
  import { ChatMessageEnhanced } from './chat-message-enhanced';
19
18
  import { ChatMessageListSkeleton } from './chat-message-skeleton';
20
19
  import { CyclingPhrase } from './cycling-phrase';
21
20
  import type { ChatMessageListProps } from './types';
22
- import { CHAT_APPEARANCE } from './types/chat.types';
23
21
  import { SCROLL_ANCHOR } from './types/message.types';
24
22
  import type { MessageContent } from './types/message.types';
25
23
 
@@ -130,7 +128,6 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
130
128
  contentClassName,
131
129
  assistantType,
132
130
  approvalVariant,
133
- appearance = CHAT_APPEARANCE.CLASSIC,
134
131
  assistantIcon,
135
132
  pendingApprovals,
136
133
  hasNextPage,
@@ -918,119 +915,118 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
918
915
  const showStreamingLoader = isTyping && !isPausedOnApproval && !(pendingApprovals && pendingApprovals.length > 0);
919
916
 
920
917
  return (
921
- <ChatAppearanceContext.Provider value={appearance}>
922
- <OverlayOpenRegistryProvider onOpenChange={handleOverlayOpenChange}>
923
- <div className="flex min-h-0 flex-1 flex-col">
924
- {/* Positioning box for the jump-to-bottom button: it wraps ONLY the
918
+ <OverlayOpenRegistryProvider onOpenChange={handleOverlayOpenChange}>
919
+ <div className="flex min-h-0 flex-1 flex-col">
920
+ {/* Positioning box for the jump-to-bottom button: it wraps ONLY the
925
921
  scroller, so `absolute bottom-…` really is the scroller's lower
926
922
  edge. The outer flex column also holds the streaming loader and the
927
923
  approvals bar, and anchoring against THAT box put the button over
928
924
  the loader for the whole of every stream — precisely when it is
929
925
  most likely to be visible. */}
930
- <div className="relative flex min-h-0 flex-1 flex-col">
926
+ <div className="relative flex min-h-0 flex-1 flex-col">
927
+ <div
928
+ ref={setScrollRef}
929
+ className={cn(
930
+ 'flex h-full w-full flex-1 flex-col overflow-y-auto overflow-x-hidden',
931
+ // `overscroll-contain` (default ON, opt-out via `overscrollContain={false}`):
932
+ // reaching the top/bottom of the thread must NOT chain the wheel/
933
+ // touch scroll to the page behind the chat — e.g. the company-hub
934
+ // deck (native body scroll + sticky slide panels) would otherwise
935
+ // advance slides while you scroll the chat (fixed in #1501; kept as
936
+ // the prop default here). Passive in-page DEMO chats disable it so
937
+ // the surrounding page keeps scrolling normally when the pointer is
938
+ // over the (non-interactive) thread.
939
+ overscrollContain && 'overscroll-contain',
940
+ 'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30',
941
+ className,
942
+ )}
943
+ {...props}
944
+ >
931
945
  <div
932
- ref={setScrollRef}
946
+ ref={setContentRef}
933
947
  className={cn(
934
- 'flex h-full w-full flex-1 flex-col overflow-y-auto overflow-x-hidden',
935
- // `overscroll-contain` (default ON, opt-out via `overscrollContain={false}`):
936
- // reaching the top/bottom of the thread must NOT chain the wheel/
937
- // touch scroll to the page behind the chat — e.g. the company-hub
938
- // deck (native body scroll + sticky slide panels) would otherwise
939
- // advance slides while you scroll the chat (fixed in #1501; kept as
940
- // the prop default here). Passive in-page DEMO chats disable it so
941
- // the surrounding page keeps scrolling normally when the pointer is
942
- // over the (non-interactive) thread.
943
- overscrollContain && 'overscroll-contain',
944
- 'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30',
945
- className,
948
+ // `fullWidth=true` drops the centered-narrow column for
949
+ // side-panel hosts (e.g. multi-platform-hub Mingo). Same
950
+ // semantics as ChatHeader / ChatInput / ChatFooter.
951
+ fullWidth
952
+ ? 'flex w-full min-w-0 flex-col pb-[var(--spacing-system-xs)]'
953
+ : 'mx-auto flex w-full min-w-0 max-w-ods-content-narrow flex-col pb-[var(--spacing-system-xs)]',
954
+ contentClassName ?? 'px-[var(--spacing-system-m)]',
946
955
  )}
947
- {...props}
956
+ style={{ minHeight: '100%' }}
948
957
  >
949
- <div
950
- ref={setContentRef}
951
- className={cn(
952
- // `fullWidth=true` drops the centered-narrow column for
953
- // side-panel hosts (e.g. multi-platform-hub Mingo). Same
954
- // semantics as ChatHeader / ChatInput / ChatFooter.
955
- fullWidth
956
- ? 'flex w-full min-w-0 flex-col pb-[var(--spacing-system-xs)]'
957
- : 'mx-auto flex w-full min-w-0 max-w-ods-content-narrow flex-col pb-[var(--spacing-system-xs)]',
958
- contentClassName ?? 'px-[var(--spacing-system-m)]',
959
- )}
960
- style={{ minHeight: '100%' }}
961
- >
962
- {hasNextPage && <div ref={sentinelRef} className="h-px" />}
963
- <div className="flex-1" />
964
- {messages.map((message, index) => {
965
- // Hidden messages (synthetic continuation prompts the host
966
- // injects after an approval card) are part of the API
967
- // conversation history but never render. Skipping here
968
- // keeps the visible thread coherent — see
969
- // `Message.hidden` doc-comment in message.types.ts.
970
- if (message.hidden) return null;
971
- // ONE OWNER for the pending turn. Both send paths mint an empty
972
- // assistant placeholder (the accumulation target — it MUST stay
973
- // in state) and flip the phase to 'thinking'. Rendering it here
974
- // too would put an author label with no body in the transcript
975
- // WHILE the footer loader below already represents the same
976
- // pending turn — two owners, and an `aria-live` log that
977
- // announces a bare "Mingo". The footer loader wins (it is
978
- // already `role="status" aria-live="polite"`, sits outside the
979
- // scroller so it can't jitter the thread, and cycles the
980
- // progress phrase), so the placeholder is not rendered AT ALL
981
- // until it carries something visible — removed from the live
982
- // region, not merely hidden. Mirrors `endSseTurn`, which prunes
983
- // an empty trailing assistant AFTER a turn; this closes the same
984
- // window BEFORE the first token. Any non-text segment (tool
985
- // execution, approval card, thinking) counts as visible.
986
- const isEmptyPendingTurn =
987
- index === messages.length - 1 &&
988
- isTyping &&
989
- message.role === 'assistant' &&
990
- !hasNonEmptyContent(message.content) &&
991
- (!Array.isArray(message.content) || message.content.every(s => s.type === 'text'));
992
- if (isEmptyPendingTurn) return null;
993
- const ownedContent = renderAfterMessage?.(message, index);
994
- const row = (
995
- <ChatMessageEnhanced
996
- key={message.id}
997
- ref={getRegisterMessageEl(message.id)}
998
- role={message.role}
999
- name={message.name}
1000
- content={message.content}
1001
- timestamp={message.timestamp}
1002
- isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
1003
- avatar={showAvatars ? message.avatar : null}
1004
- showAvatar={showAvatars}
1005
- assistantType={message.assistantType || assistantType}
1006
- approvalVariant={approvalVariant}
1007
- authorType={message.authorType}
1008
- assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
1009
- contextItems={message.contextItems}
1010
- resolveContextIcon={resolveContextIcon}
1011
- renderContextItem={renderContextItem}
1012
- renderMention={renderMention}
1013
- renderEntityCard={renderEntityCard}
1014
- refs={message.refs}
1015
- onAskSelect={index > lastUserMessageIndex ? onAskSelect : undefined}
1016
- NavLinkAnchor={NavLinkAnchor}
1017
- />
1018
- );
1019
- // Only wrap when there IS owned content: the bare row keeps
1020
- // its own key and its place as a direct flex child, so a
1021
- // thread without any stays byte-identical to before.
1022
- if (!ownedContent) return row;
1023
- return (
1024
- <div key={message.id} className="grid w-full min-w-0 grid-cols-1">
1025
- {row}
1026
- {ownedContent}
1027
- </div>
1028
- );
1029
- })}
1030
- </div>
958
+ {hasNextPage && <div ref={sentinelRef} className="h-px" />}
959
+ <div className="flex-1" />
960
+ {messages.map((message, index) => {
961
+ // Hidden messages (synthetic continuation prompts the host
962
+ // injects after an approval card) are part of the API
963
+ // conversation history but never render. Skipping here
964
+ // keeps the visible thread coherent — see
965
+ // `Message.hidden` doc-comment in message.types.ts.
966
+ if (message.hidden) return null;
967
+ // ONE OWNER for the pending turn. Both send paths mint an empty
968
+ // assistant placeholder (the accumulation target — it MUST stay
969
+ // in state) and flip the phase to 'thinking'. Rendering it here
970
+ // too would put an author label with no body in the transcript
971
+ // WHILE the footer loader below already represents the same
972
+ // pending turn — two owners, and an `aria-live` log that
973
+ // announces a bare "Mingo". The footer loader wins (it is
974
+ // already `role="status" aria-live="polite"`, sits outside the
975
+ // scroller so it can't jitter the thread, and cycles the
976
+ // progress phrase), so the placeholder is not rendered AT ALL
977
+ // until it carries something visible — removed from the live
978
+ // region, not merely hidden. Mirrors `endSseTurn`, which prunes
979
+ // an empty trailing assistant AFTER a turn; this closes the same
980
+ // window BEFORE the first token. Any non-text segment (tool
981
+ // execution, approval card, thinking) counts as visible.
982
+ const isEmptyPendingTurn =
983
+ index === messages.length - 1 &&
984
+ isTyping &&
985
+ message.role === 'assistant' &&
986
+ !hasNonEmptyContent(message.content) &&
987
+ (!Array.isArray(message.content) || message.content.every(s => s.type === 'text'));
988
+ if (isEmptyPendingTurn) return null;
989
+ const ownedContent = renderAfterMessage?.(message, index);
990
+ const row = (
991
+ <ChatMessageEnhanced
992
+ key={message.id}
993
+ ref={getRegisterMessageEl(message.id)}
994
+ role={message.role}
995
+ name={message.name}
996
+ content={message.content}
997
+ timestamp={message.timestamp}
998
+ isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
999
+ avatar={showAvatars ? message.avatar : null}
1000
+ showAvatar={showAvatars}
1001
+ assistantType={message.assistantType || assistantType}
1002
+ approvalVariant={approvalVariant}
1003
+ authorType={message.authorType}
1004
+ assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
1005
+ contextItems={message.contextItems}
1006
+ resolveContextIcon={resolveContextIcon}
1007
+ renderContextItem={renderContextItem}
1008
+ renderMention={renderMention}
1009
+ renderEntityCard={renderEntityCard}
1010
+ refs={message.refs}
1011
+ onAskSelect={index > lastUserMessageIndex ? onAskSelect : undefined}
1012
+ NavLinkAnchor={NavLinkAnchor}
1013
+ />
1014
+ );
1015
+ // Only wrap when there IS owned content: the bare row keeps
1016
+ // its own key and its place as a direct flex child, so a
1017
+ // thread without any stays byte-identical to before.
1018
+ if (!ownedContent) return row;
1019
+ return (
1020
+ <div key={message.id} className="grid w-full min-w-0 grid-cols-1">
1021
+ {row}
1022
+ {ownedContent}
1023
+ </div>
1024
+ );
1025
+ })}
1031
1026
  </div>
1027
+ </div>
1032
1028
 
1033
- {/* Jump to bottom. The manual escape hatch from auto-scroll that
1029
+ {/* Jump to bottom. The manual escape hatch from auto-scroll that
1034
1030
  every 2026 chat surface ships (AI Elements calls it
1035
1031
  `ConversationScrollButton`) and the WCAG 2.2.2 counterpart to
1036
1032
  moving content: the reader can always get back to the live
@@ -1041,80 +1037,79 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
1041
1037
  height either way — a sibling would shrink the scroller and
1042
1038
  move the very bottom it points at. Hidden while at the bottom
1043
1039
  and in passive demo hosts. */}
1044
- {autoScroll && !atBottom && messages.length > 0 && (
1045
- <button
1046
- type="button"
1047
- onClick={() => {
1048
- // Clicking IS the intent to return to the live end, so it
1049
- // re-arms the follow lock a scroll-up had released.
1050
- followBottomRef.current = true;
1051
- void scrollToBottom({ animation: 'smooth' });
1052
- }}
1053
- aria-label="Scroll to latest message"
1054
- className={cn(
1055
- 'absolute bottom-[var(--spacing-system-s)] left-1/2 z-10 -translate-x-1/2',
1056
- 'flex size-9 items-center justify-center rounded-full',
1057
- 'border border-ods-border bg-ods-card text-ods-text-primary shadow-lg',
1058
- 'transition-colors hover:bg-ods-bg-hover',
1059
- )}
1060
- >
1061
- <Arrow02DownIcon size={16} />
1062
- </button>
1063
- )}
1064
- </div>
1065
-
1066
- {/* Footer-pinned streaming loader — outside the scroller so it
1067
- doesn't jitter as the streaming message grows. Color is set
1068
- via inline style (CSS var) rather than a Tailwind class so it
1069
- is JIT-independent when this lib is consumed via yalc. */}
1070
- {showStreamingLoader && (
1071
- <div
1040
+ {autoScroll && !atBottom && messages.length > 0 && (
1041
+ <button
1042
+ type="button"
1043
+ onClick={() => {
1044
+ // Clicking IS the intent to return to the live end, so it
1045
+ // re-arms the follow lock a scroll-up had released.
1046
+ followBottomRef.current = true;
1047
+ void scrollToBottom({ animation: 'smooth' });
1048
+ }}
1049
+ aria-label="Scroll to latest message"
1072
1050
  className={cn(
1073
- fullWidth
1074
- ? 'flex w-full items-center gap-[var(--spacing-system-xxs)] py-[var(--spacing-system-xs)]'
1075
- : 'mx-auto flex w-full max-w-ods-content-narrow items-center gap-[var(--spacing-system-xxs)] py-[var(--spacing-system-xs)]',
1076
- contentClassName ?? 'px-[var(--spacing-system-m)]',
1051
+ 'absolute bottom-[var(--spacing-system-s)] left-1/2 z-10 -translate-x-1/2',
1052
+ 'flex size-9 items-center justify-center rounded-full',
1053
+ 'border border-ods-border bg-ods-card text-ods-text-primary shadow-lg',
1054
+ 'transition-colors hover:bg-ods-bg-hover',
1077
1055
  )}
1078
- style={{ color: 'var(--color-text-muted)' }}
1079
- role="status"
1080
- aria-live="polite"
1081
1056
  >
1082
- <DotsLoaderIcon className="h-6 w-6" />
1083
- <CyclingPhrase words={STREAMING_WORDS} className="text-h6" />
1084
- </div>
1057
+ <Arrow02DownIcon size={16} />
1058
+ </button>
1085
1059
  )}
1060
+ </div>
1086
1061
 
1087
- {/* Sticky Pending Approvals — outside the scroller; same
1062
+ {/* Footer-pinned streaming loader — outside the scroller so it
1063
+ doesn't jitter as the streaming message grows. Color is set
1064
+ via inline style (CSS var) rather than a Tailwind class so it
1065
+ is JIT-independent when this lib is consumed via yalc. */}
1066
+ {showStreamingLoader && (
1067
+ <div
1068
+ className={cn(
1069
+ fullWidth
1070
+ ? 'flex w-full items-center gap-[var(--spacing-system-xxs)] py-[var(--spacing-system-xs)]'
1071
+ : 'mx-auto flex w-full max-w-ods-content-narrow items-center gap-[var(--spacing-system-xxs)] py-[var(--spacing-system-xs)]',
1072
+ contentClassName ?? 'px-[var(--spacing-system-m)]',
1073
+ )}
1074
+ style={{ color: 'var(--color-text-muted)' }}
1075
+ role="status"
1076
+ aria-live="polite"
1077
+ >
1078
+ <DotsLoaderIcon className="h-6 w-6" />
1079
+ <CyclingPhrase words={STREAMING_WORDS} className="text-h6" />
1080
+ </div>
1081
+ )}
1082
+
1083
+ {/* Sticky Pending Approvals — outside the scroller; same
1088
1084
  structure as the v1 baseline. The library's RO watches
1089
1085
  `contentRef` (INSIDE the scroller), so height changes to
1090
1086
  this sibling don't trigger the library directly. If a
1091
1087
  future UX requires snapping to bottom when approvals
1092
1088
  appear, that hookup goes here. */}
1093
- {pendingApprovals && pendingApprovals.length > 0 && (
1094
- <div
1095
- className={cn(
1096
- 'border-t border-ods-border bg-ods-bg/95 backdrop-blur-sm',
1097
- fullWidth ? 'w-full' : 'mx-auto w-full max-w-ods-content-narrow',
1098
- contentClassName ?? 'px-[var(--spacing-system-m)]',
1099
- )}
1100
- >
1101
- <ChatMessageEnhanced
1102
- role="assistant"
1103
- name={assistantType === 'mingo' ? 'Mingo' : 'Fae'}
1104
- content={pendingApprovals}
1105
- timestamp={new Date()}
1106
- showAvatar={showAvatars}
1107
- assistantType={assistantType}
1108
- approvalVariant={approvalVariant}
1109
- assistantIcon={assistantIcon}
1110
- NavLinkAnchor={NavLinkAnchor}
1111
- className="py-3"
1112
- />
1113
- </div>
1114
- )}
1115
- </div>
1116
- </OverlayOpenRegistryProvider>
1117
- </ChatAppearanceContext.Provider>
1089
+ {pendingApprovals && pendingApprovals.length > 0 && (
1090
+ <div
1091
+ className={cn(
1092
+ 'border-t border-ods-border bg-ods-bg/95 backdrop-blur-sm',
1093
+ fullWidth ? 'w-full' : 'mx-auto w-full max-w-ods-content-narrow',
1094
+ contentClassName ?? 'px-[var(--spacing-system-m)]',
1095
+ )}
1096
+ >
1097
+ <ChatMessageEnhanced
1098
+ role="assistant"
1099
+ name={assistantType === 'mingo' ? 'Mingo' : 'Fae'}
1100
+ content={pendingApprovals}
1101
+ timestamp={new Date()}
1102
+ showAvatar={showAvatars}
1103
+ assistantType={assistantType}
1104
+ approvalVariant={approvalVariant}
1105
+ assistantIcon={assistantIcon}
1106
+ NavLinkAnchor={NavLinkAnchor}
1107
+ className="py-3"
1108
+ />
1109
+ </div>
1110
+ )}
1111
+ </div>
1112
+ </OverlayOpenRegistryProvider>
1118
1113
  );
1119
1114
  },
1120
1115
  );
@@ -3,11 +3,9 @@
3
3
  import { forwardRef, useState } from 'react';
4
4
  import { cn } from '../../utils/cn';
5
5
  import { AlertCircleIcon } from '../icons-v2-generated';
6
- import { useChatAppearance } from './chat-appearance-context';
7
6
  import { ExpandChevron } from './expand-chevron';
8
7
  import { useCollapsible } from './hooks/use-collapsible';
9
8
  import type { ErrorMessageDisplayProps } from './types';
10
- import { CHAT_APPEARANCE } from './types/chat.types';
11
9
 
12
10
  const iconTint = {
13
11
  error: 'text-ods-error',
@@ -20,20 +18,11 @@ const ErrorMessageDisplay = forwardRef<HTMLDivElement, ErrorMessageDisplayProps>
20
18
  const [expanded, setExpanded] = useState(false);
21
19
  const { innerRef, containerStyle } = useCollapsible({ expanded });
22
20
  const hasDetails = Boolean(details);
23
- // v2 (fae chat `chat-info-block`): outlined on the page surface, the title
24
- // always reads as a heading, and the row spacing comes from the thread.
25
- const isV2 = useChatAppearance() === CHAT_APPEARANCE.V2;
26
21
 
27
22
  return (
28
23
  <div
29
24
  ref={ref}
30
- className={cn(
31
- 'rounded-md',
32
- isV2
33
- ? 'border border-ods-border bg-ods-bg p-[var(--spacing-system-xs)]'
34
- : 'mb-[var(--spacing-system-xsf)] bg-ods-card p-[var(--spacing-system-xsf)]',
35
- className,
36
- )}
25
+ className={cn('mb-[var(--spacing-system-xsf)] rounded-md bg-ods-card p-[var(--spacing-system-xsf)]', className)}
37
26
  {...props}
38
27
  >
39
28
  <button
@@ -51,8 +40,7 @@ const ErrorMessageDisplay = forwardRef<HTMLDivElement, ErrorMessageDisplayProps>
51
40
  <span
52
41
  className={cn(
53
42
  'min-w-0 flex-1 text-h5',
54
- expanded || isV2 ? 'text-ods-text-primary' : 'text-ods-text-secondary',
55
- !expanded && 'truncate',
43
+ expanded ? 'text-ods-text-primary' : 'truncate text-ods-text-secondary',
56
44
  )}
57
45
  >
58
46
  {title}
@@ -62,13 +50,7 @@ const ErrorMessageDisplay = forwardRef<HTMLDivElement, ErrorMessageDisplayProps>
62
50
 
63
51
  {hasDetails && (
64
52
  <div style={containerStyle}>
65
- <div
66
- ref={innerRef}
67
- className={cn(
68
- 'px-[var(--spacing-system-lf)]',
69
- isV2 ? 'pt-[var(--spacing-system-xs)]' : 'pt-[var(--spacing-system-xsf)]',
70
- )}
71
- >
53
+ <div ref={innerRef} className="px-[var(--spacing-system-lf)] pt-[var(--spacing-system-xsf)]">
72
54
  <p className="text-ods-text-primary text-h6">{details}</p>
73
55
  </div>
74
56
  </div>
@@ -6,9 +6,7 @@ export * from './approval-batch-message';
6
6
  export * from './escalation-offer-message';
7
7
  export * from './ticket-escalated-message';
8
8
  export * from './ticket-event-message';
9
- export * from './system-event-message';
10
9
  export * from './ai-assistant-info';
11
- export * from './chat-appearance-context';
12
10
  export * from './context-compaction-display';
13
11
  export * from './expand-chevron';
14
12
  export * from './thinking-display';
@@ -49,10 +47,6 @@ export * from './context-items-list';
49
47
  export * from './chat-archive-page';
50
48
  export * from './model-display';
51
49
  export * from './chat-sidebar';
52
- export * from './chat-nav-sidebar';
53
- export * from './chat-list-empty-state';
54
- export * from './chat-top-navigation';
55
- export * from './onboarding-carousel';
56
50
  export type { ChatRef } from './chat-ref.types';
57
51
  export { remarkCardLinks } from './remark-card-links';
58
52
  export { remarkMentionChips } from './remark-mention-chips';
@@ -22,12 +22,6 @@ export interface MspOrganizationCardProps {
22
22
  href?: string;
23
23
  /** Click handler for the trailing action (used when `href` is not set). */
24
24
  onOpenWebsite?: () => void;
25
- /**
26
- * `card` (default): the welcome-screen card, title with the website beneath.
27
- * `footer`: the compact sidebar footer of the fae chat v2 layout, a caption
28
- * "Your IT is managed by" over the name, no website line, no card chrome.
29
- */
30
- variant?: 'card' | 'footer';
31
25
  /** Appended to the root element. */
32
26
  className?: string;
33
27
  }
@@ -50,39 +44,10 @@ export function MspOrganizationCard({
50
44
  title,
51
45
  href,
52
46
  onOpenWebsite,
53
- variant = 'card',
54
47
  className,
55
48
  }: MspOrganizationCardProps) {
56
49
  const actionLabel = website ? `Open ${website}` : 'Open organization website';
57
50
 
58
- if (variant === 'footer') {
59
- return (
60
- <div
61
- className={cn(
62
- 'flex items-center gap-[var(--spacing-system-s)] px-[var(--spacing-system-m)] py-[var(--spacing-system-s)]',
63
- className,
64
- )}
65
- >
66
- <SquareAvatar src={logoUrl} alt={name} fallback={name} size="sm" variant="square" />
67
- <div className="flex min-w-0 flex-1 flex-col">
68
- <span className="truncate text-ods-text-secondary text-h6">Your IT is managed by</span>
69
- <span className="truncate text-ods-text-primary text-h4">{title ?? name}</span>
70
- </div>
71
- {href ? (
72
- <Button asChild variant="outline" size="icon-sm" aria-label={actionLabel}>
73
- <a href={href} target="_blank" rel="noreferrer noopener">
74
- <ExternalLinkIcon />
75
- </a>
76
- </Button>
77
- ) : onOpenWebsite ? (
78
- <Button variant="outline" size="icon-sm" aria-label={actionLabel} onClick={onOpenWebsite}>
79
- <ExternalLinkIcon />
80
- </Button>
81
- ) : null}
82
- </div>
83
- );
84
- }
85
-
86
51
  return (
87
52
  <div
88
53
  className={cn(
@@ -81,21 +81,6 @@ export const APPROVAL_BLOCK_VARIANT = {
81
81
 
82
82
  export type ApprovalBlockVariant = (typeof APPROVAL_BLOCK_VARIANT)[keyof typeof APPROVAL_BLOCK_VARIANT];
83
83
 
84
- /**
85
- * Visual generation of a chat thread. `v2` is the fae chat v2 design: author
86
- * names without the trailing colon, a 24px assistant avatar, 16px between
87
- * messages, and in-thread info/error blocks on the page surface. `classic`
88
- * (default) is everything that predates it. The Fae client opts in first;
89
- * Mingo and the admin views stay classic until their own redesign lands, then
90
- * `classic` goes away.
91
- */
92
- export const CHAT_APPEARANCE = {
93
- CLASSIC: 'classic',
94
- V2: 'v2',
95
- } as const;
96
-
97
- export type ChatAppearance = (typeof CHAT_APPEARANCE)[keyof typeof CHAT_APPEARANCE];
98
-
99
84
  // ========== Connection Status Definitions ==========
100
85
 
101
86
  export const CONNECTION_STATUS = {
@@ -3,14 +3,12 @@
3
3
  */
4
4
 
5
5
  import type { ComponentType, HTMLAttributes, ReactNode, TextareaHTMLAttributes } from 'react';
6
- import type { AvatarStackPerson } from '../../ui/avatar-stack';
7
6
  import type { ChatRef } from '../chat-ref.types';
8
7
  import type { MspOrganizationCardProps } from '../msp-organization-card';
9
8
  import type {
10
9
  ApprovalBlockVariant,
11
10
  AssistantType,
12
11
  AuthorType,
13
- ChatAppearance,
14
12
  ChatApprovalStatus,
15
13
  ConnectionStatus,
16
14
  } from './chat.types';
@@ -143,9 +141,6 @@ export interface ChatMessageEnhancedProps extends Omit<HTMLAttributes<HTMLDivEle
143
141
  * (Fae desktop app) title-only card. Forwarded to
144
142
  * ApprovalRequestMessage / ApprovalBatchMessage. */
145
143
  approvalVariant?: ApprovalBlockVariant;
146
- /** Visual generation of the row. Omit to inherit the thread's
147
- * (`ChatMessageList.appearance`); `classic` outside a list. */
148
- appearance?: ChatAppearance;
149
144
  assistantIcon?: ReactNode;
150
145
  avatar?: string | null;
151
146
  timestamp?: Date;
@@ -293,9 +288,6 @@ export interface ChatMessageListProps extends HTMLAttributes<HTMLDivElement> {
293
288
  * `'client'` ONLY on true end-client surfaces — admin views of a Fae
294
289
  * dialog (tickets dialog client tab) keep the default. */
295
290
  approvalVariant?: ApprovalBlockVariant;
296
- /** Visual generation of the whole thread, blocks included. Default
297
- * `classic`; the Fae client passes `v2`. See `ChatAppearance`. */
298
- appearance?: ChatAppearance;
299
291
  assistantIcon?: ReactNode;
300
292
  pendingApprovals?: MessageSegment[];
301
293
  onApprove?: ApprovalResolutionHandler;
@@ -450,19 +442,7 @@ export interface ChatInputProps extends Omit<TextareaHTMLAttributes<HTMLTextArea
450
442
  onSend?: (message: string) => void | boolean | Promise<boolean | void>;
451
443
  onStop?: () => void | Promise<void>;
452
444
  sending?: boolean;
453
- /** Replaces the editor with the hand-off bar: the conversation is with the
454
- * technicians now, and the client waits for one of them to reply. */
455
445
  awaitingResponse?: boolean;
456
- /** Faces shown in the v2 hand-off bar (the tenant's technicians). Omit or
457
- * pass an empty list for a text-only bar. */
458
- awaitingTeam?: AvatarStackPerson[];
459
- /** Size of the whole team when `awaitingTeam` is only a sample of it: the
460
- * bar shows those faces and "+N" for the rest. */
461
- awaitingTeamCount?: number;
462
- /** `v2`: the fae chat v2 hand-off bar ("Handed off to your technical
463
- * support team" + faces). `classic` (default): "Waiting for Technician
464
- * Response". Only the awaiting state differs. */
465
- appearance?: ChatAppearance;
466
446
  /** Same `fullWidth` semantics as `ChatHeaderProps.fullWidth` — drops
467
447
  * the default `max-w-ods-content-narrow` so the input fills the
468
448
  * parent. */