@flamingo-stack/openframe-frontend-core 0.0.438 → 0.0.439

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-LR6A67KZ.js → chunk-2ELBNZJ3.js} +2 -2
  2. package/dist/{chunk-6R5SZPAQ.cjs → chunk-3WJQL4DN.cjs} +15 -15
  3. package/dist/{chunk-6R5SZPAQ.cjs.map → chunk-3WJQL4DN.cjs.map} +1 -1
  4. package/dist/{chunk-KBFQF4AV.js → chunk-7WM77CSW.js} +2 -2
  5. package/dist/{chunk-B5SGPIJ4.cjs → chunk-AUJXLVXA.cjs} +37 -37
  6. package/dist/{chunk-B5SGPIJ4.cjs.map → chunk-AUJXLVXA.cjs.map} +1 -1
  7. package/dist/{chunk-X66KRL5G.cjs → chunk-C3FH53AP.cjs} +11 -11
  8. package/dist/{chunk-X66KRL5G.cjs.map → chunk-C3FH53AP.cjs.map} +1 -1
  9. package/dist/{chunk-W2PRXXUD.js → chunk-COMBYYQY.js} +2 -2
  10. package/dist/{chunk-RTHOCS3S.js → chunk-EGRZHR7J.js} +2 -2
  11. package/dist/{chunk-D5RTZ2WN.js → chunk-F7YVI54P.js} +4 -4
  12. package/dist/{chunk-5D5SJOLE.js → chunk-G5FALXNO.js} +4 -4
  13. package/dist/{chunk-2DCP2TIF.cjs → chunk-HTVVXVJL.cjs} +1966 -1490
  14. package/dist/chunk-HTVVXVJL.cjs.map +1 -0
  15. package/dist/{chunk-WF7VGEYR.cjs → chunk-IAN7GUIV.cjs} +26 -26
  16. package/dist/{chunk-WF7VGEYR.cjs.map → chunk-IAN7GUIV.cjs.map} +1 -1
  17. package/dist/{chunk-SYTL2VIU.cjs → chunk-M3LD6DNE.cjs} +7 -7
  18. package/dist/{chunk-SYTL2VIU.cjs.map → chunk-M3LD6DNE.cjs.map} +1 -1
  19. package/dist/{chunk-YKP2F2NC.cjs → chunk-M6QGB6DC.cjs} +26 -26
  20. package/dist/{chunk-YKP2F2NC.cjs.map → chunk-M6QGB6DC.cjs.map} +1 -1
  21. package/dist/{chunk-6ETVJ5ON.js → chunk-NQQ4NLUT.js} +6577 -6101
  22. package/dist/chunk-NQQ4NLUT.js.map +1 -0
  23. package/dist/{chunk-A25RPWYF.js → chunk-P7PISHM5.js} +2 -2
  24. package/dist/{chunk-44HNH57W.js → chunk-PBTY7AH5.js} +4 -4
  25. package/dist/{chunk-ZEY7A3MP.js → chunk-PEIR7D25.js} +2 -2
  26. package/dist/{chunk-DTYPYYKD.cjs → chunk-R46QEE3R.cjs} +59 -59
  27. package/dist/{chunk-DTYPYYKD.cjs.map → chunk-R46QEE3R.cjs.map} +1 -1
  28. package/dist/{chunk-H2TQ44VD.cjs → chunk-RQQZF6G6.cjs} +31 -31
  29. package/dist/{chunk-H2TQ44VD.cjs.map → chunk-RQQZF6G6.cjs.map} +1 -1
  30. package/dist/{chunk-CMP56RUC.cjs → chunk-VWGF6IFR.cjs} +62 -62
  31. package/dist/{chunk-CMP56RUC.cjs.map → chunk-VWGF6IFR.cjs.map} +1 -1
  32. package/dist/{chunk-POFDZH2C.js → chunk-WAGTZVQN.js} +4 -4
  33. package/dist/{chunk-XFSDFO6U.js → chunk-WPVBTVI3.js} +2 -2
  34. package/dist/{chunk-VYG2CJFJ.cjs → chunk-WZ6TCAYQ.cjs} +9 -9
  35. package/dist/{chunk-VYG2CJFJ.cjs.map → chunk-WZ6TCAYQ.cjs.map} +1 -1
  36. package/dist/{chunk-PCMDRDYX.cjs → chunk-ZMUFTGL7.cjs} +5 -5
  37. package/dist/{chunk-PCMDRDYX.cjs.map → chunk-ZMUFTGL7.cjs.map} +1 -1
  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 +28 -8
  41. package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
  42. package/dist/components/chat/chat-header-icon-button.d.ts +6 -2
  43. package/dist/components/chat/chat-header-icon-button.d.ts.map +1 -1
  44. package/dist/components/chat/chat-header-search-field.d.ts +25 -0
  45. package/dist/components/chat/chat-header-search-field.d.ts.map +1 -0
  46. package/dist/components/chat/chat-list-empty-state.d.ts +20 -0
  47. package/dist/components/chat/chat-list-empty-state.d.ts.map +1 -0
  48. package/dist/components/chat/chat-panel-header.d.ts +19 -9
  49. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  50. package/dist/components/chat/embeddable-chat.d.ts +15 -6
  51. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  52. package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts +2 -0
  53. package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts.map +1 -1
  54. package/dist/components/chat/index.cjs +4 -2
  55. package/dist/components/chat/index.cjs.map +1 -1
  56. package/dist/components/chat/index.d.ts +2 -0
  57. package/dist/components/chat/index.d.ts.map +1 -1
  58. package/dist/components/chat/index.js +5 -3
  59. package/dist/components/chat/mingo-chat-history.d.ts +5 -10
  60. package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
  61. package/dist/components/chat/mingo-history-rail.d.ts +54 -0
  62. package/dist/components/chat/mingo-history-rail.d.ts.map +1 -0
  63. package/dist/components/chat/mingo-welcome.d.ts +6 -25
  64. package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
  65. package/dist/components/contact/index.cjs +3 -3
  66. package/dist/components/contact/index.js +2 -2
  67. package/dist/components/docs/index.cjs +5 -5
  68. package/dist/components/docs/index.js +4 -4
  69. package/dist/components/embeds/index.cjs +3 -3
  70. package/dist/components/embeds/index.js +2 -2
  71. package/dist/components/faq/index.cjs +3 -3
  72. package/dist/components/faq/index.js +2 -2
  73. package/dist/components/features/index.cjs +2 -2
  74. package/dist/components/features/index.js +1 -1
  75. package/dist/components/help-center-pages/index.cjs +20 -20
  76. package/dist/components/help-center-pages/index.js +11 -11
  77. package/dist/components/index.cjs +130 -128
  78. package/dist/components/index.cjs.map +1 -1
  79. package/dist/components/index.js +13 -11
  80. package/dist/components/index.js.map +1 -1
  81. package/dist/components/navigation/index.cjs +2 -2
  82. package/dist/components/navigation/index.js +1 -1
  83. package/dist/components/onboarding-guides/index.cjs +5 -5
  84. package/dist/components/onboarding-guides/index.js +4 -4
  85. package/dist/components/related-content/index.cjs +3 -3
  86. package/dist/components/related-content/index.js +2 -2
  87. package/dist/components/tickets/index.cjs +5 -5
  88. package/dist/components/tickets/index.js +4 -4
  89. package/dist/components/ui/index.cjs +4 -2
  90. package/dist/components/ui/index.cjs.map +1 -1
  91. package/dist/components/ui/index.js +5 -3
  92. package/dist/index.cjs +4 -2
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.js +5 -3
  95. package/package.json +1 -1
  96. package/src/components/chat/chat-archive-page.tsx +89 -73
  97. package/src/components/chat/chat-header-icon-button.tsx +12 -5
  98. package/src/components/chat/chat-header-search-field.tsx +144 -0
  99. package/src/components/chat/chat-list-empty-state.tsx +44 -0
  100. package/src/components/chat/chat-panel-header.tsx +76 -29
  101. package/src/components/chat/embeddable-chat.tsx +548 -125
  102. package/src/components/chat/guide-welcome.tsx +4 -4
  103. package/src/components/chat/hooks/use-chat-dialog-manager.ts +59 -5
  104. package/src/components/chat/index.ts +2 -0
  105. package/src/components/chat/mingo-chat-history.tsx +46 -100
  106. package/src/components/chat/mingo-history-rail.tsx +161 -0
  107. package/src/components/chat/mingo-welcome.tsx +17 -98
  108. package/src/stories/MingoWelcome.stories.tsx +2 -11
  109. package/dist/chunk-2DCP2TIF.cjs.map +0 -1
  110. package/dist/chunk-6ETVJ5ON.js.map +0 -1
  111. /package/dist/{chunk-LR6A67KZ.js.map → chunk-2ELBNZJ3.js.map} +0 -0
  112. /package/dist/{chunk-KBFQF4AV.js.map → chunk-7WM77CSW.js.map} +0 -0
  113. /package/dist/{chunk-W2PRXXUD.js.map → chunk-COMBYYQY.js.map} +0 -0
  114. /package/dist/{chunk-RTHOCS3S.js.map → chunk-EGRZHR7J.js.map} +0 -0
  115. /package/dist/{chunk-D5RTZ2WN.js.map → chunk-F7YVI54P.js.map} +0 -0
  116. /package/dist/{chunk-5D5SJOLE.js.map → chunk-G5FALXNO.js.map} +0 -0
  117. /package/dist/{chunk-A25RPWYF.js.map → chunk-P7PISHM5.js.map} +0 -0
  118. /package/dist/{chunk-44HNH57W.js.map → chunk-PBTY7AH5.js.map} +0 -0
  119. /package/dist/{chunk-ZEY7A3MP.js.map → chunk-PEIR7D25.js.map} +0 -0
  120. /package/dist/{chunk-POFDZH2C.js.map → chunk-WAGTZVQN.js.map} +0 -0
  121. /package/dist/{chunk-XFSDFO6U.js.map → chunk-WPVBTVI3.js.map} +0 -0
@@ -31,6 +31,7 @@
31
31
  */
32
32
 
33
33
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
34
+ import { cn } from '../../utils/cn'
34
35
  import { useControllableState } from '@radix-ui/react-use-controllable-state'
35
36
  import * as DialogPrimitive from '@radix-ui/react-dialog'
36
37
  import { usePreventScroll } from '@react-aria/overlays'
@@ -47,12 +48,18 @@ import { EntityIcon } from '../icon-display'
47
48
  import { MingoOnboardingCard } from './mingo-onboarding-card'
48
49
  import { MingoOnboardingCardSkeleton } from './mingo-onboarding-card-skeleton'
49
50
  import { MingoWelcome, type MingoWelcomeProps } from './mingo-welcome'
50
- import { MingoChatHistory } from './mingo-chat-history'
51
+ import { MingoHistoryRail } from './mingo-history-rail'
51
52
  import { GuideWelcome, type GuideWelcomeProps } from './guide-welcome'
52
53
  import { accentFromIdentityIcon, getAgentAccent } from './quick-action-chip'
53
54
  import { GuideModeBanner } from './guide-mode-banner'
54
55
  import { PortalContainerContext } from '../ui/portal-container'
55
- import { ChatPanelHeader } from './chat-panel-header'
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'
56
63
  import { ChatArchivePage } from './chat-archive-page'
57
64
  import { ChatComposer } from './chat-composer'
58
65
  import { useChatDialogManager } from './hooks/use-chat-dialog-manager'
@@ -267,18 +274,27 @@ export interface EmbeddableChatProps {
267
274
  */
268
275
  shell?: 'drawer' | 'none'
269
276
 
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
+
270
287
  /**
271
288
  * Content overrides for the default (Mingo-mode) empty state
272
- * (`<MingoWelcome>`): greeting `title`/`subtitle`, the capability
273
- * `featureCards` grid, the `promo` card, and extra `quickActions` chips.
289
+ * (`<MingoWelcome>`): greeting `title`/`subtitle`, the `promo` card, and
290
+ * extra `quickActions` chips.
274
291
  * Each field falls back to the built-in OpenFrame defaults, so the kit
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.
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.
278
295
  */
279
296
  mingoWelcome?: Omit<
280
297
  MingoWelcomeProps,
281
- | 'userName'
282
298
  | 'onStartGuideChat'
283
299
  | 'hasExistingChats'
284
300
  | 'onQuickActionHover'
@@ -380,6 +396,99 @@ const mentionTokenOf = (key: string, markerByType: Map<string, string>): string
380
396
  */
381
397
  const FALLBACK_TOP_RETRIEVED = 3
382
398
 
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
+
383
492
  /**
384
493
  * Built-in default route for an OpenFrame AI agent's display config — the
385
494
  * lowest-precedence fallback so "agent mode" works with ZERO endpoint wiring.
@@ -778,6 +887,7 @@ function EmbeddableChatInner({
778
887
  aiAgentConfigUrl: aiAgentConfigUrlProp,
779
888
  defaultActiveMode,
780
889
  shell = 'drawer',
890
+ userDisplayName,
781
891
  mingoWelcome,
782
892
  guideWelcome,
783
893
  contextPicker,
@@ -899,22 +1009,17 @@ function EmbeddableChatInner({
899
1009
  [commandsUrl],
900
1010
  )
901
1011
 
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()) ||
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() ||
918
1023
  undefined
919
1024
 
920
1025
  // Synthesize `modes` from legacy props when the new API isn't used.
@@ -1484,6 +1589,7 @@ function EmbeddableChatInner({
1484
1589
  archivedDialogs,
1485
1590
  archivedCursor,
1486
1591
  archivedLoading,
1592
+ archivedPending,
1487
1593
  openArchive,
1488
1594
  closeArchive,
1489
1595
  loadArchivedPage,
@@ -1492,6 +1598,7 @@ function EmbeddableChatInner({
1492
1598
  isOpeningDialog,
1493
1599
  isViewingArchived,
1494
1600
  handleBack,
1601
+ resetToNewChat,
1495
1602
  activeDialog,
1496
1603
  renameTarget,
1497
1604
  setRenameTarget,
@@ -1650,6 +1757,189 @@ function EmbeddableChatInner({
1650
1757
  // Host node for in-panel Radix portals (see the body wrapper below).
1651
1758
  const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null)
1652
1759
 
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
+
1653
1943
  // Chat body — defined once, then rendered inside whichever shell applies.
1654
1944
  // Radix overlays (⋯ menus, tooltips) portal into `portalHost` — a node inside
1655
1945
  // this panel — so they inherit the drawer's stacking context and need only a
@@ -1658,22 +1948,31 @@ function EmbeddableChatInner({
1658
1948
  const body = (
1659
1949
  <PortalContainerContext.Provider value={portalHost}>
1660
1950
  {/* Panel surface depends on state (Figma):
1661
- Mingo empty / returning-user + archive page → grey
1662
- `ods-card` (#212121),
1663
- Guide empty (node 7532:328223) + active conversation → dark
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
1664
1956
  `ods-bg` (#161616).
1665
1957
  The header keeps its own grey `bg-ods-card`; the content and
1666
- footer have no bg and inherit this root, so they flip together. */}
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. */}
1667
1963
  <div
1964
+ ref={panelMeasureRef}
1668
1965
  className={`flex h-full flex-col overflow-hidden transition-colors duration-200 ${
1669
- archiveOpen || (!hasConversation && activeMode === 'mingo')
1966
+ (archiveOpen && !splitActive) || stackedListView
1670
1967
  ? 'bg-ods-card'
1671
1968
  : 'bg-ods-bg'
1672
1969
  } ${previewMode ? 'pointer-events-none select-none' : ''}`}
1673
1970
  >
1674
1971
  {/* Archive-page ↔ chat-panel swap fades in (200ms) to match the
1675
- surface flip both branches share the same view-change feel. */}
1676
- {archiveOpen ? (
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 ? (
1677
1976
  <div
1678
1977
  key="archive-view"
1679
1978
  className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
@@ -1684,6 +1983,7 @@ function EmbeddableChatInner({
1684
1983
  onBack={closeArchive}
1685
1984
  onClose={handleClose}
1686
1985
  isLoading={archivedLoading}
1986
+ isFetching={archivedPending}
1687
1987
  hasMore={archivedCursor != null}
1688
1988
  onLoadMore={() => {
1689
1989
  void loadArchivedPage(archivedCursor ?? undefined)
@@ -1695,54 +1995,143 @@ function EmbeddableChatInner({
1695
1995
  key="chat-view"
1696
1996
  className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
1697
1997
  >
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
- />
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
+ )}
1746
2135
 
1747
2136
  {/* Guide-mode indicator banner (Figma node 7532:328222) —
1748
2137
  full-bleed accent strip below the header. Shown only when
@@ -1761,11 +2150,77 @@ function EmbeddableChatInner({
1761
2150
  places it, mirroring `GuideModeBanner` above. */}
1762
2151
  {activeMode === 'mingo' && mingoContextBanner}
1763
2152
 
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. */}
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>`). */}
1767
2157
  <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
+ )}
1768
2198
  <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
+ <>
1769
2224
 
1770
2225
  <div
1771
2226
  ref={galleryPanelRef}
@@ -1843,59 +2298,25 @@ function EmbeddableChatInner({
1843
2298
  quick-action chips. Guide mode keeps the slash-command
1844
2299
  onboarding list below. */
1845
2300
  <MingoWelcome
1846
- userName={userName}
1847
2301
  onStartGuideChat={
1848
2302
  effectiveModes.guide
1849
2303
  ? () => handleActiveModeChange('guide')
1850
2304
  : undefined
1851
2305
  }
1852
2306
  {...mingoWelcome}
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.
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.
1869
2316
  onQuickActionHover={(action) =>
1870
2317
  setQuickActionPreview(action.prompt ?? action.label)
1871
2318
  }
1872
2319
  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
- }
1899
2320
  />
1900
2321
  ) : (
1901
2322
  /* Figma node 7532:328214 — Guide-mode empty state: greeting
@@ -2039,6 +2460,8 @@ function EmbeddableChatInner({
2039
2460
  breakdown: currentUsageBreakdown ?? undefined,
2040
2461
  }}
2041
2462
  />
2463
+ </>
2464
+ )}
2042
2465
  </div>
2043
2466
  </div>
2044
2467
  </div>