@flamingo-stack/openframe-frontend-core 0.0.438-snapshot.20260716203356 → 0.0.438

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 (121) hide show
  1. package/dist/{chunk-HTVVXVJL.cjs → chunk-2DCP2TIF.cjs} +1490 -1966
  2. package/dist/chunk-2DCP2TIF.cjs.map +1 -0
  3. package/dist/{chunk-PBTY7AH5.js → chunk-44HNH57W.js} +4 -4
  4. package/dist/{chunk-G5FALXNO.js → chunk-5D5SJOLE.js} +4 -4
  5. package/dist/{chunk-NQQ4NLUT.js → chunk-6ETVJ5ON.js} +6101 -6577
  6. package/dist/chunk-6ETVJ5ON.js.map +1 -0
  7. package/dist/{chunk-3WJQL4DN.cjs → chunk-6R5SZPAQ.cjs} +15 -15
  8. package/dist/{chunk-3WJQL4DN.cjs.map → chunk-6R5SZPAQ.cjs.map} +1 -1
  9. package/dist/{chunk-P7PISHM5.js → chunk-A25RPWYF.js} +2 -2
  10. package/dist/{chunk-AUJXLVXA.cjs → chunk-B5SGPIJ4.cjs} +37 -37
  11. package/dist/{chunk-AUJXLVXA.cjs.map → chunk-B5SGPIJ4.cjs.map} +1 -1
  12. package/dist/{chunk-VWGF6IFR.cjs → chunk-CMP56RUC.cjs} +62 -62
  13. package/dist/{chunk-VWGF6IFR.cjs.map → chunk-CMP56RUC.cjs.map} +1 -1
  14. package/dist/{chunk-F7YVI54P.js → chunk-D5RTZ2WN.js} +4 -4
  15. package/dist/{chunk-R46QEE3R.cjs → chunk-DTYPYYKD.cjs} +59 -59
  16. package/dist/{chunk-R46QEE3R.cjs.map → chunk-DTYPYYKD.cjs.map} +1 -1
  17. package/dist/{chunk-RQQZF6G6.cjs → chunk-H2TQ44VD.cjs} +31 -31
  18. package/dist/{chunk-RQQZF6G6.cjs.map → chunk-H2TQ44VD.cjs.map} +1 -1
  19. package/dist/{chunk-7WM77CSW.js → chunk-KBFQF4AV.js} +2 -2
  20. package/dist/{chunk-2ELBNZJ3.js → chunk-LR6A67KZ.js} +2 -2
  21. package/dist/{chunk-ZMUFTGL7.cjs → chunk-PCMDRDYX.cjs} +5 -5
  22. package/dist/{chunk-ZMUFTGL7.cjs.map → chunk-PCMDRDYX.cjs.map} +1 -1
  23. package/dist/{chunk-WAGTZVQN.js → chunk-POFDZH2C.js} +4 -4
  24. package/dist/{chunk-EGRZHR7J.js → chunk-RTHOCS3S.js} +2 -2
  25. package/dist/{chunk-M3LD6DNE.cjs → chunk-SYTL2VIU.cjs} +7 -7
  26. package/dist/{chunk-M3LD6DNE.cjs.map → chunk-SYTL2VIU.cjs.map} +1 -1
  27. package/dist/{chunk-WZ6TCAYQ.cjs → chunk-VYG2CJFJ.cjs} +9 -9
  28. package/dist/{chunk-WZ6TCAYQ.cjs.map → chunk-VYG2CJFJ.cjs.map} +1 -1
  29. package/dist/{chunk-COMBYYQY.js → chunk-W2PRXXUD.js} +2 -2
  30. package/dist/{chunk-IAN7GUIV.cjs → chunk-WF7VGEYR.cjs} +26 -26
  31. package/dist/{chunk-IAN7GUIV.cjs.map → chunk-WF7VGEYR.cjs.map} +1 -1
  32. package/dist/{chunk-C3FH53AP.cjs → chunk-X66KRL5G.cjs} +11 -11
  33. package/dist/{chunk-C3FH53AP.cjs.map → chunk-X66KRL5G.cjs.map} +1 -1
  34. package/dist/{chunk-WPVBTVI3.js → chunk-XFSDFO6U.js} +2 -2
  35. package/dist/{chunk-M6QGB6DC.cjs → chunk-YKP2F2NC.cjs} +26 -26
  36. package/dist/{chunk-M6QGB6DC.cjs.map → chunk-YKP2F2NC.cjs.map} +1 -1
  37. package/dist/{chunk-PEIR7D25.js → chunk-ZEY7A3MP.js} +2 -2
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/chat-archive-page.d.ts +8 -28
  41. package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
  42. package/dist/components/chat/chat-header-icon-button.d.ts +2 -6
  43. package/dist/components/chat/chat-header-icon-button.d.ts.map +1 -1
  44. package/dist/components/chat/chat-panel-header.d.ts +9 -19
  45. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  46. package/dist/components/chat/embeddable-chat.d.ts +6 -15
  47. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  48. package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts +0 -2
  49. package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts.map +1 -1
  50. package/dist/components/chat/index.cjs +2 -4
  51. package/dist/components/chat/index.cjs.map +1 -1
  52. package/dist/components/chat/index.d.ts +0 -2
  53. package/dist/components/chat/index.d.ts.map +1 -1
  54. package/dist/components/chat/index.js +3 -5
  55. package/dist/components/chat/mingo-chat-history.d.ts +10 -5
  56. package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
  57. package/dist/components/chat/mingo-welcome.d.ts +25 -6
  58. package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
  59. package/dist/components/contact/index.cjs +3 -3
  60. package/dist/components/contact/index.js +2 -2
  61. package/dist/components/docs/index.cjs +5 -5
  62. package/dist/components/docs/index.js +4 -4
  63. package/dist/components/embeds/index.cjs +3 -3
  64. package/dist/components/embeds/index.js +2 -2
  65. package/dist/components/faq/index.cjs +3 -3
  66. package/dist/components/faq/index.js +2 -2
  67. package/dist/components/features/index.cjs +2 -2
  68. package/dist/components/features/index.js +1 -1
  69. package/dist/components/help-center-pages/index.cjs +20 -20
  70. package/dist/components/help-center-pages/index.js +11 -11
  71. package/dist/components/index.cjs +128 -130
  72. package/dist/components/index.cjs.map +1 -1
  73. package/dist/components/index.js +11 -13
  74. package/dist/components/index.js.map +1 -1
  75. package/dist/components/navigation/index.cjs +2 -2
  76. package/dist/components/navigation/index.js +1 -1
  77. package/dist/components/onboarding-guides/index.cjs +5 -5
  78. package/dist/components/onboarding-guides/index.js +4 -4
  79. package/dist/components/related-content/index.cjs +3 -3
  80. package/dist/components/related-content/index.js +2 -2
  81. package/dist/components/tickets/index.cjs +5 -5
  82. package/dist/components/tickets/index.js +4 -4
  83. package/dist/components/ui/index.cjs +2 -4
  84. package/dist/components/ui/index.cjs.map +1 -1
  85. package/dist/components/ui/index.js +3 -5
  86. package/dist/index.cjs +2 -4
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.js +3 -5
  89. package/package.json +1 -1
  90. package/src/components/chat/chat-archive-page.tsx +73 -89
  91. package/src/components/chat/chat-header-icon-button.tsx +5 -12
  92. package/src/components/chat/chat-panel-header.tsx +29 -76
  93. package/src/components/chat/embeddable-chat.tsx +125 -548
  94. package/src/components/chat/guide-welcome.tsx +4 -4
  95. package/src/components/chat/hooks/use-chat-dialog-manager.ts +5 -59
  96. package/src/components/chat/index.ts +0 -2
  97. package/src/components/chat/mingo-chat-history.tsx +100 -46
  98. package/src/components/chat/mingo-welcome.tsx +98 -17
  99. package/src/stories/MingoWelcome.stories.tsx +11 -2
  100. package/dist/chunk-HTVVXVJL.cjs.map +0 -1
  101. package/dist/chunk-NQQ4NLUT.js.map +0 -1
  102. package/dist/components/chat/chat-header-search-field.d.ts +0 -25
  103. package/dist/components/chat/chat-header-search-field.d.ts.map +0 -1
  104. package/dist/components/chat/chat-list-empty-state.d.ts +0 -20
  105. package/dist/components/chat/chat-list-empty-state.d.ts.map +0 -1
  106. package/dist/components/chat/mingo-history-rail.d.ts +0 -54
  107. package/dist/components/chat/mingo-history-rail.d.ts.map +0 -1
  108. package/src/components/chat/chat-header-search-field.tsx +0 -144
  109. package/src/components/chat/chat-list-empty-state.tsx +0 -44
  110. package/src/components/chat/mingo-history-rail.tsx +0 -161
  111. /package/dist/{chunk-PBTY7AH5.js.map → chunk-44HNH57W.js.map} +0 -0
  112. /package/dist/{chunk-G5FALXNO.js.map → chunk-5D5SJOLE.js.map} +0 -0
  113. /package/dist/{chunk-P7PISHM5.js.map → chunk-A25RPWYF.js.map} +0 -0
  114. /package/dist/{chunk-F7YVI54P.js.map → chunk-D5RTZ2WN.js.map} +0 -0
  115. /package/dist/{chunk-7WM77CSW.js.map → chunk-KBFQF4AV.js.map} +0 -0
  116. /package/dist/{chunk-2ELBNZJ3.js.map → chunk-LR6A67KZ.js.map} +0 -0
  117. /package/dist/{chunk-WAGTZVQN.js.map → chunk-POFDZH2C.js.map} +0 -0
  118. /package/dist/{chunk-EGRZHR7J.js.map → chunk-RTHOCS3S.js.map} +0 -0
  119. /package/dist/{chunk-COMBYYQY.js.map → chunk-W2PRXXUD.js.map} +0 -0
  120. /package/dist/{chunk-WPVBTVI3.js.map → chunk-XFSDFO6U.js.map} +0 -0
  121. /package/dist/{chunk-PEIR7D25.js.map → chunk-ZEY7A3MP.js.map} +0 -0
@@ -31,7 +31,6 @@
31
31
  */
32
32
 
33
33
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
34
- import { cn } from '../../utils/cn'
35
34
  import { useControllableState } from '@radix-ui/react-use-controllable-state'
36
35
  import * as DialogPrimitive from '@radix-ui/react-dialog'
37
36
  import { usePreventScroll } from '@react-aria/overlays'
@@ -48,18 +47,12 @@ import { EntityIcon } from '../icon-display'
48
47
  import { MingoOnboardingCard } from './mingo-onboarding-card'
49
48
  import { MingoOnboardingCardSkeleton } from './mingo-onboarding-card-skeleton'
50
49
  import { MingoWelcome, type MingoWelcomeProps } from './mingo-welcome'
51
- import { MingoHistoryRail } from './mingo-history-rail'
50
+ import { MingoChatHistory } from './mingo-chat-history'
52
51
  import { GuideWelcome, type GuideWelcomeProps } from './guide-welcome'
53
52
  import { accentFromIdentityIcon, getAgentAccent } from './quick-action-chip'
54
53
  import { GuideModeBanner } from './guide-mode-banner'
55
54
  import { PortalContainerContext } from '../ui/portal-container'
56
- import { ChatPanelHeader, type ChatPanelHeaderProps } from './chat-panel-header'
57
- import { ChatHeaderIconButton } from './chat-header-icon-button'
58
- import { ChatHeaderSearchField } from './chat-header-search-field'
59
- import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu'
60
- import { Arrow02RightIcon, Arrow02LeftIcon, SearchIcon, ClockHistoryIcon, Ellipsis01Icon, Refresh01LeftIcon } from '../icons-v2-generated'
61
- import { Chevron02LeftIcon } from '../icons-v2-generated/arrows/chevron-02-left-icon'
62
- import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon'
55
+ import { ChatPanelHeader } from './chat-panel-header'
63
56
  import { ChatArchivePage } from './chat-archive-page'
64
57
  import { ChatComposer } from './chat-composer'
65
58
  import { useChatDialogManager } from './hooks/use-chat-dialog-manager'
@@ -274,27 +267,18 @@ export interface EmbeddableChatProps {
274
267
  */
275
268
  shell?: 'drawer' | 'none'
276
269
 
277
- /**
278
- * Display name of the signed-in user, shown as the sub-line under the chat
279
- * title in the panel header. The server-resolved chat identity
280
- * (`useChatIdentity().user.name`) always wins when present; this is the
281
- * host-supplied fallback so the header still shows who's signed in when the
282
- * identity endpoint doesn't return a name (e.g. a tenant whose identity
283
- * route omits it). Empty/undefined → the header renders the title alone.
284
- */
285
- userDisplayName?: string
286
-
287
270
  /**
288
271
  * Content overrides for the default (Mingo-mode) empty state
289
- * (`<MingoWelcome>`): greeting `title`/`subtitle`, the `promo` card, and
290
- * extra `quickActions` chips.
272
+ * (`<MingoWelcome>`): greeting `title`/`subtitle`, the capability
273
+ * `featureCards` grid, the `promo` card, and extra `quickActions` chips.
291
274
  * Each field falls back to the built-in OpenFrame defaults, so the kit
292
- * stays platform-agnostic. `onStartGuideChat` and `hasExistingChats` are
293
- * wired internally and are NOT overridable here. The quick-action
294
- * hover-preview callbacks are also wired internally.
275
+ * stays platform-agnostic. `userName`, `onStartGuideChat` and
276
+ * `hasExistingChats` are wired internally and are NOT overridable here.
277
+ * The quick-action hover-preview callbacks are also wired internally.
295
278
  */
296
279
  mingoWelcome?: Omit<
297
280
  MingoWelcomeProps,
281
+ | 'userName'
298
282
  | 'onStartGuideChat'
299
283
  | 'hasExistingChats'
300
284
  | 'onQuickActionHover'
@@ -396,99 +380,6 @@ const mentionTokenOf = (key: string, markerByType: Map<string, string>): string
396
380
  */
397
381
  const FALLBACK_TOP_RETRIEVED = 3
398
382
 
399
- /**
400
- * Split (wide) Mingo layout metrics (Figma 113:60931 / 113:63630). The
401
- * "Current Chats" rail is a fixed 320px column; the chat block fills the rest
402
- * with a 400px floor. Below their sum the panel is too tight for two columns,
403
- * so the layout falls back to the stacked (single-column) view where the
404
- * history renders inline in the Mingo empty state. Purely width-driven and
405
- * self-contained — the host wires nothing.
406
- */
407
- const HISTORY_RAIL_WIDTH = 320
408
- const CHAT_BLOCK_MIN_WIDTH = 400
409
- const SPLIT_MIN_WIDTH = HISTORY_RAIL_WIDTH + CHAT_BLOCK_MIN_WIDTH
410
-
411
- /** Persists the user's rail collapse choice across drawer open/close + reloads. */
412
- const RAIL_COLLAPSED_STORAGE_KEY = 'mingo-chat-history-collapsed'
413
-
414
- /** Exit-timer budget (ms) before the collapsed rail unmounts — must cover the
415
- * longest leave (`RAIL_COLLAPSE_MS`) plus a little slack so the width/opacity
416
- * transition always settles before the element is removed. */
417
- const RAIL_ANIM_MS = 320
418
-
419
- /**
420
- * Presence + width-transition state for the history rail. The rail animates its
421
- * WIDTH (320 ⇄ 0) + opacity so the chat block grows/shrinks smoothly alongside
422
- * it — no hard reflow "jump" when it appears/disappears.
423
- *
424
- * - `mounted` — whether to render the rail at all (stays true through the leave
425
- * so the collapse can play out before unmount).
426
- * - `active` — drives the expanded (`w-80 opacity-100`) vs collapsed
427
- * (`w-0 opacity-0`) classes. On a fresh mount it flips true on the next frame
428
- * so the open transitions from collapsed → expanded (an intro slide).
429
- */
430
- function useRailPresence(open: boolean): { mounted: boolean; active: boolean } {
431
- const [mounted, setMounted] = useState(open)
432
- // Start collapsed even when open, so the first paint transitions OPEN.
433
- const [active, setActive] = useState(false)
434
- useEffect(() => {
435
- if (open) {
436
- setMounted(true)
437
- // Flip to expanded only AFTER the browser has painted the collapsed
438
- // (w-0) frame — otherwise the mount + expand coalesce into one paint and
439
- // the width transition never plays (the rail "pops" open). A single rAF
440
- // fires before that first paint; a SECOND rAF lands after it, so the
441
- // enter transition runs from a real w-0 → w-80.
442
- let raf2 = 0
443
- const raf1 = requestAnimationFrame(() => {
444
- raf2 = requestAnimationFrame(() => setActive(true))
445
- })
446
- return () => {
447
- cancelAnimationFrame(raf1)
448
- cancelAnimationFrame(raf2)
449
- }
450
- }
451
- setActive(false) // collapse (width/opacity transition to 0)
452
- if (!mounted) return // already gone — nothing to unmount later
453
- const t = setTimeout(() => setMounted(false), RAIL_ANIM_MS)
454
- return () => clearTimeout(t)
455
- }, [open, mounted])
456
- return { mounted, active }
457
- }
458
-
459
- /**
460
- * Animated container for a history-rail cell (the header's "Current Chats" cell
461
- * and the body's list column). The OUTER box transitions its width 0 ⇄ 320 +
462
- * opacity (so the neighbouring chat block slides smoothly); the INNER keeps a
463
- * fixed `w-80` so the rail content never squishes mid-animation — it's just
464
- * clipped by the shrinking outer (`overflow-hidden`). One easing both ways;
465
- * the collapse runs a touch longer than the open for a soft close.
466
- */
467
- function RailSlot({
468
- active,
469
- innerClassName,
470
- children,
471
- }: {
472
- active: boolean
473
- innerClassName?: string
474
- children: React.ReactNode
475
- }) {
476
- return (
477
- <div
478
- className={cn(
479
- // Border on the OUTER so the rail/chat divider tracks the animating
480
- // boundary (and fades out with the panel) instead of vanishing early.
481
- 'shrink-0 overflow-hidden border-r border-ods-border transition-[width,opacity] ease-out',
482
- active
483
- ? 'w-80 opacity-100 duration-[240ms]'
484
- : 'w-0 opacity-0 duration-[300ms]',
485
- )}
486
- >
487
- <div className={cn('h-full w-80', innerClassName)}>{children}</div>
488
- </div>
489
- )
490
- }
491
-
492
383
  /**
493
384
  * Built-in default route for an OpenFrame AI agent's display config — the
494
385
  * lowest-precedence fallback so "agent mode" works with ZERO endpoint wiring.
@@ -887,7 +778,6 @@ function EmbeddableChatInner({
887
778
  aiAgentConfigUrl: aiAgentConfigUrlProp,
888
779
  defaultActiveMode,
889
780
  shell = 'drawer',
890
- userDisplayName,
891
781
  mingoWelcome,
892
782
  guideWelcome,
893
783
  contextPicker,
@@ -1009,17 +899,22 @@ function EmbeddableChatInner({
1009
899
  [commandsUrl],
1010
900
  )
1011
901
 
1012
- // Header sub-line: the full display name shown under the chat title. The
1013
- // server-resolved identity wins; the host-supplied `userDisplayName` is the
1014
- // fallback so the header still names the signed-in user when the identity
1015
- // endpoint returns no name. Empty → the header shows the title alone.
1016
- const headerUserName =
1017
- identityUser?.name?.trim() ||
1018
- [identityUser?.firstName, identityUser?.lastName]
1019
- .filter(Boolean)
1020
- .join(' ')
1021
- .trim() ||
1022
- userDisplayName?.trim() ||
902
+ // Greeting first-name comes from the SERVER-resolved identity (single
903
+ // source of truth — never a client-injected runtime field). Resolution
904
+ // order:
905
+ // 1. `identityUser.firstName` — dedicated optional field from the
906
+ // identity webservice (populated via `X-Chat-First-Name` for
907
+ // bearer-act-as, or by the hub's profile lookup for cookie sessions).
908
+ // 2. `identityUser.name.split(' ')[0]` — legacy fallback for sources
909
+ // that only return a full name. Empty-string-safe (`?.`-chain).
910
+ // 3. `undefined` — anon, loading, or no name available → greeting
911
+ // collapses to the no-name variant `Hey, I'm Mingo`.
912
+ // We coalesce an empty string to `undefined` so the JSX `userName ? …`
913
+ // branch treats `''` the same as missing — embedders that send
914
+ // `firstName: ''` shouldn't render `Hey , I'm Mingo`.
915
+ const userName =
916
+ (identityUser?.firstName?.trim() ||
917
+ identityUser?.name?.split(' ')[0]?.trim()) ||
1023
918
  undefined
1024
919
 
1025
920
  // Synthesize `modes` from legacy props when the new API isn't used.
@@ -1589,7 +1484,6 @@ function EmbeddableChatInner({
1589
1484
  archivedDialogs,
1590
1485
  archivedCursor,
1591
1486
  archivedLoading,
1592
- archivedPending,
1593
1487
  openArchive,
1594
1488
  closeArchive,
1595
1489
  loadArchivedPage,
@@ -1598,7 +1492,6 @@ function EmbeddableChatInner({
1598
1492
  isOpeningDialog,
1599
1493
  isViewingArchived,
1600
1494
  handleBack,
1601
- resetToNewChat,
1602
1495
  activeDialog,
1603
1496
  renameTarget,
1604
1497
  setRenameTarget,
@@ -1757,189 +1650,6 @@ function EmbeddableChatInner({
1757
1650
  // Host node for in-panel Radix portals (see the body wrapper below).
1758
1651
  const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null)
1759
1652
 
1760
- // ── Split (wide) Mingo layout ─────────────────────────────────────────────
1761
- // The panel measures its own width and, in Mingo mode, promotes the inline
1762
- // dialog history to a fixed 320px "Current Chats" rail once there's room for
1763
- // both it and a 400px chat block. Narrower than that (or collapsed, or Guide
1764
- // mode / archive page) it stays the stacked single-column layout with the
1765
- // history inline in the empty state. Fully self-contained — the host opts
1766
- // into nothing; the switch is width-driven inside this component.
1767
- const [panelWidth, setPanelWidth] = useState(0)
1768
- const panelMeasureRef = useRef<HTMLDivElement | null>(null)
1769
- useEffect(() => {
1770
- const el = panelMeasureRef.current
1771
- if (!el || typeof ResizeObserver === 'undefined') return
1772
- const ro = new ResizeObserver((entries) => {
1773
- const w = entries[0]?.contentRect?.width
1774
- if (typeof w === 'number') setPanelWidth(w)
1775
- })
1776
- ro.observe(el)
1777
- setPanelWidth(el.clientWidth)
1778
- return () => ro.disconnect()
1779
- }, [])
1780
-
1781
- // Rail collapse toggle (Figma ⟶| control), persisted so it survives the
1782
- // drawer remounting on close.
1783
- const [railCollapsed, setRailCollapsed] = useState(false)
1784
- useEffect(() => {
1785
- try {
1786
- if (window.localStorage.getItem(RAIL_COLLAPSED_STORAGE_KEY) === '1')
1787
- setRailCollapsed(true)
1788
- } catch {
1789
- // Storage can throw (private mode) — default to expanded.
1790
- }
1791
- }, [])
1792
- const toggleRailCollapsed = useCallback(() => {
1793
- setRailCollapsed((prev) => {
1794
- const next = !prev
1795
- try {
1796
- window.localStorage.setItem(RAIL_COLLAPSED_STORAGE_KEY, next ? '1' : '0')
1797
- } catch {
1798
- // Best-effort persistence; the in-memory state still applies this session.
1799
- }
1800
- return next
1801
- })
1802
- }, [])
1803
-
1804
- // Header magnifier toggle — reveals the rail's built-in list search bar on
1805
- // demand (matches the Figma header). Only meaningful when search is wired.
1806
- const [railSearchOpen, setRailSearchOpen] = useState(false)
1807
-
1808
- // Layout decision. `wideMingo` gates the desktop two-cell header (which hosts
1809
- // the expand control even while collapsed); `splitActive` additionally
1810
- // requires the rail to be expanded before its column renders. The archive
1811
- // page stays split-eligible: in wide mode it opens INSIDE the left rail (the
1812
- // right chat block stays put), and only falls back to the full-panel archive
1813
- // when stacked/collapsed.
1814
- const splitEligible = activeMode === 'mingo'
1815
- const canSplit = panelWidth >= SPLIT_MIN_WIDTH
1816
- // Embedded previews (hero demo tabs) always use the compact single-column
1817
- // header, never the two-column split — so `previewMode` opts out of `wideMingo`.
1818
- const wideMingo = splitEligible && canSplit && !previewMode
1819
- const splitActive = wideMingo && !railCollapsed
1820
- // Keep the "Current Chats" rail (header cell + list column) mounted through
1821
- // its width/opacity collapse so it eases away instead of hard-cutting; both
1822
- // synchronized parts wrap in `<RailSlot active={railPresence.active}>`.
1823
- const railPresence = useRailPresence(splitActive)
1824
- // The wide header is used for BOTH wide layouts — split (rail beside the chat)
1825
- // and collapsed (`wideCollapsed`: chat / new-chat welcome in the fill column,
1826
- // rail hidden). Its built-in corner toggle expands/collapses the rail — the
1827
- // only chat-list navigation in wide mode, so there's no back chevron here.
1828
- // `|| railPresence.mounted` keeps it through the rail's exit animation when a
1829
- // resize drops below the split threshold (wideMingo → narrow).
1830
- const showWideHeader = wideMingo || railPresence.mounted
1831
-
1832
- // ── Narrow (stacked) list ⇄ compose navigation ────────────────────────────
1833
- // The redesigned narrow layout (Figma 341:36190 / 113:69276) splits the old
1834
- // combined empty state into two views: a "Current Chats" LIST (no composer)
1835
- // and a "New Chat" COMPOSE view (greeting + composer) reached from the list's
1836
- // "Start New Chat" button. Wide mode shows both columns at once, so this only
1837
- // drives the single-column presentation.
1838
- const [composeOpen, setComposeOpen] = useState(false)
1839
- // Opening a conversation supersedes the compose view — reset it so backing out
1840
- // of the conversation lands on the list, not a stale compose screen.
1841
- useEffect(() => {
1842
- if (hasConversation) setComposeOpen(false)
1843
- }, [hasConversation])
1844
- const isMingoMode = activeMode === 'mingo'
1845
- // The stacked "Current Chats" list — the NARROW single-column Mingo view. It
1846
- // is a narrow-only layout: in any wide layout the chat list lives in the rail,
1847
- // and collapsing the rail (`wideCollapsed`) shows the chat / new-chat welcome
1848
- // in the fill column (navigation is via re-expanding the rail), NOT the list.
1849
- // Requires no open conversation, no archive, and not composing (the composer
1850
- // lives on the compose view).
1851
- const stackedListView =
1852
- isMingoMode &&
1853
- !hasConversation &&
1854
- !archiveOpen &&
1855
- !wideMingo &&
1856
- !composeOpen
1857
-
1858
- // Shared header derivations — consumed by the stacked `ChatPanelHeader`, its
1859
- // mobile fallback, and the desktop split header's right cell, so the title /
1860
- // back / ⋯ semantics can't drift between layouts.
1861
- const headerShowBack = hasConversation || guideCanReturnToMingo
1862
- const headerTitle = hasConversation
1863
- ? activeDialog?.title || 'New Chat'
1864
- : isGuideEmpty
1865
- ? (headerAssistantName ?? 'Mingo Guide')
1866
- : 'Current Chats'
1867
- const headerBackAriaLabel = hasConversation
1868
- ? isViewingArchived
1869
- ? 'Back to archive'
1870
- : 'Back'
1871
- : 'Back to Mingo'
1872
- const headerOnBack = hasConversation
1873
- ? handleBack
1874
- : () => handleActiveModeChange('mingo')
1875
- const headerOnRestore =
1876
- isViewingArchived && unarchiveDialog && activeDialog
1877
- ? () => setRestoreTarget(activeDialog)
1878
- : undefined
1879
- const headerOnRename =
1880
- activeDialogId && activeDialog && mingoCaps.canRename
1881
- ? () => setRenameTarget(activeDialog)
1882
- : undefined
1883
- const headerOnArchive =
1884
- activeDialogId && activeDialog && mingoCaps.canArchive
1885
- ? () => setArchiveTarget(activeDialog)
1886
- : undefined
1887
- const headerOnOpenArchive = fetchArchivedDialogs ? openArchive : undefined
1888
-
1889
- // "Start New Chat" (rail button) — reset to the new-chat welcome. Uses the
1890
- // dedicated reset (not `handleBack`, which would reopen the archive when the
1891
- // open conversation is archived) so it always lands on a fresh chat.
1892
- const handleNewChat = useCallback(() => {
1893
- resetToNewChat()
1894
- }, [resetToNewChat])
1895
-
1896
- // Desktop split header ⋯ menu (active, non-archived conversation only).
1897
- const splitHeaderMenuItems = [
1898
- headerOnRename && { id: 'rename', label: 'Rename chat', onClick: headerOnRename },
1899
- headerOnArchive && { id: 'archive', label: 'Archive chat', onClick: headerOnArchive },
1900
- ].filter(Boolean) as ActionsMenuItem[]
1901
-
1902
- // Narrow (single-column) header. The Mingo empty state splits into a "Current
1903
- // Chats" list header (search + archive, no back) and a "New Chat" compose
1904
- // header (back to the list); conversations + guide keep the shared header
1905
- // derivations. Wide mode uses its own two-cell header instead.
1906
- const narrowMingoEmpty = isMingoMode && !hasConversation && !archiveOpen
1907
- const narrowHeaderProps: ChatPanelHeaderProps = narrowMingoEmpty
1908
- ? composeOpen
1909
- ? {
1910
- showBack: true,
1911
- title: 'New Chat',
1912
- subtitle: headerUserName,
1913
- backAriaLabel: 'Back to chats',
1914
- onBack: () => setComposeOpen(false),
1915
- onClose: handleClose,
1916
- }
1917
- : {
1918
- showBack: false,
1919
- title: 'Current Chats',
1920
- onClose: handleClose,
1921
- onOpenArchive: headerOnOpenArchive,
1922
- onToggleSearch: mingoCaps.onSearchChange
1923
- ? () => setRailSearchOpen((o) => !o)
1924
- : undefined,
1925
- searchActive: railSearchOpen,
1926
- searchQuery: mingoCaps.searchQuery,
1927
- onSearchChange: mingoCaps.onSearchChange,
1928
- }
1929
- : {
1930
- showBack: headerShowBack,
1931
- title: headerTitle,
1932
- subtitle: headerUserName,
1933
- backAriaLabel: headerBackAriaLabel,
1934
- isArchivedView: isViewingArchived,
1935
- onBack: headerOnBack,
1936
- onClose: handleClose,
1937
- onRestore: headerOnRestore,
1938
- onRename: headerOnRename,
1939
- onArchive: headerOnArchive,
1940
- onOpenArchive: headerOnOpenArchive,
1941
- }
1942
-
1943
1653
  // Chat body — defined once, then rendered inside whichever shell applies.
1944
1654
  // Radix overlays (⋯ menus, tooltips) portal into `portalHost` — a node inside
1945
1655
  // this panel — so they inherit the drawer's stacking context and need only a
@@ -1948,31 +1658,22 @@ function EmbeddableChatInner({
1948
1658
  const body = (
1949
1659
  <PortalContainerContext.Provider value={portalHost}>
1950
1660
  {/* Panel surface depends on state (Figma):
1951
- • Narrow "Current Chats" LIST + FULL-PANEL archive page → grey
1952
- `ods-card` (#212121) — matching the grey rail those lists live
1953
- in when the panel is wide,
1954
- • Mingo welcome / compose (node 113:69208), Guide empty
1955
- (node 7532:328223), and the active conversation → dark
1661
+ • Mingo empty / returning-user + archive page → grey
1662
+ `ods-card` (#212121),
1663
+ • Guide empty (node 7532:328223) + active conversation → dark
1956
1664
  `ods-bg` (#161616).
1957
1665
  The header keeps its own grey `bg-ods-card`; the content and
1958
- footer have no bg and inherit this root, so they flip together.
1959
- The archive only tints the root when it's FULL-PANEL (`!splitActive`)
1960
- — in the split layout it lives in the rail (which owns its own
1961
- grey), so the chat block on the right must keep the conversation's
1962
- dark surface instead of following the archive. */}
1666
+ footer have no bg and inherit this root, so they flip together. */}
1963
1667
  <div
1964
- ref={panelMeasureRef}
1965
1668
  className={`flex h-full flex-col overflow-hidden transition-colors duration-200 ${
1966
- (archiveOpen && !splitActive) || stackedListView
1669
+ archiveOpen || (!hasConversation && activeMode === 'mingo')
1967
1670
  ? 'bg-ods-card'
1968
1671
  : 'bg-ods-bg'
1969
1672
  } ${previewMode ? 'pointer-events-none select-none' : ''}`}
1970
1673
  >
1971
1674
  {/* Archive-page ↔ chat-panel swap fades in (200ms) to match the
1972
- surface flip. The full-panel archive is used only when NOT in
1973
- the wide split layout — there the archive opens inside the left
1974
- rail instead (see the rail column below). */}
1975
- {archiveOpen && !splitActive ? (
1675
+ surface flip — both branches share the same view-change feel. */}
1676
+ {archiveOpen ? (
1976
1677
  <div
1977
1678
  key="archive-view"
1978
1679
  className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
@@ -1983,7 +1684,6 @@ function EmbeddableChatInner({
1983
1684
  onBack={closeArchive}
1984
1685
  onClose={handleClose}
1985
1686
  isLoading={archivedLoading}
1986
- isFetching={archivedPending}
1987
1687
  hasMore={archivedCursor != null}
1988
1688
  onLoadMore={() => {
1989
1689
  void loadArchivedPage(archivedCursor ?? undefined)
@@ -1995,143 +1695,54 @@ function EmbeddableChatInner({
1995
1695
  key="chat-view"
1996
1696
  className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
1997
1697
  >
1998
- {showWideHeader ? (
1999
- // Wide Mingo header (Figma 113:60931 / 113:63630). Split: a
2000
- // two-cell bar — the "Current Chats" rail header (search /
2001
- // archive / collapse) over the 320px rail + the chat-block header
2002
- // (title / ⋯ / close) over the fill column. Collapsed
2003
- // (`wideCollapsed`): the rail cell is gone, leaving the corner
2004
- // expand toggle + the chat-block header (the fill column shows
2005
- // the chat / new-chat welcome). No back chevron — chat-list
2006
- // navigation is the rail toggle. Shown whenever the panel is wide
2007
- // enough to split (`panelWidth ≥ SPLIT_MIN_WIDTH`), NOT gated on
2008
- // the viewport `md` breakpoint — otherwise a 720–799px panel on a
2009
- // sub-`md` viewport would split the body yet drop to the mobile
2010
- // single-bar header (no rail search / collapse controls).
2011
- <div className="flex h-14 w-full flex-shrink-0 overflow-hidden border-b border-ods-border bg-ods-card">
2012
- {railPresence.mounted && (
2013
- <RailSlot
2014
- active={railPresence.active}
2015
- innerClassName="flex overflow-hidden"
2016
- >
2017
- {archiveOpen ? (
2018
- // Archive open in the wide layout — the rail header
2019
- // turns into a "Chat Archive" back bar (the list itself
2020
- // renders in the rail below); the chat block is untouched.
2021
- // Back is a full-height leading cell, matching the other
2022
- // header cells (not a small inline chevron).
2023
- <>
2024
- <ChatHeaderIconButton
2025
- divider="right"
2026
- onClick={closeArchive}
2027
- aria-label="Back to chats"
2028
- >
2029
- <Chevron02LeftIcon size={24} />
2030
- </ChatHeaderIconButton>
2031
- <div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
2032
- <span className="min-w-0 flex-1 truncate text-h3 text-ods-text-primary">
2033
- Chat Archive
2034
- </span>
2035
- </div>
2036
- </>
2037
- ) : (
2038
- // "Current Chats" rail header — title fills, then the
2039
- // search + archive controls. When search is open the
2040
- // inline field takes over the title area in place
2041
- // (Figma 116:51217) and the magnifier toggle is hidden.
2042
- <>
2043
- {railSearchOpen && mingoCaps.onSearchChange ? (
2044
- <ChatHeaderSearchField
2045
- initialValue={mingoCaps.searchQuery}
2046
- onSearchChange={mingoCaps.onSearchChange}
2047
- onCollapse={() => setRailSearchOpen(false)}
2048
- />
2049
- ) : (
2050
- <>
2051
- <div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
2052
- <span className="min-w-0 flex-1 truncate text-h3 text-ods-text-primary">
2053
- Current Chats
2054
- </span>
2055
- </div>
2056
- {mingoCaps.onSearchChange && (
2057
- <ChatHeaderIconButton
2058
- onClick={() => setRailSearchOpen((o) => !o)}
2059
- aria-label="Search chats"
2060
- aria-pressed={railSearchOpen}
2061
- >
2062
- <SearchIcon size={24} />
2063
- </ChatHeaderIconButton>
2064
- )}
2065
- </>
2066
- )}
2067
- {headerOnOpenArchive && (
2068
- <ChatHeaderIconButton onClick={headerOnOpenArchive} aria-label="Chat archive">
2069
- <ClockHistoryIcon size={24} />
2070
- </ChatHeaderIconButton>
2071
- )}
2072
- </>
2073
- )}
2074
- </RailSlot>
2075
- )}
2076
-
2077
- <div className="flex flex-1 min-w-0">
2078
- {/* Rail collapse / expand toggle — lives on the chat side,
2079
- at its left edge (Figma 259:90465). Right arrow collapses
2080
- the list; left arrow brings it back. */}
2081
- <button
2082
- type="button"
2083
- onClick={toggleRailCollapsed}
2084
- aria-label={splitActive ? 'Collapse chat list' : 'Show chat list'}
2085
- aria-pressed={!splitActive}
2086
- className="flex size-14 shrink-0 items-center justify-center border-r border-ods-border text-ods-text-secondary transition-colors hover:bg-ods-bg-hover hover:text-ods-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
2087
- >
2088
- {splitActive ? <Arrow02RightIcon size={24} /> : <Arrow02LeftIcon size={24} />}
2089
- </button>
2090
- {/* No back cell in the wide layout — the left rail (and
2091
- its collapse/expand toggle) already provides chat-list
2092
- navigation, so a back chevron would be redundant. */}
2093
- <div className="flex flex-1 min-w-0 items-center px-[var(--spacing-system-mf)] py-[var(--spacing-system-sf)]">
2094
- <div className="flex min-w-0 flex-col">
2095
- <p className="truncate text-h3 leading-tight text-ods-text-primary">
2096
- {headerShowBack ? headerTitle : 'New Chat'}
2097
- </p>
2098
- {headerUserName && (
2099
- <p className="truncate text-h6 leading-tight text-ods-text-secondary">
2100
- {headerUserName}
2101
- </p>
2102
- )}
2103
- </div>
2104
- </div>
2105
-
2106
- {isViewingArchived && headerOnRestore && (
2107
- <ChatHeaderIconButton onClick={headerOnRestore} aria-label="Unarchive chat">
2108
- <Refresh01LeftIcon size={24} />
2109
- </ChatHeaderIconButton>
2110
- )}
2111
- {headerShowBack && !isViewingArchived && splitHeaderMenuItems.length > 0 && (
2112
- <ActionsMenuDropdown
2113
- triggerAriaLabel="Chat actions"
2114
- onCloseAutoFocus={(e) => e.preventDefault()}
2115
- groups={[{ items: splitHeaderMenuItems }]}
2116
- customTrigger={
2117
- <ChatHeaderIconButton aria-label="Chat actions">
2118
- <Ellipsis01Icon size={24} />
2119
- </ChatHeaderIconButton>
2120
- }
2121
- />
2122
- )}
2123
- <ChatHeaderIconButton onClick={handleClose} aria-label="Close">
2124
- <XmarkIcon size={24} />
2125
- </ChatHeaderIconButton>
2126
- </div>
2127
- </div>
2128
- ) : (
2129
- // Narrow single-column header — list / compose / conversation /
2130
- // guide, resolved into `narrowHeaderProps` above. (Wide layouts,
2131
- // including rail-collapsed, use the wide header above.) Embedded
2132
- // previews (hero demo tabs) force the compact bar via `compact`.
2133
- <ChatPanelHeader {...narrowHeaderProps} compact={previewMode} />
2134
- )}
1698
+ <ChatPanelHeader
1699
+ // Embedded previews (the hero demo tabs) keep the small bar at
1700
+ // every width — the phone-sized full-screen header (large title)
1701
+ // is wrong in a small embedded panel.
1702
+ compact={previewMode}
1703
+ // Guide-mode empty state shows a back-chevron + "Mingo Guide"
1704
+ // (back returns to the default Mingo welcome); an open
1705
+ // conversation shows back + the dialog title; the Mingo list
1706
+ // keeps the static "Current Chats" title.
1707
+ showBack={hasConversation || guideCanReturnToMingo}
1708
+ title={
1709
+ hasConversation
1710
+ ? activeDialog?.title || 'New Chat'
1711
+ : isGuideEmpty
1712
+ ? (headerAssistantName ?? 'Mingo Guide')
1713
+ : 'Current Chats'
1714
+ }
1715
+ backAriaLabel={
1716
+ hasConversation
1717
+ ? isViewingArchived
1718
+ ? 'Back to archive'
1719
+ : 'Back'
1720
+ : 'Back to Mingo'
1721
+ }
1722
+ isArchivedView={isViewingArchived}
1723
+ onBack={
1724
+ hasConversation
1725
+ ? handleBack
1726
+ : () => handleActiveModeChange('mingo')
1727
+ }
1728
+ onClose={handleClose}
1729
+ onRestore={
1730
+ isViewingArchived && unarchiveDialog && activeDialog
1731
+ ? () => setRestoreTarget(activeDialog)
1732
+ : undefined
1733
+ }
1734
+ onRename={
1735
+ activeDialogId && activeDialog && mingoCaps.canRename
1736
+ ? () => setRenameTarget(activeDialog)
1737
+ : undefined
1738
+ }
1739
+ onArchive={
1740
+ activeDialogId && activeDialog && mingoCaps.canArchive
1741
+ ? () => setArchiveTarget(activeDialog)
1742
+ : undefined
1743
+ }
1744
+ onOpenArchive={fetchArchivedDialogs ? openArchive : undefined}
1745
+ />
2135
1746
 
2136
1747
  {/* Guide-mode indicator banner (Figma node 7532:328222) —
2137
1748
  full-bleed accent strip below the header. Shown only when
@@ -2150,77 +1761,11 @@ function EmbeddableChatInner({
2150
1761
  places it, mirroring `GuideModeBanner` above. */}
2151
1762
  {activeMode === 'mingo' && mingoContextBanner}
2152
1763
 
2153
- {/* Chat-panel row. In the wide Mingo layout (`splitActive`) the
2154
- dialog history is hoisted into a fixed 320px "Current Chats"
2155
- rail on the left; the stacked layout keeps it inline in the
2156
- Mingo empty state (`<MingoChatHistory>` via `<MingoWelcome>`). */}
1764
+ {/* Chat-panel row. The dialog history is rendered inline in the
1765
+ Mingo empty state (`<MingoChatHistory>`), so there's no
1766
+ separate left sidebar. */}
2157
1767
  <div className="flex flex-1 min-h-0 overflow-hidden">
2158
- {railPresence.mounted && (
2159
- <RailSlot
2160
- active={railPresence.active}
2161
- innerClassName="flex flex-col min-h-0 bg-ods-card"
2162
- >
2163
- {archiveOpen ? (
2164
- // Archive list inside the rail (header lives in the split
2165
- // header's left cell above). Right chat block stays put.
2166
- <ChatArchivePage
2167
- embedded
2168
- dialogs={archivedDialogs}
2169
- onSelectDialog={handleArchivedSelect}
2170
- isLoading={archivedLoading}
2171
- isFetching={archivedPending}
2172
- hasMore={archivedCursor != null}
2173
- onLoadMore={() => {
2174
- void loadArchivedPage(archivedCursor ?? undefined)
2175
- }}
2176
- />
2177
- ) : (
2178
- <MingoHistoryRail
2179
- dialogs={dialogs}
2180
- activeDialogId={activeDialogId ?? undefined}
2181
- onSelectDialog={handleSelectDialog}
2182
- onNewChat={handleNewChat}
2183
- onRequestRename={mingoCaps.canRename ? setRenameTarget : undefined}
2184
- onRequestArchive={mingoCaps.canArchive ? setArchiveTarget : undefined}
2185
- searchQuery={mingoCaps.searchQuery}
2186
- hasMore={hasMoreDialogs}
2187
- isLoadingMore={isDialogsLoading && dialogs.length > 0}
2188
- onLoadMore={() => {
2189
- void loadMoreDialogs()
2190
- }}
2191
- isLoadingHistory={dialogsInitialLoading}
2192
- loadError={dialogsLoadError}
2193
- onRetry={reloadDialogs}
2194
- />
2195
- )}
2196
- </RailSlot>
2197
- )}
2198
1768
  <div className="flex flex-1 flex-col min-h-0 min-w-0">
2199
- {stackedListView ? (
2200
- // Narrow "Current Chats" list (Figma 341:36190) — the default
2201
- // single-column view. No composer here; "Start New Chat" opens
2202
- // the compose view.
2203
- <MingoHistoryRail
2204
- className="animate-in fade-in-0 duration-200"
2205
- dialogs={dialogs}
2206
- activeDialogId={activeDialogId ?? undefined}
2207
- onSelectDialog={handleSelectDialog}
2208
- onNewChat={() => setComposeOpen(true)}
2209
- newChatAlways
2210
- onRequestRename={mingoCaps.canRename ? setRenameTarget : undefined}
2211
- onRequestArchive={mingoCaps.canArchive ? setArchiveTarget : undefined}
2212
- searchQuery={mingoCaps.searchQuery}
2213
- hasMore={hasMoreDialogs}
2214
- isLoadingMore={isDialogsLoading && dialogs.length > 0}
2215
- onLoadMore={() => {
2216
- void loadMoreDialogs()
2217
- }}
2218
- isLoadingHistory={dialogsInitialLoading}
2219
- loadError={dialogsLoadError}
2220
- onRetry={reloadDialogs}
2221
- />
2222
- ) : (
2223
- <>
2224
1769
 
2225
1770
  <div
2226
1771
  ref={galleryPanelRef}
@@ -2298,25 +1843,59 @@ function EmbeddableChatInner({
2298
1843
  quick-action chips. Guide mode keeps the slash-command
2299
1844
  onboarding list below. */
2300
1845
  <MingoWelcome
1846
+ userName={userName}
2301
1847
  onStartGuideChat={
2302
1848
  effectiveModes.guide
2303
1849
  ? () => handleActiveModeChange('guide')
2304
1850
  : undefined
2305
1851
  }
2306
1852
  {...mingoWelcome}
2307
- // History now lives in its own list view (the wide rail or
2308
- // the narrow "Current Chats" list), so the welcome is always
2309
- // the new-user compose surface: greeting + grid + promo +
2310
- // primary Guide chip.
2311
- hasExistingChats={false}
2312
- // Hover/focus PREVIEWS the action's full prompt as ghost text
2313
- // in the empty composer (see `quickActionPreview` /
2314
- // `ChatInput.previewText`). Wired after the `{...mingoWelcome}`
2315
- // spread so the host can't override it.
1853
+ // NOTE: admin "try-asking chips" (suggestedQueries) are
1854
+ // intentionally NOT surfaced in Mingo mode — they belong to
1855
+ // the Guide-mode empty state only. Mingo shows just the
1856
+ // host-provided `mingoWelcome.quickActions` (via the spread).
1857
+ // Derived internally (returning-user variation) — placed
1858
+ // after the spread so it can't be overridden by the prop.
1859
+ hasExistingChats={dialogs.length > 0}
1860
+ isLoadingHistory={dialogsInitialLoading}
1861
+ loadError={dialogsLoadError}
1862
+ onRetry={reloadDialogs}
1863
+ historySearchable={!!mingoCaps.onSearchChange}
1864
+ // Hover/focus PREVIEWS the action's full prompt as ghost
1865
+ // text in the empty composer — same behaviour as Guide-mode
1866
+ // chips (see `quickActionPreview` / `ChatInput.previewText`).
1867
+ // Wired after the `{...mingoWelcome}` spread so the host
1868
+ // can't override it.
2316
1869
  onQuickActionHover={(action) =>
2317
1870
  setQuickActionPreview(action.prompt ?? action.label)
2318
1871
  }
2319
1872
  onQuickActionHoverEnd={() => setQuickActionPreview(null)}
1873
+ dialogHistory={
1874
+ // Keep the history (and its search bar) mounted during an
1875
+ // active search even at 0 results — otherwise a no-match
1876
+ // query would unmount the bar and flash the new-user
1877
+ // greeting. `MingoWelcome` renders `dialogHistory` with top
1878
+ // priority, so this wins over the greeting.
1879
+ dialogs.length > 0 || !!mingoCaps.searchQuery ? (
1880
+ <MingoChatHistory
1881
+ dialogs={dialogs}
1882
+ onSelectDialog={handleSelectDialog}
1883
+ onRequestRename={
1884
+ mingoCaps.canRename ? setRenameTarget : undefined
1885
+ }
1886
+ onRequestArchive={
1887
+ mingoCaps.canArchive ? setArchiveTarget : undefined
1888
+ }
1889
+ searchQuery={mingoCaps.searchQuery}
1890
+ onSearchChange={mingoCaps.onSearchChange}
1891
+ hasMore={hasMoreDialogs}
1892
+ isLoadingMore={isDialogsLoading && dialogs.length > 0}
1893
+ onLoadMore={() => {
1894
+ void loadMoreDialogs()
1895
+ }}
1896
+ />
1897
+ ) : undefined
1898
+ }
2320
1899
  />
2321
1900
  ) : (
2322
1901
  /* Figma node 7532:328214 — Guide-mode empty state: greeting
@@ -2460,8 +2039,6 @@ function EmbeddableChatInner({
2460
2039
  breakdown: currentUsageBreakdown ?? undefined,
2461
2040
  }}
2462
2041
  />
2463
- </>
2464
- )}
2465
2042
  </div>
2466
2043
  </div>
2467
2044
  </div>