@flamingo-stack/openframe-frontend-core 0.0.704 → 0.0.705

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 (90) hide show
  1. package/dist/{chunk-MGGGBHYD.cjs → chunk-2QEUFXNS.cjs} +10 -10
  2. package/dist/{chunk-LDPEQGKT.cjs → chunk-3MX5S4CF.cjs} +132 -80
  3. package/dist/{chunk-UK62YTH6.cjs → chunk-4I45Y3DN.cjs} +2 -2
  4. package/dist/{chunk-AMYY3PNL.cjs → chunk-4KX6OED2.cjs} +42 -42
  5. package/dist/{chunk-4DQMTBMK.cjs → chunk-6NJKKO73.cjs} +11 -11
  6. package/dist/{chunk-JEYRXT5L.js → chunk-6SEANMOU.js} +7 -7
  7. package/dist/{chunk-C7XGRLT5.cjs → chunk-7LS22BZU.cjs} +6 -6
  8. package/dist/{chunk-2JDVL7UV.js → chunk-7VVFKJY5.js} +70 -18
  9. package/dist/{chunk-7JIL3WOH.cjs → chunk-A75QY6CD.cjs} +14 -14
  10. package/dist/{chunk-VTASHWER.js → chunk-AQWZNP6A.js} +3 -3
  11. package/dist/{chunk-ZQFZSFQI.js → chunk-DENB2XJC.js} +2 -2
  12. package/dist/{chunk-FXENTYE5.js → chunk-DW7G4KG3.js} +1 -1
  13. package/dist/{chunk-I7DFUQMN.js → chunk-F6AFPNDF.js} +5 -6
  14. package/dist/{chunk-5QJDM6UV.js → chunk-F6I6XZMN.js} +3 -3
  15. package/dist/{chunk-4KGXLVCF.js → chunk-FCGC42TS.js} +3 -3
  16. package/dist/{chunk-2G546OND.js → chunk-G5A3PSPV.js} +19 -7
  17. package/dist/{chunk-N3TO7JV3.cjs → chunk-HPWWNJE7.cjs} +49 -49
  18. package/dist/{chunk-MI3JUJ75.cjs → chunk-IHERL5V4.cjs} +21 -21
  19. package/dist/{chunk-XIFOBQRQ.cjs → chunk-IKTQ3ZAG.cjs} +4 -4
  20. package/dist/{chunk-262YQFOS.cjs → chunk-JEZKGCZI.cjs} +24 -24
  21. package/dist/{chunk-L3IC42IF.cjs → chunk-KOWVJD4V.cjs} +17 -18
  22. package/dist/{chunk-IHU5ANUY.js → chunk-MKXO4Q5P.js} +1 -1
  23. package/dist/{chunk-IZGDG5X6.cjs → chunk-OUY3HHW6.cjs} +57 -45
  24. package/dist/{chunk-WUJNYGO2.js → chunk-PDWVPI3S.js} +0 -4
  25. package/dist/{chunk-ERCV6HHJ.js → chunk-PN2RKPDF.js} +4 -4
  26. package/dist/{chunk-7LFIKJX2.js → chunk-QFXRPRFT.js} +2 -2
  27. package/dist/{chunk-NRLJUBCS.js → chunk-RBDP4WDE.js} +2 -2
  28. package/dist/{chunk-C3BWIU7R.cjs → chunk-TYWLNCNH.cjs} +1 -5
  29. package/dist/{chunk-XATTBO5X.js → chunk-VA5T32XA.js} +1 -1
  30. package/dist/{chunk-LTSTC3N5.cjs → chunk-VESKITRQ.cjs} +379 -379
  31. package/dist/{chunk-HFJW24JJ.cjs → chunk-WA6GUYQ4.cjs} +79 -79
  32. package/dist/{chunk-QVSFBSWV.js → chunk-X5MQ7JRP.js} +4 -4
  33. package/dist/{chunk-XJKZ4IKT.cjs → chunk-YFBM2K6X.cjs} +4 -4
  34. package/dist/{chunk-6WQIUQUC.js → chunk-YHM5AWTT.js} +5 -5
  35. package/dist/{chunk-SYXQLETR.cjs → chunk-ZSAGLQOD.cjs} +9 -9
  36. package/dist/{chunk-HJAYVKWQ.js → chunk-ZSAO2HBW.js} +6 -6
  37. package/dist/components/case-studies/index.cjs +17 -17
  38. package/dist/components/case-studies/index.js +5 -5
  39. package/dist/components/chat/chat-input.d.ts.map +1 -1
  40. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  41. package/dist/components/chat/embeddable-chat.d.ts +8 -0
  42. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  43. package/dist/components/chat/index.cjs +5 -3
  44. package/dist/components/chat/index.d.ts +1 -1
  45. package/dist/components/chat/index.d.ts.map +1 -1
  46. package/dist/components/chat/index.js +4 -2
  47. package/dist/components/chat/mingo-chat-rail.d.ts +3 -1
  48. package/dist/components/chat/mingo-chat-rail.d.ts.map +1 -1
  49. package/dist/components/contact/index.cjs +6 -6
  50. package/dist/components/contact/index.js +5 -5
  51. package/dist/components/docs/index.cjs +9 -9
  52. package/dist/components/docs/index.js +8 -8
  53. package/dist/components/embeds/index.cjs +6 -6
  54. package/dist/components/embeds/index.js +5 -5
  55. package/dist/components/faq/index.cjs +6 -6
  56. package/dist/components/faq/index.js +5 -5
  57. package/dist/components/features/index.cjs +5 -5
  58. package/dist/components/features/index.js +4 -4
  59. package/dist/components/help-center-pages/index.cjs +81 -81
  60. package/dist/components/help-center-pages/index.js +16 -16
  61. package/dist/components/index.cjs +51 -47
  62. package/dist/components/index.js +19 -15
  63. package/dist/components/meeting-scheduler/index.cjs +32 -32
  64. package/dist/components/meeting-scheduler/index.js +4 -4
  65. package/dist/components/navigation/app-layout-side-panel.d.ts +18 -1
  66. package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -1
  67. package/dist/components/navigation/index.cjs +10 -8
  68. package/dist/components/navigation/index.d.ts +1 -0
  69. package/dist/components/navigation/index.d.ts.map +1 -1
  70. package/dist/components/navigation/index.js +9 -7
  71. package/dist/components/onboarding-guides/index.cjs +8 -8
  72. package/dist/components/onboarding-guides/index.js +7 -7
  73. package/dist/components/tickets/index.cjs +9 -9
  74. package/dist/components/tickets/index.js +8 -8
  75. package/dist/components/ui/index.cjs +7 -5
  76. package/dist/components/ui/index.js +6 -4
  77. package/dist/hooks/index.cjs +3 -3
  78. package/dist/hooks/index.js +2 -2
  79. package/dist/index.cjs +14 -10
  80. package/dist/index.js +12 -8
  81. package/package.json +1 -1
  82. package/src/components/chat/chat-input.tsx +5 -1
  83. package/src/components/chat/chat-message-enhanced.tsx +4 -3
  84. package/src/components/chat/embeddable-chat.tsx +64 -6
  85. package/src/components/chat/index.ts +1 -0
  86. package/src/components/chat/mingo-chat-rail.tsx +24 -7
  87. package/src/components/navigation/app-layout-side-panel.tsx +30 -2
  88. package/src/components/navigation/index.ts +1 -0
  89. package/src/components/platform/SoftwareSourceBadge.tsx +1 -1
  90. package/src/styles/app-globals.css +13 -0
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  SoftwareInfo,
10
10
  SoftwareSourceBadge,
11
11
  ToolBadge
12
- } from "./chunk-I7DFUQMN.js";
12
+ } from "./chunk-F6AFPNDF.js";
13
13
  import {
14
14
  AppHeader,
15
15
  AppLayout,
@@ -26,17 +26,18 @@ import {
26
26
  NAVIGATION_SIDEBAR_WIDTH_VAR,
27
27
  NavigationItemBadge,
28
28
  NavigationSidebar,
29
+ SIDE_PANEL_FRAME_WIDTH,
29
30
  SlidingSidebar,
30
31
  TicketAlertsButton,
31
32
  TopNavigation
32
- } from "./chunk-2G546OND.js";
33
+ } from "./chunk-G5A3PSPV.js";
33
34
  import {
34
35
  MobileNavigationDropdown,
35
36
  MultiLevelNavigation,
36
37
  StickySectionNav,
37
38
  useSectionNavigation
38
- } from "./chunk-ZQFZSFQI.js";
39
- import "./chunk-7LFIKJX2.js";
39
+ } from "./chunk-DENB2XJC.js";
40
+ import "./chunk-QFXRPRFT.js";
40
41
  import {
41
42
  AIEnrichButton,
42
43
  AIEnrichSection,
@@ -665,7 +666,7 @@ import {
665
666
  withFirstTouchAttribution,
666
667
  withWalkthroughOpenParam,
667
668
  writeDismissCookie
668
- } from "./chunk-4KGXLVCF.js";
669
+ } from "./chunk-FCGC42TS.js";
669
670
  import "./chunk-LQYXMEYF.js";
670
671
  import "./chunk-4JPLYOS6.js";
671
672
  import {
@@ -783,7 +784,7 @@ import {
783
784
  validateSchema,
784
785
  validateVariables,
785
786
  variablesToUrlParams
786
- } from "./chunk-IHU5ANUY.js";
787
+ } from "./chunk-MKXO4Q5P.js";
787
788
  import "./chunk-HFL2F4RM.js";
788
789
  import {
789
790
  CONTENT_REF_GROUPS,
@@ -937,6 +938,7 @@ import {
937
938
  MESSAGE_ROLE,
938
939
  MESSAGE_TYPE,
939
940
  MINGO_V2_RAIL_WIDTH,
941
+ MINGO_V2_SPLIT_WIDTH,
940
942
  MUX_IMAGE_ORIGIN,
941
943
  MUX_STREAM_ORIGIN,
942
944
  MarkdownEngine,
@@ -1204,7 +1206,7 @@ import {
1204
1206
  useVideoOriginPreconnect,
1205
1207
  useVideoWarmup,
1206
1208
  usesCanonicalStatusStyle
1207
- } from "./chunk-2JDVL7UV.js";
1209
+ } from "./chunk-7VVFKJY5.js";
1208
1210
  import {
1209
1211
  HEADING_EMOJI_RE,
1210
1212
  HUB_HEADER_OFFSET_PX,
@@ -1246,7 +1248,7 @@ import {
1246
1248
  usePrefersReducedMotionState,
1247
1249
  useResetOnPageHidden,
1248
1250
  useSuppressCloneFocus
1249
- } from "./chunk-WUJNYGO2.js";
1251
+ } from "./chunk-PDWVPI3S.js";
1250
1252
  import "./chunk-GY4IZG22.js";
1251
1253
  import {
1252
1254
  AboutIcon,
@@ -2447,6 +2449,7 @@ export {
2447
2449
  MESSAGE_TYPE,
2448
2450
  MINGO_APPROVAL_REQUEST_TYPE,
2449
2451
  MINGO_V2_RAIL_WIDTH,
2452
+ MINGO_V2_SPLIT_WIDTH,
2450
2453
  MOBILE_NAV_PANEL_ID,
2451
2454
  MUX_IMAGE_ORIGIN,
2452
2455
  MUX_STREAM_ORIGIN,
@@ -2705,6 +2708,7 @@ export {
2705
2708
  SEO_DESCRIPTION_MAX_LENGTH,
2706
2709
  SEO_TITLE_MAX_LENGTH,
2707
2710
  SHELL_TYPES,
2711
+ SIDE_PANEL_FRAME_WIDTH,
2708
2712
  SLOT_STICKINESS,
2709
2713
  SOCIAL_PLATFORM_ALIASES,
2710
2714
  SOCIAL_PLATFORM_ALT_HOSTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.704",
3
+ "version": "0.0.705",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -631,9 +631,12 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>((allProps, ref) => {
631
631
  <div className="relative min-w-0 flex-1">
632
632
  {/* No chip-line override on the placeholder: it only renders while
633
633
  the draft is EMPTY, so the editor is always on its plain line
634
- box here. */}
634
+ box here. `data-editor-ghost` (here and on the preview) pins
635
+ both overlays to 16px on phones alongside `data-editor` — the
636
+ iOS focus-zoom rule in app-globals.css. */}
635
637
  {isEmpty && !showPreview && (
636
638
  <span
639
+ data-editor-ghost
637
640
  className={cn(
638
641
  // One line, clipped with an ellipsis (Figma 954:28455): the
639
642
  // disabled copy is a sentence, and the panel's narrowest
@@ -659,6 +662,7 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>((allProps, ref) => {
659
662
  editor. */}
660
663
  {showPreview && (
661
664
  <p
665
+ data-editor-ghost
662
666
  className={cn(
663
667
  'pointer-events-none absolute inset-x-0 top-0 m-0 select-none truncate text-ods-text-secondary text-h4',
664
668
  hasChips && '!leading-9',
@@ -522,9 +522,10 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
522
522
  // its own short line above the message (on the right for the user), never
523
523
  // beside it: a face in the row indents every line of the message by its
524
524
  // width. The NAME is written only where it is the information itself: a
525
- // system line and a human technician. Every face carries its name for
526
- // assistive tech and on hover.
527
- const showName = isSystem || authorType === 'admin';
525
+ // system line and a human technician answering. Mingo sends the admin's
526
+ // own turns as `admin` too; those are the user's words and draw as such.
527
+ // Every face carries its name for assistive tech and on hover.
528
+ const showName = isSystem || (authorType === 'admin' && !isUser);
528
529
  const displayName = name || (isUser ? 'User' : assistantType === 'mingo' ? 'Mingo' : 'Fae');
529
530
  const faceBox = 'h-5 w-5 flex-shrink-0';
530
531
  const face: ReactNode =
@@ -131,6 +131,8 @@ const HISTORY_RAIL_WIDTH = 320;
131
131
  /** The v2 chat list (`MingoChatRail`, Figma `chat-sidebar`): the narrowest the v2 chat draws. */
132
132
  export const MINGO_V2_RAIL_WIDTH = 296;
133
133
  const CHAT_BLOCK_MIN_WIDTH = 400;
134
+ /** Mingo v2: the list and the chat side by side at their narrowest. */
135
+ export const MINGO_V2_SPLIT_WIDTH = MINGO_V2_RAIL_WIDTH + CHAT_BLOCK_MIN_WIDTH;
134
136
  const SPLIT_MIN_WIDTH = HISTORY_RAIL_WIDTH + CHAT_BLOCK_MIN_WIDTH;
135
137
 
136
138
  // Desktop drawer is this fraction of the viewport width (clamped to the
@@ -364,6 +366,12 @@ export interface EmbeddableChatProps {
364
366
  * the open chat, which stays open.
365
367
  */
366
368
  collapseTo?: 'list' | 'column';
369
+ /**
370
+ * v2: the |← control on the chat list while the panel shows the list alone.
371
+ * The host widens its panel; the chat opens the conversation open last (or
372
+ * the newest in the list), or a new chat when there is none.
373
+ */
374
+ onExpand?: () => void;
367
375
 
368
376
  /** v2: the status glyph at the end of a chat's row (working / unread / approval). */
369
377
  dialogStatusOf?: (dialog: DialogItem) => MingoDialogStatus | undefined;
@@ -1024,6 +1032,7 @@ function EmbeddableChatInner({
1024
1032
  appearance = CHAT_APPEARANCE.CLASSIC,
1025
1033
  onCollapse,
1026
1034
  collapseTo = 'list',
1035
+ onExpand,
1027
1036
  dialogStatusOf,
1028
1037
  userDisplayName,
1029
1038
  userAvatarUrl,
@@ -1601,18 +1610,25 @@ function EmbeddableChatInner({
1601
1610
  [handleNavigationClose],
1602
1611
  );
1603
1612
 
1613
+ // "Ask Mingo" about a row (a card or source chip in the thread, or a page's
1614
+ // `ask-ai:open-with-ref`): asks in the open conversation. Forwarded through a
1615
+ // ref because what it does depends on dialog state declared further down
1616
+ // (`askAboutRef.current` is set next to it: an archived chat is read-only).
1617
+ const askAboutRef = useRef<(reference: ChatRef) => void>(discussRef);
1618
+ const askAbout = useCallback((reference: ChatRef) => askAboutRef.current(reference), []);
1619
+
1604
1620
  // Host-provided renderer for inline entity cards — routes through the
1605
1621
  // shared dispatcher in lib's `entity-cards/dispatch.tsx`.
1606
1622
  const renderEntityCard = useCallback(
1607
1623
  (reference: ChatRef): React.ReactNode =>
1608
1624
  renderChatInlineEntityCard(reference, {
1609
- onDiscuss: discussRef,
1625
+ onDiscuss: askAbout,
1610
1626
  onDisplay: displayRef,
1611
1627
  baseRoute: resolvedBaseRoute,
1612
1628
  chipBasePlatform,
1613
1629
  extras,
1614
1630
  }),
1615
- [discussRef, displayRef, resolvedBaseRoute, chipBasePlatform, extras],
1631
+ [askAbout, displayRef, resolvedBaseRoute, chipBasePlatform, extras],
1616
1632
  );
1617
1633
 
1618
1634
  // Stable assistant-icon element. `<ChatMessageList>` forwards this prop to
@@ -1832,11 +1848,11 @@ function EmbeddableChatInner({
1832
1848
  ).detail;
1833
1849
  if (!detail || detail.source !== source) return;
1834
1850
  setIsOpen(true);
1835
- setTimeout(() => discussRef(detail.ref as ChatRef), 0);
1851
+ setTimeout(() => askAbout(detail.ref as ChatRef), 0);
1836
1852
  };
1837
1853
  window.addEventListener('ask-ai:open-with-ref', handler);
1838
1854
  return () => window.removeEventListener('ask-ai:open-with-ref', handler);
1839
- }, [source, discussRef, setIsOpen]);
1855
+ }, [source, askAbout, setIsOpen]);
1840
1856
 
1841
1857
  // Listen for plain "open chat" events (no row context). Fired by the
1842
1858
  // header MingoAiButton. Same strict source filter as `ask-ai:open-with-ref`
@@ -1960,12 +1976,12 @@ function EmbeddableChatInner({
1960
1976
  baseRoute={resolvedBaseRoute}
1961
1977
  chipBasePlatform={chipBasePlatform}
1962
1978
  onClose={handleNavigationClose}
1963
- onDiscuss={discussRef}
1979
+ onDiscuss={askAbout}
1964
1980
  />
1965
1981
  </div>
1966
1982
  );
1967
1983
  },
1968
- [chatLoading, messages.length, resolvedBaseRoute, chipBasePlatform, handleNavigationClose, discussRef],
1984
+ [chatLoading, messages.length, resolvedBaseRoute, chipBasePlatform, handleNavigationClose, askAbout],
1969
1985
  );
1970
1986
 
1971
1987
  // Host node for in-panel Radix portals (see the body wrapper below).
@@ -2176,6 +2192,37 @@ function EmbeddableChatInner({
2176
2192
  resetToNewChat();
2177
2193
  }, [resetToNewChat]);
2178
2194
 
2195
+ // "Ask Mingo" from an archived chat: the conversation is read-only (the
2196
+ // backend refuses writes), so the question starts a new chat instead. It is
2197
+ // queued until the host has let go of the archived dialog: until then its
2198
+ // `discussRef` still writes into the dialog it has open.
2199
+ const queuedAskRef = useRef<ChatRef | null>(null);
2200
+ useEffect(() => {
2201
+ askAboutRef.current = reference => {
2202
+ if (!isViewingArchived) {
2203
+ discussRef(reference);
2204
+ return;
2205
+ }
2206
+ queuedAskRef.current = reference;
2207
+ if (archiveOpen) closeArchive();
2208
+ resetToNewChat();
2209
+ setComposeOpen(true);
2210
+ };
2211
+ });
2212
+ useEffect(() => {
2213
+ const reference = queuedAskRef.current;
2214
+ if (!reference || activeDialogId != null) return;
2215
+ queuedAskRef.current = null;
2216
+ discussRef(reference);
2217
+ }, [activeDialogId, discussRef]);
2218
+
2219
+ // The conversation open last, for the |← control: going back to the list
2220
+ // closes it, and widening the panel again should bring it back.
2221
+ const lastDialogIdRef = useRef<string | null>(null);
2222
+ useEffect(() => {
2223
+ if (activeDialogId && !isViewingArchived) lastDialogIdRef.current = activeDialogId;
2224
+ }, [activeDialogId, isViewingArchived]);
2225
+
2179
2226
  // Host prefill (`prefillDraft`) — staged, not written at once. The composer
2180
2227
  // it goes into may only mount on the commit `resetToNewChat` +
2181
2228
  // `setComposeOpen(true)` produce (narrow layout: list → compose; or the
@@ -2654,6 +2701,17 @@ function EmbeddableChatInner({
2654
2701
  <MingoChatRail
2655
2702
  {...mingoRailProps}
2656
2703
  onNewChat={() => setComposeOpen(true)}
2704
+ onExpand={
2705
+ onExpand &&
2706
+ (() => {
2707
+ const last = lastDialogIdRef.current;
2708
+ const target = last && dialogs.some(d => d.id === last) ? last : dialogs[0]?.id;
2709
+ if (archiveOpen) closeArchive();
2710
+ if (target) handleSelectDialog(target);
2711
+ else handleNewChat();
2712
+ onExpand();
2713
+ })
2714
+ }
2657
2715
  className="w-full border-r-0 duration-200 animate-in fade-in-0"
2658
2716
  />
2659
2717
  ) : stackedListView ? (
@@ -99,5 +99,6 @@ export {
99
99
  type EmbeddableChatHandle,
100
100
  type EmbeddableChatProps,
101
101
  MINGO_V2_RAIL_WIDTH,
102
+ MINGO_V2_SPLIT_WIDTH,
102
103
  } from './embeddable-chat';
103
104
  export * from './proxy-credentials-panel';
@@ -4,6 +4,7 @@ import { type ReactNode, useEffect, useRef, useState } from 'react';
4
4
  import { cn } from '../../utils/cn';
5
5
  import {
6
6
  AlertCircleIcon,
7
+ Arrow02LeftIcon,
7
8
  ChatPlusIcon,
8
9
  ChatQuestionIcon,
9
10
  ChatsIcon,
@@ -69,6 +70,8 @@ export interface MingoChatRailProps {
69
70
  /** The list failed to load with nothing cached. */
70
71
  loadError?: boolean;
71
72
  onRetry?: () => void;
73
+ /** The |← control beside the logo: widen the panel to the list and the chat. */
74
+ onExpand?: () => void;
72
75
  className?: string;
73
76
  }
74
77
 
@@ -113,6 +116,7 @@ export function MingoChatRail({
113
116
  isLoadingHistory = false,
114
117
  loadError = false,
115
118
  onRetry,
119
+ onExpand,
116
120
  className,
117
121
  }: MingoChatRailProps) {
118
122
  const [searchOpen, setSearchOpen] = useState(!!searchQuery?.trim());
@@ -251,13 +255,26 @@ export function MingoChatRail({
251
255
  data-accent="mingo"
252
256
  aria-label="Mingo chats"
253
257
  logo={
254
- <MingoIcon
255
- color="currentColor"
256
- eyesColor="var(--ods-flamingo-cyan-base)"
257
- cornerColor="var(--ods-flamingo-cyan-base)"
258
- className="size-6 text-ods-text-primary"
259
- aria-hidden
260
- />
258
+ <div className="flex flex-1 items-center justify-between">
259
+ <MingoIcon
260
+ color="currentColor"
261
+ eyesColor="var(--ods-flamingo-cyan-base)"
262
+ cornerColor="var(--ods-flamingo-cyan-base)"
263
+ className="size-6 text-ods-text-primary"
264
+ aria-hidden
265
+ />
266
+ {onExpand && (
267
+ <Button
268
+ variant="transparent"
269
+ size="icon"
270
+ aria-label="Expand chat"
271
+ onClick={onExpand}
272
+ className="size-6 p-0 text-ods-text-secondary hover:text-ods-text-primary md:size-6"
273
+ >
274
+ <Arrow02LeftIcon size={24} />
275
+ </Button>
276
+ )}
277
+ </div>
261
278
  }
262
279
  items={items}
263
280
  sectionLabel="Your Current Chats"
@@ -44,13 +44,23 @@ export interface AppLayoutSidePanelRenderState {
44
44
  collapsesTo: 'column' | 'minimum' | null;
45
45
  /** Step the panel back (see `collapsesTo`), animated. */
46
46
  collapse: () => void;
47
+ /** Docked at its minimum with room for more: `expand` widens it. */
48
+ canExpand: boolean;
49
+ /** Widen the panel from its minimum to `defaultWidth` (as much as fits), animated. */
50
+ expand: () => void;
47
51
  }
48
52
 
49
53
  export interface AppLayoutSidePanelConfig {
50
54
  /** Panel body. Told its width so it can choose what fits. */
51
55
  children: (state: AppLayoutSidePanelRenderState) => ReactNode;
52
- /** Narrowest docked width, and the width it starts at: the narrowest its content draws. */
56
+ /** Narrowest docked width: the narrowest its content draws. */
53
57
  minWidth: number;
58
+ /**
59
+ * Width it starts at until the user picks one (a first launch), and the
60
+ * width `expand` takes it back to from its minimum. As much of it as fits:
61
+ * with no room the panel stays narrower. Default `minWidth`.
62
+ */
63
+ defaultWidth?: number;
54
64
  /** Narrowest the content may get before the panel takes over. Default 400. */
55
65
  minContentWidth?: number;
56
66
  /** localStorage key for the chosen width. Without it the width lasts the session. */
@@ -75,6 +85,11 @@ export interface AppLayoutSidePanelConfig {
75
85
 
76
86
  /** Inset of the docked card from the window edge and the header. */
77
87
  export const SIDE_PANEL_INSET = 16;
88
+ /**
89
+ * The docked card's 1px border on both sides: its body is this much narrower
90
+ * than the panel's width. Add it to a content width to size the panel by it.
91
+ */
92
+ export const SIDE_PANEL_FRAME_WIDTH = 2;
78
93
  const MOBILE_QUERY = `not all and ${breakpoints.md}`;
79
94
  const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';
80
95
  /** Length of the docked <-> full morph; keep in step with `duration-300` below. */
@@ -228,6 +243,8 @@ export interface AppLayoutSidePanelState extends SidePanelLayout {
228
243
  endMorph: () => void;
229
244
  collapsesTo: AppLayoutSidePanelRenderState['collapsesTo'];
230
245
  collapse: () => void;
246
+ canExpand: boolean;
247
+ expand: () => void;
231
248
  toggle: () => void;
232
249
  close: () => void;
233
250
  resize: (next: number) => void;
@@ -256,6 +273,7 @@ export function useAppLayoutSidePanel(
256
273
  const minContentWidth = config?.minContentWidth ?? 400;
257
274
  const collapsed = config?.collapsed ?? false;
258
275
  const minimum: SidePanelSize = { width: minWidth, expanded: false };
276
+ const defaultWidth = Math.max(minWidth, config?.defaultWidth ?? minWidth);
259
277
 
260
278
  const [rowWidth, setRowWidth] = useState(0);
261
279
  useIsomorphicLayoutEffect(() => {
@@ -271,7 +289,7 @@ export function useAppLayoutSidePanel(
271
289
  const reduceMotion = useMediaQuery(REDUCED_MOTION_QUERY) === true;
272
290
  const [morph, setMorph] = useState<SidePanelMorph | null>(null);
273
291
  const [liveWidth, setLiveWidth] = useState<number | null>(null);
274
- const [storedSize, setStoredSize] = useStoredSize(config?.storageKey, minimum);
292
+ const [storedSize, setStoredSize] = useStoredSize(config?.storageKey, { width: defaultWidth, expanded: false });
275
293
  const [ownIsOpen, setOwnIsOpen] = useState(false);
276
294
  const isOpen = config?.open ?? ownIsOpen;
277
295
  const onOpenChange = config?.onOpenChange;
@@ -340,6 +358,8 @@ export function useAppLayoutSidePanel(
340
358
  let collapsesTo: AppLayoutSidePanelRenderState['collapsesTo'] = null;
341
359
  if (layout.mode === 'full' && layout.canDock) collapsesTo = layout.dockedWidth > minWidth ? 'column' : 'minimum';
342
360
  else if (layout.mode === 'docked' && layout.width > minWidth) collapsesTo = 'minimum';
361
+ const canExpand =
362
+ layout.mode === 'docked' && layout.width <= minWidth && defaultWidth > minWidth && layout.maxDockedWidth > minWidth;
343
363
 
344
364
  return {
345
365
  ...layout,
@@ -362,6 +382,12 @@ export function useAppLayoutSidePanel(
362
382
  setSize({ width: minWidth, expanded: false });
363
383
  }
364
384
  },
385
+ canExpand,
386
+ expand: () => {
387
+ if (!canExpand) return;
388
+ animate('narrow');
389
+ dock(defaultWidth);
390
+ },
365
391
  toggle: () => setIsOpen(!isOpen),
366
392
  close: () => setIsOpen(false),
367
393
  resize,
@@ -586,6 +612,8 @@ export function AppLayoutSidePanel({ config, state }: AppLayoutSidePanelProps) {
586
612
  close,
587
613
  collapsesTo: state.collapsesTo,
588
614
  collapse: state.collapse,
615
+ canExpand: state.canExpand,
616
+ expand: state.expand,
589
617
  })}
590
618
  </div>
591
619
  </div>
@@ -35,6 +35,7 @@ export type {
35
35
  AppLayoutSidePanelMode,
36
36
  AppLayoutSidePanelRenderState,
37
37
  } from './app-layout-side-panel';
38
+ export { SIDE_PANEL_FRAME_WIDTH } from './app-layout-side-panel';
38
39
 
39
40
  export {
40
41
  AppLayoutDrawer,
@@ -33,7 +33,7 @@
33
33
  */
34
34
 
35
35
  import type React from 'react';
36
- import { cn } from '../../utils/common';
36
+ import { cn } from '../../utils/cn';
37
37
  import { PackageIcon, WindowsIcon, MacOSIcon, LinuxIcon } from '../icons';
38
38
 
39
39
  export type SoftwareSource =
@@ -154,6 +154,19 @@ body[data-scroll-locked][data-scroll-locked] {
154
154
  transform-origin: left top;
155
155
  transform: scale(1);
156
156
  }
157
+
158
+ /* The chat composer (chat-input.tsx) is a contenteditable div, not a
159
+ textarea, and WebKit's focus auto-zoom keys off its font size the same
160
+ way. Keyed on its own data-editor rather than [contenteditable]: a global
161
+ match would also resize consumers' editors (CodeMirror's .cm-content) and
162
+ drop the composer back to 14px while disabled. Its placeholder and hover
163
+ preview overlay it as siblings, so they are pinned with it or the text
164
+ would change size on the first keystroke. Mention chips stay small only
165
+ because Tag sets its own text-h5. */
166
+ [data-editor],
167
+ [data-editor-ghost] {
168
+ font-size: 16px !important;
169
+ }
157
170
  }
158
171
 
159
172
  /* Shadcn/ui HSL CSS variables for Tailwind compatibility */