@flamingo-stack/openframe-frontend-core 0.0.587-1973.5528.1 → 0.0.587-2000.5532.1

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 (165) hide show
  1. package/dist/chat-protocol/events.d.ts +2 -8
  2. package/dist/chat-protocol/events.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +0 -188
  4. package/dist/chat-protocol/index.cjs.map +1 -1
  5. package/dist/chat-protocol/index.js +0 -188
  6. package/dist/chat-protocol/index.js.map +1 -1
  7. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
  8. package/dist/{chunk-5VCDP6UA.cjs → chunk-3FHWLQDG.cjs} +10 -10
  9. package/dist/{chunk-5VCDP6UA.cjs.map → chunk-3FHWLQDG.cjs.map} +1 -1
  10. package/dist/{chunk-RE3KVI2Q.js → chunk-3PDTGDOA.js} +5 -5
  11. package/dist/{chunk-5X7AJ67N.js → chunk-436G4DI4.js} +3 -3
  12. package/dist/{chunk-BLC4JOFC.cjs → chunk-4IIFSFJ3.cjs} +15 -15
  13. package/dist/{chunk-BLC4JOFC.cjs.map → chunk-4IIFSFJ3.cjs.map} +1 -1
  14. package/dist/{chunk-JR37UTA2.cjs → chunk-5O5JMGEN.cjs} +33 -33
  15. package/dist/{chunk-JR37UTA2.cjs.map → chunk-5O5JMGEN.cjs.map} +1 -1
  16. package/dist/{chunk-7A6J4JTW.js → chunk-6BFEJXMI.js} +3 -3
  17. package/dist/{chunk-S2UY2C4A.cjs → chunk-7WIKOL25.cjs} +10 -10
  18. package/dist/{chunk-S2UY2C4A.cjs.map → chunk-7WIKOL25.cjs.map} +1 -1
  19. package/dist/{chunk-DVMQV3M4.cjs → chunk-A5PXHMCJ.cjs} +69 -69
  20. package/dist/{chunk-DVMQV3M4.cjs.map → chunk-A5PXHMCJ.cjs.map} +1 -1
  21. package/dist/{chunk-RQ5PYSRJ.js → chunk-BLJEPNMJ.js} +4 -4
  22. package/dist/{chunk-4MVGFYRE.js → chunk-D7YISHXC.js} +190 -534
  23. package/dist/chunk-D7YISHXC.js.map +1 -0
  24. package/dist/{chunk-4S7GMJYA.cjs → chunk-DMQMRFGS.cjs} +21 -21
  25. package/dist/{chunk-4S7GMJYA.cjs.map → chunk-DMQMRFGS.cjs.map} +1 -1
  26. package/dist/{chunk-AIUIIGO3.cjs → chunk-DTQCZ6AR.cjs} +24 -24
  27. package/dist/{chunk-AIUIIGO3.cjs.map → chunk-DTQCZ6AR.cjs.map} +1 -1
  28. package/dist/{chunk-QKAUBYU5.cjs → chunk-E7NFHRLX.cjs} +3 -3
  29. package/dist/{chunk-QKAUBYU5.cjs.map → chunk-E7NFHRLX.cjs.map} +1 -1
  30. package/dist/{chunk-WFWWQO2I.js → chunk-F3YRPKUA.js} +2 -2
  31. package/dist/{chunk-EKKR5ZFB.cjs → chunk-G45RGWHF.cjs} +543 -887
  32. package/dist/chunk-G45RGWHF.cjs.map +1 -0
  33. package/dist/{chunk-ZRFSUCAH.js → chunk-HCWZP44O.js} +5 -3
  34. package/dist/{chunk-ZRFSUCAH.js.map → chunk-HCWZP44O.js.map} +1 -1
  35. package/dist/{chunk-NEI7XMCM.cjs → chunk-IGBNEPTD.cjs} +9 -9
  36. package/dist/{chunk-NEI7XMCM.cjs.map → chunk-IGBNEPTD.cjs.map} +1 -1
  37. package/dist/{chunk-3UJMZADA.cjs → chunk-KQ5JQVYL.cjs} +43 -43
  38. package/dist/{chunk-3UJMZADA.cjs.map → chunk-KQ5JQVYL.cjs.map} +1 -1
  39. package/dist/{chunk-FDWGO244.js → chunk-LODCKZ72.js} +2 -2
  40. package/dist/{chunk-ULOMJH7C.cjs → chunk-MF7TMFVB.cjs} +5 -5
  41. package/dist/{chunk-ULOMJH7C.cjs.map → chunk-MF7TMFVB.cjs.map} +1 -1
  42. package/dist/{chunk-DSXD77KX.cjs → chunk-MXCWOMNT.cjs} +3 -3
  43. package/dist/{chunk-DSXD77KX.cjs.map → chunk-MXCWOMNT.cjs.map} +1 -1
  44. package/dist/{chunk-E2QFZDCP.js → chunk-OKD7MX7Y.js} +5 -5
  45. package/dist/{chunk-OW3FUJ2I.js → chunk-P24OI2CD.js} +3 -3
  46. package/dist/{chunk-QSO24MEK.js → chunk-R2U2D7LU.js} +2 -2
  47. package/dist/{chunk-KQWI25NP.js → chunk-SI57FQZ2.js} +2 -2
  48. package/dist/{chunk-JQO47UHC.js → chunk-SVR75PFW.js} +5 -5
  49. package/dist/{chunk-UYHETUX3.cjs → chunk-UBSIENYF.cjs} +162 -160
  50. package/dist/chunk-UBSIENYF.cjs.map +1 -0
  51. package/dist/{chunk-OZRT3ZI3.js → chunk-V4WUSOGS.js} +3 -3
  52. package/dist/{chunk-UODM3HJC.js → chunk-VWNU2PFX.js} +3 -3
  53. package/dist/{chunk-SCVIBVFS.cjs → chunk-WQXWH7LF.cjs} +4 -4
  54. package/dist/{chunk-SCVIBVFS.cjs.map → chunk-WQXWH7LF.cjs.map} +1 -1
  55. package/dist/{chunk-7COFWOCJ.cjs → chunk-XNEPABUD.cjs} +22 -22
  56. package/dist/{chunk-7COFWOCJ.cjs.map → chunk-XNEPABUD.cjs.map} +1 -1
  57. package/dist/{chunk-QYDIVL4X.js → chunk-YB5DFQKD.js} +6 -6
  58. package/dist/components/case-studies/index.cjs +9 -9
  59. package/dist/components/case-studies/index.js +3 -3
  60. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  61. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  62. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  63. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  64. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +1 -4
  65. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  66. package/dist/components/chat/hooks/use-slash-commands.d.ts +3 -3
  67. package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
  68. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +28 -2
  69. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  70. package/dist/components/chat/index.cjs +2 -2
  71. package/dist/components/chat/index.js +1 -1
  72. package/dist/components/chat/remark-strip-citations.d.ts.map +1 -1
  73. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
  74. package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
  75. package/dist/components/chat/types/component.types.d.ts +0 -4
  76. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  77. package/dist/components/chat/types/message.types.d.ts +1 -35
  78. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  79. package/dist/components/chat/types/unified-chat-state.types.d.ts +3 -3
  80. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  81. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  82. package/dist/components/contact/index.cjs +4 -4
  83. package/dist/components/contact/index.js +3 -3
  84. package/dist/components/docs/index.cjs +7 -7
  85. package/dist/components/docs/index.js +6 -6
  86. package/dist/components/embeds/index.cjs +4 -4
  87. package/dist/components/embeds/index.js +3 -3
  88. package/dist/components/faq/index.cjs +4 -4
  89. package/dist/components/faq/index.js +3 -3
  90. package/dist/components/features/index.cjs +3 -3
  91. package/dist/components/features/index.js +2 -2
  92. package/dist/components/help-center-pages/index.cjs +23 -23
  93. package/dist/components/help-center-pages/index.js +14 -14
  94. package/dist/components/index.cjs +36 -36
  95. package/dist/components/index.js +13 -13
  96. package/dist/components/meeting-scheduler/index.cjs +21 -21
  97. package/dist/components/meeting-scheduler/index.js +2 -2
  98. package/dist/components/navigation/index.cjs +6 -6
  99. package/dist/components/navigation/index.js +5 -5
  100. package/dist/components/onboarding-guides/index.cjs +6 -6
  101. package/dist/components/onboarding-guides/index.js +5 -5
  102. package/dist/components/tickets/index.cjs +7 -7
  103. package/dist/components/tickets/index.js +6 -6
  104. package/dist/components/ui/data-table/use-data-table.d.ts.map +1 -1
  105. package/dist/components/ui/hover-dropdown.d.ts.map +1 -1
  106. package/dist/components/ui/index.cjs +3 -3
  107. package/dist/components/ui/index.js +2 -2
  108. package/dist/index.cjs +7 -7
  109. package/dist/index.js +6 -6
  110. package/dist/utils/index.cjs.map +1 -1
  111. package/dist/utils/index.js.map +1 -1
  112. package/package.json +1 -2
  113. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -130
  114. package/src/chat-protocol/events.ts +1 -9
  115. package/src/chat-protocol/nats-decoder.ts +0 -7
  116. package/src/components/chat/__tests__/chat-message-list.test.tsx +0 -70
  117. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +0 -86
  118. package/src/components/chat/chat-message-enhanced.tsx +13 -28
  119. package/src/components/chat/chat-message-list.tsx +22 -27
  120. package/src/components/chat/embeddable-chat.tsx +36 -55
  121. package/src/components/chat/entity-cards/dispatch.tsx +17 -86
  122. package/src/components/chat/hooks/use-nats-chat-adapter.ts +0 -7
  123. package/src/components/chat/hooks/use-slash-commands.ts +5 -9
  124. package/src/components/chat/hooks/use-sse-chat-adapter.ts +29 -2
  125. package/src/components/chat/remark-strip-citations.ts +3 -5
  126. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -56
  127. package/src/components/chat/stream/chat-stream-reducer.ts +0 -31
  128. package/src/components/chat/tool-execution-display.tsx +2 -5
  129. package/src/components/chat/types/component.types.ts +0 -4
  130. package/src/components/chat/types/message.types.ts +0 -39
  131. package/src/components/chat/types/unified-chat-state.types.ts +3 -4
  132. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +0 -57
  133. package/src/components/chat/utils/process-historical-messages.ts +1 -19
  134. package/src/components/ui/__tests__/data-table-compiler-identity.test.tsx +74 -0
  135. package/src/components/ui/data-table/use-data-table.ts +36 -1
  136. package/src/components/ui/hover-dropdown.tsx +3 -8
  137. package/dist/chat-protocol/source-metadata.d.ts +0 -5
  138. package/dist/chat-protocol/source-metadata.d.ts.map +0 -1
  139. package/dist/chunk-4MVGFYRE.js.map +0 -1
  140. package/dist/chunk-EKKR5ZFB.cjs.map +0 -1
  141. package/dist/chunk-UYHETUX3.cjs.map +0 -1
  142. package/dist/components/chat/utils/chat-source-metadata.d.ts +0 -5
  143. package/dist/components/chat/utils/chat-source-metadata.d.ts.map +0 -1
  144. package/src/chat-protocol/__tests__/source-metadata.test.ts +0 -74
  145. package/src/chat-protocol/source-metadata.ts +0 -202
  146. package/src/components/chat/__tests__/embeddable-chat-sources.test.tsx +0 -411
  147. package/src/components/chat/__tests__/remark-strip-citations.test.ts +0 -52
  148. package/src/components/chat/__tests__/tool-execution-display.test.tsx +0 -93
  149. package/src/components/chat/entity-cards/__tests__/dispatch-video-refs.test.tsx +0 -74
  150. package/src/components/chat/hooks/__tests__/use-slash-commands.test.ts +0 -64
  151. package/src/components/chat/utils/chat-source-metadata.ts +0 -19
  152. /package/dist/{chunk-RE3KVI2Q.js.map → chunk-3PDTGDOA.js.map} +0 -0
  153. /package/dist/{chunk-5X7AJ67N.js.map → chunk-436G4DI4.js.map} +0 -0
  154. /package/dist/{chunk-7A6J4JTW.js.map → chunk-6BFEJXMI.js.map} +0 -0
  155. /package/dist/{chunk-RQ5PYSRJ.js.map → chunk-BLJEPNMJ.js.map} +0 -0
  156. /package/dist/{chunk-WFWWQO2I.js.map → chunk-F3YRPKUA.js.map} +0 -0
  157. /package/dist/{chunk-FDWGO244.js.map → chunk-LODCKZ72.js.map} +0 -0
  158. /package/dist/{chunk-E2QFZDCP.js.map → chunk-OKD7MX7Y.js.map} +0 -0
  159. /package/dist/{chunk-OW3FUJ2I.js.map → chunk-P24OI2CD.js.map} +0 -0
  160. /package/dist/{chunk-QSO24MEK.js.map → chunk-R2U2D7LU.js.map} +0 -0
  161. /package/dist/{chunk-KQWI25NP.js.map → chunk-SI57FQZ2.js.map} +0 -0
  162. /package/dist/{chunk-JQO47UHC.js.map → chunk-SVR75PFW.js.map} +0 -0
  163. /package/dist/{chunk-OZRT3ZI3.js.map → chunk-V4WUSOGS.js.map} +0 -0
  164. /package/dist/{chunk-UODM3HJC.js.map → chunk-VWNU2PFX.js.map} +0 -0
  165. /package/dist/{chunk-QYDIVL4X.js.map → chunk-YB5DFQKD.js.map} +0 -0
@@ -808,14 +808,13 @@ function SourceChips({
808
808
  const [expanded, setExpanded] = useState(false);
809
809
 
810
810
  if (cited.length === 0) {
811
- const visible = expanded ? uncited : uncited.slice(0, FALLBACK_TOP_RETRIEVED);
812
- const hiddenCount = Math.max(0, uncited.length - FALLBACK_TOP_RETRIEVED);
813
- if (visible.length === 0) return null;
811
+ const fallback = uncited.slice(0, FALLBACK_TOP_RETRIEVED);
812
+ if (fallback.length === 0) return null;
814
813
  return (
815
814
  <div className="mt-2 flex flex-col gap-1.5 border-t border-ods-border pt-2">
816
815
  <span className="uppercase text-ods-text-muted text-h6">Top retrieved sources</span>
817
- <div className={`flex flex-wrap gap-1.5 ${expanded ? 'max-h-[200px] overflow-y-auto overscroll-contain' : ''}`}>
818
- {visible.map(src => (
816
+ <div className="flex flex-wrap gap-1.5">
817
+ {fallback.map(src => (
819
818
  <SourceChip
820
819
  key={src.index}
821
820
  src={src}
@@ -825,16 +824,6 @@ function SourceChips({
825
824
  onDiscuss={onDiscuss}
826
825
  />
827
826
  ))}
828
- {hiddenCount > 0 && (
829
- <button
830
- onClick={() => setExpanded(!expanded)}
831
- className="inline-flex cursor-pointer items-center gap-1 rounded border border-ods-accent bg-ods-card px-2 py-0.5 text-ods-accent transition-colors text-h6 hover:bg-ods-accent/10"
832
- aria-expanded={expanded}
833
- aria-label={expanded ? 'Show fewer sources' : `Show ${hiddenCount} additional retrieved sources`}
834
- >
835
- {expanded ? 'Show less' : `+${hiddenCount} more retrieved ${hiddenCount === 1 ? 'source' : 'sources'}`}
836
- </button>
837
- )}
838
827
  </div>
839
828
  </div>
840
829
  );
@@ -1590,10 +1579,6 @@ function EmbeddableChatInner({
1590
1579
  // explicitly — see `Message.hidden` and `chat-message-list`'s skip.
1591
1580
  ...(m.hidden ? { hidden: true } : {}),
1592
1581
  ...(m.scrollAnchor ? { scrollAnchor: m.scrollAnchor } : {}),
1593
- // Preserve same-turn rich metadata. Source chips and entity-card
1594
- // renderers consume it after this raw-to-Message projection.
1595
- ...(m.sources && m.sources.length > 0 ? { sources: m.sources } : {}),
1596
- ...(m.refs && m.refs.length > 0 ? { refs: m.refs } : {}),
1597
1582
  // Forward attached context items so the user bubble renders its chips.
1598
1583
  ...(m.contextItems && m.contextItems.length > 0 ? { contextItems: m.contextItems } : {}),
1599
1584
  };
@@ -1844,41 +1829,24 @@ function EmbeddableChatInner({
1844
1829
  return out;
1845
1830
  }, [commandsById, enabledSet]);
1846
1831
 
1847
- const renderSourcesAfterMessage = useCallback(
1848
- (message: Message, index: number) => {
1849
- if (message.role !== 'assistant' || !message.sources?.length) return null;
1850
- if (chatLoading && index === messages.length - 1) return null;
1851
- const content =
1852
- typeof message.content === 'string'
1853
- ? message.content
1854
- : message.content
1855
- .filter(segment => segment.type === 'text')
1856
- .map(segment => segment.text)
1857
- .join('\n');
1858
- const citationOrder = [...content.matchAll(/\[(\d+)\]/g)].map(m => parseInt(m[1], 10));
1859
- const seenOrder = new Map<number, number>();
1860
- citationOrder.forEach(idx => {
1861
- if (!seenOrder.has(idx)) seenOrder.set(idx, seenOrder.size);
1862
- });
1863
- const cited = message.sources
1864
- .filter(s => seenOrder.has(s.index))
1865
- .sort((a, b) => (seenOrder.get(a.index) ?? 0) - (seenOrder.get(b.index) ?? 0));
1866
- const uncited = message.sources.filter(s => !seenOrder.has(s.index));
1867
- return (
1868
- <div className="flex-shrink-0 pb-2">
1869
- <SourceChips
1870
- cited={cited}
1871
- uncited={uncited}
1872
- baseRoute={resolvedBaseRoute}
1873
- chipBasePlatform={chipBasePlatform}
1874
- onClose={handleNavigationClose}
1875
- onDiscuss={discussRef}
1876
- />
1877
- </div>
1878
- );
1879
- },
1880
- [chatLoading, messages.length, resolvedBaseRoute, chipBasePlatform, handleNavigationClose, discussRef],
1881
- );
1832
+ // Find sources for the last assistant message; split into cited / uncited.
1833
+ const lastAssistantMsg = [...rawMessages].reverse().find(m => m.role === 'assistant');
1834
+ const lastSources = useMemo(() => {
1835
+ if (chatLoading) return undefined;
1836
+ const sources = lastAssistantMsg?.sources;
1837
+ if (!sources || sources.length === 0) return undefined;
1838
+ const content = lastAssistantMsg?.content || '';
1839
+ const citationOrder = [...content.matchAll(/\[(\d+)\]/g)].map(m => parseInt(m[1], 10));
1840
+ const seenOrder = new Map<number, number>();
1841
+ citationOrder.forEach(idx => {
1842
+ if (!seenOrder.has(idx)) seenOrder.set(idx, seenOrder.size);
1843
+ });
1844
+ const cited = sources
1845
+ .filter(s => seenOrder.has(s.index))
1846
+ .sort((a, b) => (seenOrder.get(a.index) ?? 0) - (seenOrder.get(b.index) ?? 0));
1847
+ const uncited = sources.filter(s => !seenOrder.has(s.index));
1848
+ return { cited, uncited };
1849
+ }, [lastAssistantMsg, chatLoading]);
1882
1850
 
1883
1851
  // Host node for in-panel Radix portals (see the body wrapper below).
1884
1852
  const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null);
@@ -2422,7 +2390,6 @@ function EmbeddableChatInner({
2422
2390
  resolveContextIcon={resolveContextIcon}
2423
2391
  renderContextItem={renderContextItem}
2424
2392
  renderMention={renderMention}
2425
- renderAfterMessage={renderSourcesAfterMessage}
2426
2393
  // Gated on `chatLoading` for the same reason the composer
2427
2394
  // is: no second send while a turn is in flight. Passive
2428
2395
  // demo hosts (previewMode) stay read-only.
@@ -2460,6 +2427,20 @@ function EmbeddableChatInner({
2460
2427
  onLoadMore={loadMoreMessages}
2461
2428
  />
2462
2429
  )}
2430
+ {lastSources &&
2431
+ (lastSources.cited.length > 0 || lastSources.uncited.length > 0) &&
2432
+ !chatLoading && (
2433
+ <div className="flex-shrink-0 pb-2">
2434
+ <SourceChips
2435
+ cited={lastSources.cited}
2436
+ uncited={lastSources.uncited}
2437
+ baseRoute={resolvedBaseRoute}
2438
+ chipBasePlatform={chipBasePlatform}
2439
+ onClose={handleNavigationClose}
2440
+ onDiscuss={discussRef}
2441
+ />
2442
+ </div>
2443
+ )}
2463
2444
  </div>
2464
2445
  ) : activeMode === 'mingo' ? (
2465
2446
  /* Figma node 7532:222444 — default (Mingo-mode) empty state:
@@ -30,7 +30,6 @@
30
30
  */
31
31
 
32
32
  import React, { type ReactNode } from 'react';
33
- import { isSourceMetadataCardRef } from '../../../chat-protocol/source-metadata';
34
33
  import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context';
35
34
  import Image from '../../../embed-shims/next-image';
36
35
  import { useRouter } from '../../../embed-shims/next-navigation';
@@ -42,7 +41,6 @@ import {
42
41
  formatTimeWithTimezone,
43
42
  formatDurationFromRange,
44
43
  } from '../../../utils/format';
45
- import { extractYouTubeId } from '../../features/video';
46
44
  import { MingoIcon } from '../../icons';
47
45
  import { ArrowRightUpIcon } from '../../icons-v2-generated/arrows/arrow-right-up-icon';
48
46
  import { ClickupLogoIcon } from '../../icons-v2-generated/brand-logos/clickup-logo-icon';
@@ -424,15 +422,6 @@ function hubspotStatusToVariant(status: string | undefined): MingoInfoCardStatus
424
422
  * which renders the marker as plain text (same anti-hallucination
425
423
  * posture as every other card type).
426
424
  */
427
- function validatedHttpsUrl(value: unknown): string | null {
428
- if (typeof value !== 'string') return null;
429
- try {
430
- return new URL(value).protocol === 'https:' ? value : null;
431
- } catch {
432
- return null;
433
- }
434
- }
435
-
436
425
  function decodeVideoMarkerId(id: string): { videoUrl?: string; youtubeUrl?: string } | null {
437
426
  // Prefix checks FIRST: an id like `mux-1234567` (11 chars) would also
438
427
  // satisfy the bare-YouTube-id pattern below.
@@ -450,8 +439,8 @@ function decodeVideoMarkerId(id: string): { videoUrl?: string; youtubeUrl?: stri
450
439
  const url = atob(b64);
451
440
  // https-only — the id is model-emitted text; never let a marker
452
441
  // smuggle a non-https src into a player.
453
- const safeUrl = validatedHttpsUrl(url);
454
- return safeUrl ? { videoUrl: safeUrl } : null;
442
+ if (!/^https:\/\//.test(url)) return null;
443
+ return { videoUrl: url };
455
444
  } catch {
456
445
  return null;
457
446
  }
@@ -460,29 +449,6 @@ function decodeVideoMarkerId(id: string): { videoUrl?: string; youtubeUrl?: stri
460
449
  return null;
461
450
  }
462
451
 
463
- function validatedVideoMetadata(chatRef: ChatRef): Record<string, unknown> | null {
464
- const input = chatRef.metadata ?? {};
465
- const metadata: Record<string, unknown> = {};
466
- for (const key of ['videoUrl', 'highlightVideoUrl', 'videoPoster', 'highlightVideoPoster'] as const) {
467
- const url = validatedHttpsUrl(input[key]);
468
- if (url) metadata[key] = url;
469
- }
470
- const youtubeUrl = input.youtubeUrl;
471
- if (typeof youtubeUrl === 'string' && /^[A-Za-z0-9_-]{11}$/.test(youtubeUrl)) {
472
- metadata.youtubeUrl = youtubeUrl;
473
- }
474
-
475
- const safeUrl = validatedHttpsUrl(chatRef.url);
476
- if (safeUrl) {
477
- const youtubeId = extractYouTubeId(safeUrl);
478
- delete metadata.videoUrl;
479
- delete metadata.youtubeUrl;
480
- metadata[youtubeId ? 'youtubeUrl' : 'videoUrl'] = youtubeId ?? safeUrl;
481
- }
482
-
483
- return Object.keys(metadata).length > 0 ? metadata : null;
484
- }
485
-
486
452
  /** Read hero-video metadata off a FETCHED item — the API-driven
487
453
  * replacement for the retired refs-frame `metadata.videoUrl` check. Two item
488
454
  * shapes: ChatRef-shaped items (per-object hydration routes) carry the
@@ -1262,10 +1228,6 @@ interface ChatCardRegistryEntry {
1262
1228
  * point — a host that re-homes a type must be able to say so even when
1263
1229
  * the row already carries the content host's own url. */
1264
1230
  noComposedHref?: boolean;
1265
- /** Render a document card from same-turn SOURCES metadata when its optional
1266
- * hydration endpoint has no row. Only doc refs opt in: their source record
1267
- * already carries the complete title/path/navigation fallback. */
1268
- renderFromSourceMetadata?: boolean;
1269
1231
  }
1270
1232
 
1271
1233
  /**
@@ -1288,7 +1250,6 @@ function refHydratedEntry(
1288
1250
  docType: string,
1289
1251
  label: string,
1290
1252
  renderRef: (displayRef: ChatRef, opts: ChatCardRenderOptions) => React.ReactNode,
1291
- renderFromSourceMetadata = false,
1292
1253
  ): ChatCardRegistryEntry {
1293
1254
  return {
1294
1255
  label,
@@ -1298,19 +1259,6 @@ function refHydratedEntry(
1298
1259
  fallbackHref: (item: { url?: string | null }) => item?.url ?? null,
1299
1260
  skeleton: () => <MingoInfoCardSkeleton />,
1300
1261
  render: (item, chatRef, opts) => renderRef(fetchedItemDisplayRef(item, chatRef), opts),
1301
- renderFromSourceMetadata,
1302
- };
1303
- }
1304
-
1305
- function sourceMetadataCardItem(chatRef: ChatRef): ChatCardItem {
1306
- return {
1307
- type: chatRef.type,
1308
- id: chatRef.id,
1309
- title: chatRef.title,
1310
- url: chatRef.url,
1311
- targetPlatform: chatRef.targetPlatform,
1312
- sourceRepo: chatRef.sourceRepo,
1313
- metadata: chatRef.metadata,
1314
1262
  };
1315
1263
  }
1316
1264
 
@@ -1506,18 +1454,12 @@ const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
1506
1454
  hubspot_ticket_self: refHydratedEntry('hubspot_ticket_self', 'HubSpot ticket (self)', (displayRef, opts) => (
1507
1455
  <HubspotTicketChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
1508
1456
  )),
1509
- data_room_doc: refHydratedEntry(
1510
- 'data_room_doc',
1511
- 'Data-room doc',
1512
- (displayRef, opts) => <DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />,
1513
- true,
1514
- ),
1515
- markdown: refHydratedEntry(
1516
- 'markdown',
1517
- 'Doc page (markdown)',
1518
- (displayRef, opts) => <DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />,
1519
- true,
1520
- ),
1457
+ data_room_doc: refHydratedEntry('data_room_doc', 'Data-room doc', (displayRef, opts) => (
1458
+ <DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
1459
+ )),
1460
+ markdown: refHydratedEntry('markdown', 'Doc page (markdown)', (displayRef, opts) => (
1461
+ <DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
1462
+ )),
1521
1463
  // Body-synthesized video ids (`shortVideoId` content hashes / YouTube
1522
1464
  // ids) have no backing table row to fetch — ref-only by nature.
1523
1465
  video: {
@@ -1960,13 +1902,6 @@ export function ChatCardLoader({
1960
1902
  }
1961
1903
  if (isLoading) return <>{fetchEntry.skeleton?.() ?? null}</>;
1962
1904
  if (!item) {
1963
- const hasSourceMetadata =
1964
- fetchEntry.renderFromSourceMetadata &&
1965
- isSourceMetadataCardRef(chatRef) &&
1966
- typeof finalChatRef.metadata?.path === 'string';
1967
- if (hasSourceMetadata) {
1968
- return finish(fetchEntry.render(sourceMetadataCardItem(finalChatRef), finalChatRef, renderOpts));
1969
- }
1970
1905
  // FAIL LOUD (explicit product decision 2026-08-13): a card that
1971
1906
  // cannot hydrate renders a VISIBLE error card — silent removal made
1972
1907
  // broken answers look fine. Three distinguishable states:
@@ -2048,28 +1983,24 @@ export function renderChatInlineEntityCard(
2048
1983
  />
2049
1984
  );
2050
1985
 
2051
- // SOURCES metadata may enrich an opaque video marker with its playable URL.
2052
- // Self-describing marker ids remain transport-independent through
2053
- // `decodeVideoMarkerId`; undecodable ids without matching metadata fall
2054
- // through to the loader and degrade to plain text.
1986
+ // Embedded videos are SELF-DESCRIBING: the marker id alone recovers the
1987
+ // playable URL (`decodeVideoMarkerId`), so the player renders on ANY
1988
+ // transport with zero refs/fetch. Undecodable ids (legacy sha1 hashes,
1989
+ // hallucinations) fall through to the loader → plain-text degrade.
2055
1990
  // Hero videos on FETCHED entity types (webinars, releases, …) are
2056
1991
  // handled post-fetch inside ChatCardLoader (`itemVideoMetadata`) — the
2057
- // marker metadata is not required for that post-fetch path.
1992
+ // marker's ref plays no part in either path.
2058
1993
  if (chatRef.type === 'video') {
2059
- const enrichedMetadata = validatedVideoMetadata(chatRef);
2060
1994
  const decoded = decodeVideoMarkerId(chatRef.id);
2061
- const playableMetadata = enrichedMetadata ?? decoded;
2062
- if (playableMetadata) {
1995
+ if (decoded) {
2063
1996
  const displayRef: ChatRef = {
2064
1997
  ...chatRef,
2065
1998
  title: chatRef.title && chatRef.title !== chatRef.id ? chatRef.title : 'Video',
2066
1999
  url:
2067
2000
  chatRef.url ??
2068
- (typeof playableMetadata.videoUrl === 'string' ? playableMetadata.videoUrl : null) ??
2069
- (typeof playableMetadata.youtubeUrl === 'string'
2070
- ? `https://www.youtube.com/watch?v=${playableMetadata.youtubeUrl}`
2071
- : null),
2072
- metadata: playableMetadata,
2001
+ decoded.videoUrl ??
2002
+ (decoded.youtubeUrl ? `https://www.youtube.com/watch?v=${decoded.youtubeUrl}` : null),
2003
+ metadata: { ...(chatRef.metadata ?? {}), ...decoded },
2073
2004
  };
2074
2005
  return (
2075
2006
  <BlockCard inline={<ChatInlineVideoPill chatRef={displayRef} />}>
@@ -61,7 +61,6 @@ import type {
61
61
  MessageSegment,
62
62
  NatsMessageType,
63
63
  ChatApprovalStatus,
64
- ChatSource,
65
64
  } from '../types';
66
65
  import type { DialogItem } from '../types/component.types';
67
66
  import type {
@@ -353,8 +352,6 @@ export function mapProcessedToUnified(
353
352
  role: 'user' | 'assistant' | 'error';
354
353
  content: string | MessageSegment[];
355
354
  name?: string;
356
- sources?: ChatSource[];
357
- refs?: ChatRef[];
358
355
  }>,
359
356
  ): UnifiedChatMessage[] {
360
357
  const out: UnifiedChatMessage[] = [];
@@ -366,16 +363,12 @@ export function mapProcessedToUnified(
366
363
  role: m.role,
367
364
  content: '',
368
365
  segments: m.content,
369
- ...(m.sources ? { sources: m.sources } : {}),
370
- ...(m.refs ? { refs: m.refs } : {}),
371
366
  });
372
367
  } else {
373
368
  out.push({
374
369
  id: m.id,
375
370
  role: m.role,
376
371
  content: m.content,
377
- ...(m.sources ? { sources: m.sources } : {}),
378
- ...(m.refs ? { refs: m.refs } : {}),
379
372
  });
380
373
  }
381
374
  }
@@ -24,12 +24,10 @@ import type { SlashCommandActionId, SlashCommandSummaryAction, SlashCommandSumma
24
24
  // canonical declaration.
25
25
  export type { SlashCommandActionId, SlashCommandSummaryAction, SlashCommandSummary };
26
26
 
27
- const COMMANDS_MAX_RESULTS = 10;
28
-
29
27
  /**
30
- * Fetch the full slash-command registry for the CURRENT deployment platform,
31
- * then match id-prefix `prefix` locally. The chat source is server-resolved —
32
- * the client sends NO `source` param on the URL.
28
+ * Fetch slash-command suggestions for the CURRENT deployment platform,
29
+ * matching id-prefix `prefix`. The chat source is server-resolved — the
30
+ * client sends NO `source` param on the URL.
33
31
  *
34
32
  * `commandsUrl` is required — supplied by `useChatRuntime().endpoints.commandsUrl`
35
33
  * at the call site. NO default value.
@@ -58,15 +56,13 @@ export async function fetchSlashCommands(
58
56
  ): Promise<SlashCommandSummary[]> {
59
57
  try {
60
58
  const url = new URL(commandsUrl, window.location.origin);
59
+ if (prefix) url.searchParams.set('q', prefix);
61
60
  // `headers: {}` opts out of the default `Content-Type: application/json`
62
61
  // — this is a bare GET with no body, so no content-type is needed.
63
62
  const res = await embedAuthedFetch(url.toString(), { signal, headers: {} });
64
63
  if (!res.ok) return [];
65
64
  const data = (await res.json()) as { commands?: SlashCommandSummary[] };
66
- const commands = data.commands ?? [];
67
- const normalizedPrefix = prefix.trim().toLowerCase();
68
- if (!normalizedPrefix) return commands;
69
- return commands.filter(({ id }) => id.startsWith(normalizedPrefix)).slice(0, COMMANDS_MAX_RESULTS);
65
+ return data.commands ?? [];
70
66
  } catch (err) {
71
67
  // Cancellation (unmount / dep change) MUST propagate so react-query treats
72
68
  // it as cancelled, not as a successful empty result. Every OTHER failure
@@ -63,7 +63,7 @@ import type { ChatDialogStore } from '../stream/chat-dialog-store';
63
63
  import type { ChatStreamReducerOptions } from '../stream/chat-stream-reducer';
64
64
  import { useChatStreamReducer } from '../stream/use-chat-stream-reducer';
65
65
  import type { DialogItem } from '../types/component.types';
66
- import type { ChatSource, Message, MessageSegment } from '../types/message.types';
66
+ import type { Message, MessageSegment } from '../types/message.types';
67
67
  import type {
68
68
  UnifiedChatState,
69
69
  UnifiedChatMessage,
@@ -81,7 +81,6 @@ import { useSlashCommandRegistry, type SlashCommandSummary } from './use-slash-c
81
81
  // Canonical home of the per-turn meta row moved to the stream module in
82
82
  // Phase 3; re-exported here to keep the legacy import path stable.
83
83
  export type { ChatTurnMeta } from '../stream/chat-stream-reducer';
84
- export type { ChatSource } from '../types/message.types';
85
84
 
86
85
  // ─── Stable no-op references for the Guide-mode dialog-management stubs ──
87
86
  // Plain module-scope constants so the adapter's return identity stays
@@ -129,6 +128,34 @@ const noopBumpMetaTick = (): void => {};
129
128
  * hub-side platform-utils, not in lib). */
130
129
  export type DocSource = string;
131
130
 
131
+ export interface ChatSource {
132
+ index: number;
133
+ name: string;
134
+ path: string;
135
+ documentType: string;
136
+ externalUrl?: string;
137
+ /** Platform that owns the destination at `externalUrl`. */
138
+ targetPlatform?: string | null;
139
+ /** Primary-key value for single-row chips. */
140
+ id?: string;
141
+ /** Per-row items for grouped chips. */
142
+ items?: Array<{
143
+ id: string;
144
+ documentType: string;
145
+ name: string;
146
+ externalUrl?: string;
147
+ targetPlatform?: string | null;
148
+ /** In-app doc-tree path for markdown / data-room-doc rows so the
149
+ * grouped chip's anchor can trigger an in-page doc-tree swap via
150
+ * `handleChatNavClick` (parity with single-row chips + cards). */
151
+ path?: string | null;
152
+ }>;
153
+ /** RagTableConfig.id for this source. */
154
+ sourceRepo?: string;
155
+ /** Optional display label override returned by the chat API. */
156
+ label?: string;
157
+ }
158
+
132
159
  export interface DocChatMessage {
133
160
  id: string;
134
161
  role: 'user' | 'assistant';
@@ -22,16 +22,13 @@ import type { Plugin } from 'unified';
22
22
  import { visit } from 'unist-util-visit';
23
23
 
24
24
  const CITATION_REGEX = /(^|\s)\[\d{1,2}(?:\s*,\s*\d{1,2})*\](?=[\s.,;:!?)\]]|$)/g;
25
- const CITATION_LIST_REGEX =
26
- /(^|\s)(?:\[\d{1,2}(?:\s*,\s*\d{1,2})*\]\s*,\s*)+(?:(?:and|or)\s+)?\[\d{1,2}(?:\s*,\s*\d{1,2})*\](?=[\s.,;:!?)\]]|$)/g;
27
25
 
28
26
  export const remarkStripCitations: Plugin<[], Root> = () => {
29
27
  return (tree: Root) => {
30
28
  visit(tree, 'text', (node: Text, index, parent) => {
31
29
  if (!parent || typeof index !== 'number') return;
32
30
  if (!node.value || !node.value.includes('[')) return;
33
- const withoutCitationLists = node.value.replace(CITATION_LIST_REGEX, '$1');
34
- const withoutCitations = withoutCitationLists.replace(CITATION_REGEX, '$1');
31
+ const withoutCitations = node.value.replace(CITATION_REGEX, '$1');
35
32
  // No citation matched → leave the node untouched, INCLUDING its
36
33
  // whitespace (collapsing unconditionally would rewrite deliberate
37
34
  // spacing in prose like `array[1] value`).
@@ -44,7 +41,8 @@ export const remarkStripCitations: Plugin<[], Root> = () => {
44
41
  // leaf and moves on to the next sibling, never re-entering this slot.
45
42
  parent.children.splice(index, 1, {
46
43
  ...node,
47
- value: withoutCitations.replace(/\s+([.,;:!?])/g, '$1').replace(/ {2,}/g, ' '),
44
+ // Collapse doubled spaces the removal can leave mid-sentence.
45
+ value: withoutCitations.replace(/ {2,}/g, ' '),
48
46
  });
49
47
  });
50
48
  };
@@ -1289,59 +1289,3 @@ describe('createChatStreamReducer — ask cards', () => {
1289
1289
  expect(last.segments?.map(s => s.type)).toEqual(['ask', 'ask']);
1290
1290
  });
1291
1291
  });
1292
-
1293
- describe('createChatStreamReducer — Mingo source metadata', () => {
1294
- const sources = [
1295
- {
1296
- index: 1,
1297
- name: 'Enroll devices',
1298
- path: 'docs/enroll.md',
1299
- documentType: 'markdown',
1300
- },
1301
- ];
1302
-
1303
- it('attaches metadata received before text to the same assistant turn', () => {
1304
- const reducer = createChatStreamReducer({ transport: 'nats' });
1305
- const refs = [
1306
- {
1307
- type: 'video',
1308
- id: 'mux-9b6586b494',
1309
- title: 'Community demo',
1310
- url: 'https://stream.mux.com/playback-id.m3u8',
1311
- metadata: { videoUrl: 'https://stream.mux.com/playback-id.m3u8' },
1312
- },
1313
- ];
1314
-
1315
- reducer.apply({ type: 'turn-start', seq: 1 });
1316
- reducer.apply({ type: 'sources', sources, refs, seq: 2 });
1317
- reducer.apply({ type: 'text-delta', text: 'Follow the guide [1].', seq: 3 });
1318
-
1319
- expect(reducer.state.messages).toHaveLength(1);
1320
- expect(reducer.state.messages[0]).toMatchObject({ sources, refs });
1321
- });
1322
-
1323
- it('deduplicates cumulative metadata and does not leak it into the next turn', () => {
1324
- const reducer = createChatStreamReducer({ transport: 'nats' });
1325
-
1326
- reducer.apply({ type: 'turn-start', seq: 1 });
1327
- reducer.apply({ type: 'sources', sources, seq: 2 });
1328
- reducer.apply({
1329
- type: 'sources',
1330
- sources: [
1331
- { ...sources[0], name: 'Duplicate' },
1332
- { index: 2, name: 'Install agent', path: 'docs/install.md', documentType: 'markdown' },
1333
- ],
1334
- seq: 3,
1335
- });
1336
- reducer.apply({ type: 'text-delta', text: 'First answer [1] [2].', seq: 4 });
1337
- reducer.apply({ type: 'turn-end', seq: 5 });
1338
- reducer.apply({ type: 'turn-start', seq: 6 });
1339
- reducer.apply({ type: 'text-delta', text: 'Second answer.', seq: 7 });
1340
-
1341
- expect(reducer.state.messages[0].sources).toEqual([
1342
- sources[0],
1343
- { index: 2, name: 'Install agent', path: 'docs/install.md', documentType: 'markdown' },
1344
- ]);
1345
- expect(reducer.state.messages[1].sources).toBeUndefined();
1346
- });
1347
- });
@@ -60,7 +60,6 @@ import {
60
60
  type ParticipantEvent,
61
61
  type UsageEvent,
62
62
  } from '../../../chat-protocol/events';
63
- import type { ChatRef } from '../chat-ref.types';
64
63
  import type {
65
64
  ApprovalBatchSegment,
66
65
  ApprovalRequestSegment,
@@ -71,7 +70,6 @@ import type {
71
70
  ToolExecutionSegment,
72
71
  ExecutingToolState,
73
72
  } from '../types';
74
- import type { ChatSource } from '../types/message.types';
75
73
  import type { PendingApproval } from '../types/processing.types';
76
74
  import type {
77
75
  DialogTokenUsage,
@@ -80,7 +78,6 @@ import type {
80
78
  UnifiedUsageBreakdown,
81
79
  } from '../types/unified-chat-state.types';
82
80
  import { approvalDisplaysInline } from '../utils/approval-display';
83
- import { mergeChatRefs, mergeChatSources } from '../utils/chat-source-metadata';
84
81
  import {
85
82
  type MessageSegmentAccumulator,
86
83
  createMessageSegmentAccumulator,
@@ -679,9 +676,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
679
676
  let sendCount = 0;
680
677
  let sseCurrentText = '';
681
678
 
682
- let pendingSources: ChatSource[] | undefined;
683
- let pendingRefs: ChatRef[] | undefined;
684
-
685
679
  // ── Snapshot cache ─────────────────────────────────────────────────────
686
680
  let stateCache: ChatReducerState | null = null;
687
681
  function invalidate(): void {
@@ -779,18 +773,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
779
773
  setMessagesInternal(stampTrailingAssistantSeq(updateTrailingAssistant(messages, segments), seq));
780
774
  }
781
775
 
782
- function attachPendingSourceMetadata(): void {
783
- if (!pendingSources && !pendingRefs) return;
784
- const last = messages[messages.length - 1];
785
- if (!last || last.role !== 'assistant') return;
786
- const next = {
787
- ...last,
788
- ...(pendingSources ? { sources: pendingSources } : {}),
789
- ...(pendingRefs ? { refs: pendingRefs } : {}),
790
- };
791
- setMessagesInternal([...messages.slice(0, -1), next]);
792
- }
793
-
794
776
  function applyStreamStartToState(): void {
795
777
  setPhaseInternal('streaming');
796
778
  // A new stream must never overwrite a COMPLETED trailing assistant
@@ -984,8 +966,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
984
966
 
985
967
  switch (event.type) {
986
968
  case 'turn-start': {
987
- pendingSources = undefined;
988
- pendingRefs = undefined;
989
969
  isInStream = true;
990
970
  hasEverStreamed = true;
991
971
  turnStartedAt = Date.now();
@@ -1001,15 +981,6 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
1001
981
  emit('onStreamEnd');
1002
982
  setPhaseInternal('idle');
1003
983
  accumulator.resetSegments();
1004
- pendingSources = undefined;
1005
- pendingRefs = undefined;
1006
- break;
1007
- }
1008
-
1009
- case 'sources': {
1010
- pendingSources = mergeChatSources(pendingSources, event.sources);
1011
- if (event.refs) pendingRefs = mergeChatRefs(pendingRefs, event.refs);
1012
- attachPendingSourceMetadata();
1013
984
  break;
1014
985
  }
1015
986
 
@@ -1046,12 +1017,10 @@ export function createChatStreamReducer(options: ChatStreamReducerOptions = {}):
1046
1017
  if (isInStream || !hasEverStreamed) {
1047
1018
  emitSegments(segments);
1048
1019
  applySegmentsToState(segments, withSeqMeta(undefined));
1049
- attachPendingSourceMetadata();
1050
1020
  } else {
1051
1021
  const delta: MessageSegment[] = [{ type: kind, text: event.text }];
1052
1022
  emitSegments(delta, { append: true });
1053
1023
  applySegmentsToState(delta, withSeqMeta({ append: true }));
1054
- attachPendingSourceMetadata();
1055
1024
  }
1056
1025
  break;
1057
1026
  }
@@ -54,9 +54,6 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
54
54
 
55
55
  const hasResult = isExecuted && typeof message.result === 'string' && message.result.length > 0;
56
56
  const hasBody = argEntries.length > 0 || hasResult || isExecuting;
57
- const isConciseRemoteRead =
58
- !message.integratedToolType && !!message.toolTitle?.trim() && argEntries.length === 0 && !hasResult;
59
- const isStaticRow = isClientView || isConciseRemoteRead;
60
57
 
61
58
  const renderStatusIcon = () => {
62
59
  if (isExecuting) return <DotsLoaderIcon size={16} className="text-ods-text-secondary" />;
@@ -85,7 +82,7 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
85
82
  {previewText}
86
83
  </div>
87
84
  <div className="flex h-5 w-5 shrink-0 items-center justify-center">{renderStatusIcon()}</div>
88
- {!isStaticRow && (
85
+ {!isClientView && (
89
86
  <div className="flex h-5 w-5 shrink-0 items-center justify-center">
90
87
  <ExpandChevron expanded={expanded} />
91
88
  </div>
@@ -104,7 +101,7 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
104
101
  )}
105
102
  {...props}
106
103
  >
107
- {isStaticRow ? (
104
+ {isClientView ? (
108
105
  // Client (Fae end-user): static, non-expandable row — no chevron, no
109
106
  // body. Just the explanation + status; command/args/result are hidden.
110
107
  <div className="flex w-full items-start gap-[var(--spacing-system-xs)] p-[var(--spacing-system-s)] text-left">