@flamingo-stack/openframe-frontend-core 0.0.458 → 0.0.459-snapshot.20260721100450

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 (129) hide show
  1. package/dist/{chunk-YV2YA3QH.cjs → chunk-2KRAHL2J.cjs} +31 -31
  2. package/dist/{chunk-YV2YA3QH.cjs.map → chunk-2KRAHL2J.cjs.map} +1 -1
  3. package/dist/{chunk-KIIOBK7G.cjs → chunk-32N6U2Z2.cjs} +26 -26
  4. package/dist/{chunk-KIIOBK7G.cjs.map → chunk-32N6U2Z2.cjs.map} +1 -1
  5. package/dist/{chunk-R5QYMUKN.js → chunk-3SFYW3J7.js} +2 -2
  6. package/dist/{chunk-ZHQQRYMB.js → chunk-5AUOHZWP.js} +4 -4
  7. package/dist/{chunk-HWTUVLYB.js → chunk-5IX4BNIY.js} +2 -2
  8. package/dist/{chunk-ZDJ4ZUQ4.cjs → chunk-6NACOLUM.cjs} +37 -37
  9. package/dist/{chunk-ZDJ4ZUQ4.cjs.map → chunk-6NACOLUM.cjs.map} +1 -1
  10. package/dist/{chunk-WGJPTFRH.js → chunk-6R3NA5WS.js} +2 -2
  11. package/dist/{chunk-QL7ODLJA.js → chunk-722A62GL.js} +4 -4
  12. package/dist/{chunk-L676XVP4.cjs → chunk-7VNAK2TM.cjs} +7 -7
  13. package/dist/{chunk-L676XVP4.cjs.map → chunk-7VNAK2TM.cjs.map} +1 -1
  14. package/dist/{chunk-IDKDOQZL.js → chunk-AQNZOW6H.js} +2 -2
  15. package/dist/{chunk-H7ETLG65.js → chunk-BOPT23N4.js} +4 -4
  16. package/dist/{chunk-FTXDIDNX.cjs → chunk-GTJJ4Y7Z.cjs} +15 -15
  17. package/dist/{chunk-FTXDIDNX.cjs.map → chunk-GTJJ4Y7Z.cjs.map} +1 -1
  18. package/dist/{chunk-MKLP2SOZ.cjs → chunk-HFTBMWAO.cjs} +1221 -1380
  19. package/dist/chunk-HFTBMWAO.cjs.map +1 -0
  20. package/dist/{chunk-7IRPMNRL.cjs → chunk-IMG46R5G.cjs} +59 -59
  21. package/dist/{chunk-7IRPMNRL.cjs.map → chunk-IMG46R5G.cjs.map} +1 -1
  22. package/dist/{chunk-YQBPIUZU.js → chunk-ITCV37Q2.js} +2 -2
  23. package/dist/{chunk-DQ3EHM6G.cjs → chunk-KG6VKWRF.cjs} +9 -9
  24. package/dist/{chunk-DQ3EHM6G.cjs.map → chunk-KG6VKWRF.cjs.map} +1 -1
  25. package/dist/{chunk-XUNJILJT.cjs → chunk-KJOM3JWD.cjs} +62 -62
  26. package/dist/{chunk-XUNJILJT.cjs.map → chunk-KJOM3JWD.cjs.map} +1 -1
  27. package/dist/{chunk-5USSSRAL.cjs → chunk-MK5PLTDE.cjs} +26 -26
  28. package/dist/{chunk-5USSSRAL.cjs.map → chunk-MK5PLTDE.cjs.map} +1 -1
  29. package/dist/{chunk-U2YDR5DT.js → chunk-NWHCBXRQ.js} +2 -2
  30. package/dist/{chunk-VTQSB6QB.cjs → chunk-NZNF7RCQ.cjs} +5 -5
  31. package/dist/{chunk-VTQSB6QB.cjs.map → chunk-NZNF7RCQ.cjs.map} +1 -1
  32. package/dist/{chunk-KQGFKG6Y.js → chunk-OMY4PRE3.js} +2 -2
  33. package/dist/{chunk-TNLFAJPA.js → chunk-XFH7PMGS.js} +4746 -4905
  34. package/dist/chunk-XFH7PMGS.js.map +1 -0
  35. package/dist/{chunk-6B27O3ZB.js → chunk-YWMHHI3V.js} +4 -4
  36. package/dist/{chunk-LDAOPWXZ.cjs → chunk-ZODNSYZC.cjs} +11 -11
  37. package/dist/{chunk-LDAOPWXZ.cjs.map → chunk-ZODNSYZC.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/chart.d.ts +1 -1
  41. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  42. package/dist/components/chat/chat-sidebar.d.ts.map +1 -1
  43. package/dist/components/chat/chat-ticket-list.d.ts.map +1 -1
  44. package/dist/components/chat/guide-welcome.d.ts +5 -1
  45. package/dist/components/chat/guide-welcome.d.ts.map +1 -1
  46. package/dist/components/chat/index.cjs +2 -2
  47. package/dist/components/chat/index.js +1 -1
  48. package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
  49. package/dist/components/chat/mingo-welcome.d.ts +5 -1
  50. package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
  51. package/dist/components/chat/quick-action-wall.d.ts +16 -6
  52. package/dist/components/chat/quick-action-wall.d.ts.map +1 -1
  53. package/dist/components/contact/index.cjs +3 -3
  54. package/dist/components/contact/index.js +2 -2
  55. package/dist/components/docs/index.cjs +5 -5
  56. package/dist/components/docs/index.js +4 -4
  57. package/dist/components/embeds/index.cjs +3 -3
  58. package/dist/components/embeds/index.js +2 -2
  59. package/dist/components/faq/index.cjs +3 -3
  60. package/dist/components/faq/index.js +2 -2
  61. package/dist/components/features/index.cjs +2 -2
  62. package/dist/components/features/index.js +1 -1
  63. package/dist/components/features/notifications/notification-drawer.d.ts.map +1 -1
  64. package/dist/components/features/time-tracker/time-tracker-panel.d.ts.map +1 -1
  65. package/dist/components/help-center-pages/index.cjs +20 -20
  66. package/dist/components/help-center-pages/index.js +11 -11
  67. package/dist/components/index.cjs +129 -135
  68. package/dist/components/index.cjs.map +1 -1
  69. package/dist/components/index.js +9 -15
  70. package/dist/components/index.js.map +1 -1
  71. package/dist/components/layout/page-container.d.ts.map +1 -1
  72. package/dist/components/navigation/app-layout-drawer.d.ts +1 -1
  73. package/dist/components/navigation/index.cjs +2 -2
  74. package/dist/components/navigation/index.js +1 -1
  75. package/dist/components/onboarding-guides/index.cjs +5 -5
  76. package/dist/components/onboarding-guides/index.js +4 -4
  77. package/dist/components/related-content/index.cjs +3 -3
  78. package/dist/components/related-content/index.js +2 -2
  79. package/dist/components/tickets/index.cjs +5 -5
  80. package/dist/components/tickets/index.js +4 -4
  81. package/dist/components/ui/drawer.d.ts +1 -1
  82. package/dist/components/ui/drawer.d.ts.map +1 -1
  83. package/dist/components/ui/index.cjs +2 -8
  84. package/dist/components/ui/index.cjs.map +1 -1
  85. package/dist/components/ui/index.d.ts +0 -1
  86. package/dist/components/ui/index.d.ts.map +1 -1
  87. package/dist/components/ui/index.js +1 -7
  88. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  89. package/dist/components/ui/modal.d.ts.map +1 -1
  90. package/dist/index.cjs +2 -8
  91. package/dist/index.cjs.map +1 -1
  92. package/dist/index.js +1 -7
  93. package/package.json +1 -3
  94. package/src/components/chat/chat-message-list.tsx +6 -7
  95. package/src/components/chat/chat-sidebar.tsx +4 -9
  96. package/src/components/chat/chat-ticket-list.tsx +2 -8
  97. package/src/components/chat/embeddable-chat.tsx +6 -0
  98. package/src/components/chat/guide-welcome.tsx +10 -6
  99. package/src/components/chat/mingo-chat-history.tsx +4 -6
  100. package/src/components/chat/mingo-welcome.tsx +10 -6
  101. package/src/components/chat/quick-action-wall.tsx +53 -8
  102. package/src/components/features/notifications/notification-drawer.tsx +4 -8
  103. package/src/components/features/time-tracker/time-tracker-panel.tsx +3 -11
  104. package/src/components/layout/page-container.tsx +5 -17
  105. package/src/components/ui/drawer.tsx +3 -8
  106. package/src/components/ui/index.ts +0 -1
  107. package/src/components/ui/modal-v2.tsx +2 -12
  108. package/src/components/ui/modal.tsx +2 -9
  109. package/src/stories/QuickActionWall.stories.tsx +65 -0
  110. package/src/styles/index.css +0 -2
  111. package/dist/chunk-MKLP2SOZ.cjs.map +0 -1
  112. package/dist/chunk-TNLFAJPA.js.map +0 -1
  113. package/dist/components/ui/overlay-scroll-area.d.ts +0 -59
  114. package/dist/components/ui/overlay-scroll-area.d.ts.map +0 -1
  115. package/src/components/ui/overlay-scroll-area.tsx +0 -219
  116. package/src/stories/OverlayScrollArea.stories.tsx +0 -52
  117. package/src/styles/overlay-scrollbars-theme.css +0 -19
  118. package/src/styles/vendor-overlayscrollbars.css +0 -673
  119. /package/dist/{chunk-R5QYMUKN.js.map → chunk-3SFYW3J7.js.map} +0 -0
  120. /package/dist/{chunk-ZHQQRYMB.js.map → chunk-5AUOHZWP.js.map} +0 -0
  121. /package/dist/{chunk-HWTUVLYB.js.map → chunk-5IX4BNIY.js.map} +0 -0
  122. /package/dist/{chunk-WGJPTFRH.js.map → chunk-6R3NA5WS.js.map} +0 -0
  123. /package/dist/{chunk-QL7ODLJA.js.map → chunk-722A62GL.js.map} +0 -0
  124. /package/dist/{chunk-IDKDOQZL.js.map → chunk-AQNZOW6H.js.map} +0 -0
  125. /package/dist/{chunk-H7ETLG65.js.map → chunk-BOPT23N4.js.map} +0 -0
  126. /package/dist/{chunk-YQBPIUZU.js.map → chunk-ITCV37Q2.js.map} +0 -0
  127. /package/dist/{chunk-U2YDR5DT.js.map → chunk-NWHCBXRQ.js.map} +0 -0
  128. /package/dist/{chunk-KQGFKG6Y.js.map → chunk-OMY4PRE3.js.map} +0 -0
  129. /package/dist/{chunk-6B27O3ZB.js.map → chunk-YWMHHI3V.js.map} +0 -0
@@ -3,7 +3,6 @@
3
3
  import * as React from 'react'
4
4
  import { cn } from '../../utils/cn'
5
5
  import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
6
- import { OverlayScrollArea } from '../ui/overlay-scroll-area'
7
6
  import { ChatTicketItem, type ChatTicketItemData } from './entity-cards/chat-ticket-item'
8
7
 
9
8
  export interface ChatTicketListProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -47,12 +46,7 @@ const ChatTicketList = React.forwardRef<HTMLDivElement, ChatTicketListProps>(
47
46
  !fadeBottom && "border-b rounded-b-md",
48
47
  )}
49
48
  >
50
- <OverlayScrollArea
51
- viewportRef={scrollRef}
52
- className="h-full"
53
- contentClassName="overscroll-contain"
54
- onScroll={updateFade}
55
- >
49
+ <div ref={scrollRef} className="overflow-y-auto overscroll-contain h-full" onScroll={updateFade}>
56
50
  {tickets.map((ticket) => (
57
51
  <ChatTicketItem
58
52
  key={ticket.id}
@@ -60,7 +54,7 @@ const ChatTicketList = React.forwardRef<HTMLDivElement, ChatTicketListProps>(
60
54
  onClick={onTicketClick}
61
55
  />
62
56
  ))}
63
- </OverlayScrollArea>
57
+ </div>
64
58
 
65
59
  {/* Scroll-fade overlays — tinted with the page background so edge
66
60
  tickets fade into the surface behind the list in BOTH themes
@@ -2357,6 +2357,9 @@ function EmbeddableChatInner({
2357
2357
  setQuickActionPreview(action.prompt ?? action.label)
2358
2358
  }
2359
2359
  onQuickActionHoverEnd={() => setQuickActionPreview(null)}
2360
+ // A built-in agent caps the quick-action wall at 2 rows;
2361
+ // this is the Mingo surface, so fall back to 'mingo'.
2362
+ agentSlug={activeAgentSlug ?? 'mingo'}
2360
2363
  />
2361
2364
  ) : (
2362
2365
  /* Figma node 7532:328214 — Guide-mode empty state: greeting
@@ -2404,6 +2407,9 @@ function EmbeddableChatInner({
2404
2407
  setQuickActionPreview(action.prompt ?? action.label)
2405
2408
  }
2406
2409
  onQuickActionHoverEnd={() => setQuickActionPreview(null)}
2410
+ // In agent mode a built-in agent (fae/mingo) caps the wall at
2411
+ // 2 rows; host/guide mode (no active agent) keeps the default.
2412
+ agentSlug={activeAgentSlug}
2407
2413
  >
2408
2414
  {/* Figma node 7363:205938 — single-column slash-command
2409
2415
  list. No own scroll (GuideWelcome's region scrolls); the
@@ -4,7 +4,6 @@ import * as React from 'react'
4
4
  import { cn } from '../../utils/cn'
5
5
  import { MingoIcon } from '../icons'
6
6
  import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
7
- import { OverlayScrollArea } from '../ui/overlay-scroll-area'
8
7
  import { Skeleton } from '../ui/skeleton'
9
8
  import { QuickActionWall } from './quick-action-wall'
10
9
  import { EntityIcon } from '../icon-display'
@@ -60,6 +59,10 @@ export interface GuideWelcomeProps {
60
59
  onQuickActionHover?: (action: GuideQuickAction) => void
61
60
  /** Pointer/keyboard focus leaves the chip — e.g. restore the composer. */
62
61
  onQuickActionHoverEnd?: () => void
62
+ /** Agent this empty state belongs to — forwarded to the quick-action
63
+ * {@link QuickActionWall} so a built-in agent (`'fae'`/`'mingo'`) caps the
64
+ * brick stack at 2 rows. Unset (host/guide mode) keeps the full `rows` cap. */
65
+ agentSlug?: string
63
66
  /** Slash-command onboarding list — rendered inside the shared scroll region
64
67
  * below the greeting (so greeting + list scroll together, with edge fades). */
65
68
  children?: React.ReactNode
@@ -96,6 +99,7 @@ export function GuideWelcome({
96
99
  onQuickAction,
97
100
  onQuickActionHover,
98
101
  onQuickActionHoverEnd,
102
+ agentSlug,
99
103
  children,
100
104
  className,
101
105
  }: GuideWelcomeProps) {
@@ -142,11 +146,10 @@ export function GuideWelcome({
142
146
  {/* Greeting + slash-command list share one scroll region; `relative` so
143
147
  the edge fades can overlay it. */}
144
148
  <div className="relative flex flex-1 min-h-0 flex-col">
145
- <OverlayScrollArea
146
- viewportRef={scrollRef}
149
+ <div
150
+ ref={scrollRef}
147
151
  onScroll={updateScrollFade}
148
- className="flex-1 min-h-0"
149
- contentClassName="flex min-h-full flex-col gap-[var(--spacing-system-m)] overscroll-contain"
152
+ className="flex flex-1 min-h-0 flex-col gap-[var(--spacing-system-m)] overflow-y-auto overscroll-contain"
150
153
  >
151
154
  {/* Greeting grows to fill (`flex-1`) so the slash-command list stays
152
155
  anchored below it — but its content is anchored at the TOP of the
@@ -189,7 +192,7 @@ export function GuideWelcome({
189
192
  </div>
190
193
 
191
194
  {children}
192
- </OverlayScrollArea>
195
+ </div>
193
196
 
194
197
  {/* Edge scroll-fades — visible only when content is hidden beyond them. */}
195
198
  <ScrollFadeOverlay edge="top" visible={fadeTop} className="h-12" />
@@ -207,6 +210,7 @@ export function GuideWelcome({
207
210
  {quickActions.length > 0 && (
208
211
  <QuickActionWall
209
212
  chips={chipItems}
213
+ agentSlug={agentSlug}
210
214
  rows={4}
211
215
  pauseOnHover
212
216
  dragScroll
@@ -5,7 +5,6 @@ import { cn } from '../../utils/cn'
5
5
  import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu'
6
6
  import { Button } from '../ui/button'
7
7
  import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
8
- import { OverlayScrollArea } from '../ui/overlay-scroll-area'
9
8
  import { Ellipsis01Icon, SearchXmarkIcon } from '../icons-v2-generated'
10
9
  import { ChatListEmptyState } from './chat-list-empty-state'
11
10
  import type { DialogItem } from './types/component.types'
@@ -339,11 +338,10 @@ export function MingoChatHistory({
339
338
  {/* Scroll region — the search INPUT now lives in the panel header
340
339
  (Figma 116:51217), so the list is just the grouped rows + fades. */}
341
340
  <div className="relative flex flex-1 min-h-0 flex-col">
342
- <OverlayScrollArea
343
- viewportRef={scrollRef}
341
+ <div
342
+ ref={scrollRef}
344
343
  onScroll={updateFade}
345
- className="flex-1 min-h-0"
346
- contentClassName="flex flex-col gap-[var(--spacing-system-m)] overscroll-contain"
344
+ className="flex flex-1 min-h-0 flex-col gap-[var(--spacing-system-m)] overflow-y-auto overscroll-contain"
347
345
  >
348
346
  {noSearchResults ? (
349
347
  // No search matches — same centred glyph + title + caption layout as
@@ -376,7 +374,7 @@ export function MingoChatHistory({
376
374
  ))
377
375
  )}
378
376
  {hasMore ? <div ref={sentinelRef} className="h-px shrink-0" /> : null}
379
- </OverlayScrollArea>
377
+ </div>
380
378
 
381
379
  {/* Scroll-fade — only while content is hidden in that direction. */}
382
380
  <ScrollFadeOverlay edge="top" visible={fadeTop} color="var(--color-bg-card)" className="h-12" />
@@ -8,7 +8,6 @@ import { QuickActionChipButton } from './quick-action-chip'
8
8
  import { QuickActionWall } from './quick-action-wall'
9
9
  import { Button } from '../ui/button'
10
10
  import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
11
- import { OverlayScrollArea } from '../ui/overlay-scroll-area'
12
11
  import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon'
13
12
  import {
14
13
  CompassIcon,
@@ -88,6 +87,10 @@ export interface MingoWelcomeProps {
88
87
  * action — switches the host chat to Guide mode) and enables the default
89
88
  * promo notification. When omitted, both are suppressed. */
90
89
  onStartGuideChat?: () => void
90
+ /** Agent this empty state belongs to — forwarded to the quick-action
91
+ * {@link QuickActionWall} so a built-in agent (`'fae'`/`'mingo'`) caps the
92
+ * brick stack at 2 rows. Defaults to `'mingo'` (this is the Mingo surface). */
93
+ agentSlug?: string
91
94
  /** Appended to the root element. */
92
95
  className?: string
93
96
  }
@@ -139,6 +142,7 @@ export function MingoWelcome({
139
142
  loadError = false,
140
143
  onRetry,
141
144
  onStartGuideChat,
145
+ agentSlug = 'mingo',
142
146
  className,
143
147
  }: MingoWelcomeProps) {
144
148
  // Greeting never weaves the user's name in — always the plain "Hey, I'm
@@ -240,11 +244,10 @@ export function MingoWelcome({
240
244
  input. The wrapper is `relative` so the scroll-fade gradients can
241
245
  overlay the top/bottom edges. */}
242
246
  <div className="relative flex flex-1 min-h-0 flex-col">
243
- <OverlayScrollArea
244
- viewportRef={scrollRef}
247
+ <div
248
+ ref={scrollRef}
245
249
  onScroll={updateScrollFade}
246
- className="flex-1 min-h-0"
247
- contentClassName="flex min-h-full flex-col gap-[var(--spacing-system-m)] overscroll-contain"
250
+ className="flex flex-1 min-h-0 flex-col gap-[var(--spacing-system-m)] overflow-y-auto overscroll-contain"
248
251
  >
249
252
  {/* Greeting — grows to fill (`flex-1`) so it centres vertically,
250
253
  keeping the grid anchored at the bottom of the scroll area. Default
@@ -263,7 +266,7 @@ export function MingoWelcome({
263
266
  <p className="text-h4 text-ods-text-secondary">{subtitle}</p>
264
267
  </div>
265
268
  </div>
266
- </OverlayScrollArea>
269
+ </div>
267
270
 
268
271
  {/* Edge scroll-fades — visible only when content is hidden beyond them.
269
272
  Fade into the panel's dark `ods-bg` surface (the default color), matching
@@ -339,6 +342,7 @@ export function MingoWelcome({
339
342
  onHoverStart: () => onQuickActionHover?.(action),
340
343
  onHoverEnd: () => onQuickActionHoverEnd?.(),
341
344
  }))}
345
+ agentSlug={agentSlug}
342
346
  rows={4}
343
347
  pauseOnHover
344
348
  dragScroll
@@ -42,6 +42,11 @@ export function interleave<T>(a: ReadonlyArray<T>, b: ReadonlyArray<T>): T[] {
42
42
  // row copy overflows any sensible container, so the loop always engages.
43
43
  const DEFAULT_MIN_CHIPS_PER_ROW = 14
44
44
 
45
+ // Chat-agent walls (fae/mingo) cap the brick stack at 2 rows so the quick
46
+ // actions never crowd the composer; every other surface keeps the caller's
47
+ // `rows` as its cap.
48
+ const AGENT_MAX_ROWS = 2
49
+
45
50
  // =============================================================================
46
51
  // Types
47
52
  // =============================================================================
@@ -92,14 +97,24 @@ export interface QuickActionWallProps {
92
97
  * walls where repeats would duplicate flip ids, need no padding). */
93
98
  minChips?: number
94
99
  /**
95
- * BRICK-WALL mode: render this many stacked independent single-row
96
- * marquees (chips distributed round-robin) instead of one track. Rows pack
97
- * their chips edge-to-edge like bricks — a grid would column-align chips
98
- * of different widths and leave holes — and each row loops on its own
99
- * wrap, so the courses stagger organically. Horizontal only; the fades
100
+ * BRICK-WALL mode: the MAXIMUM number of stacked single-row marquees. The
101
+ * actual row count grows with the chip supply — `ceil(chips / 14)` capped at
102
+ * `rows` — so a short action set fills one row (padded with its own repeats)
103
+ * instead of spreading one chip per row. The originals are split evenly
104
+ * ACROSS the active rows first, THEN each row pads to a full course, so no
105
+ * row is ever all-duplicates while another holds the unique chips. Rows pack
106
+ * edge-to-edge like bricks (a grid would column-align mismatched widths and
107
+ * leave holes) and each loops on its own wrap. Horizontal only; the fades
100
108
  * draw ONCE over the whole stack.
101
109
  */
102
110
  rows?: number
111
+ /**
112
+ * Chat agent this wall belongs to (`'fae'` / `'mingo'`). When set to a
113
+ * built-in agent it caps the brick stack at {@link AGENT_MAX_ROWS} (2) so the
114
+ * actions stay compact above the composer; any other value (or unset) leaves
115
+ * the cap at `rows`. Optional — decorative/marketing walls omit it.
116
+ */
117
+ agentSlug?: string
103
118
  /** Draw skeleton chips instead of `chips` (the shared
104
119
  * {@link QuickActionChipSkeleton} — 1:1 geometry with loaded chips).
105
120
  * Callers own the policy (`loading` flag vs skeleton-when-empty). */
@@ -150,6 +165,7 @@ export function QuickActionWall({
150
165
  copyGap,
151
166
  minChips,
152
167
  rows,
168
+ agentSlug,
153
169
  loading = false,
154
170
  skeletonCount = 24,
155
171
  className,
@@ -159,7 +175,9 @@ export function QuickActionWall({
159
175
  // Repeat-pad the track (stable suffixed keys; repeats keep the full chip —
160
176
  // identical selected/interactive state, so every visible instance behaves
161
177
  // the same and the clone copy stays geometry-identical by construction).
162
- const padTarget = minChips ?? (rows ? rows * DEFAULT_MIN_CHIPS_PER_ROW : undefined)
178
+ // Brick mode does its own per-row padding below, so this pads only the
179
+ // single-track (wrap / vertical) walls.
180
+ const padTarget = rows ? undefined : minChips
163
181
  const track = React.useMemo(() => {
164
182
  if (!padTarget || chips.length === 0 || chips.length >= padTarget) return chips
165
183
  const repeats = Math.ceil(padTarget / chips.length)
@@ -198,8 +216,35 @@ export function QuickActionWall({
198
216
 
199
217
  // ---- BRICK-WALL mode: stacked independent row marquees ---------------------
200
218
  if (rows && rows > 0) {
201
- const rowLists = Array.from({ length: rows }, (_, r) => track.filter((_, i) => i % rows === r))
202
- const skelPerRow = Math.ceil(skeletonCount / rows)
219
+ // Chat-agent walls (fae/mingo) grow the stack with the chip supply and cap
220
+ // it at AGENT_MAX_ROWS (2) so the actions stay compact above the composer.
221
+ // Every other surface keeps exactly `rows` rows (marketing/onboarding walls
222
+ // are sized for their design and carry far fewer than a full course/row).
223
+ const agentCapped = agentSlug === 'fae' || agentSlug === 'mingo'
224
+ const rowCap = agentCapped ? Math.min(rows, AGENT_MAX_ROWS) : rows
225
+ const dataRows = agentCapped
226
+ ? Math.min(Math.max(1, Math.ceil(chips.length / DEFAULT_MIN_CHIPS_PER_ROW)), rowCap)
227
+ : rows
228
+ // Skeletons hold the cap so the row count never jumps when real chips land.
229
+ const stackRows = loading ? rowCap : dataRows
230
+ // Per-row overflow target: a custom `minChips` budget split across the
231
+ // stack, else the 14-chip default that guarantees the loop engages.
232
+ const perRowTarget = minChips ? Math.ceil(minChips / stackRows) : DEFAULT_MIN_CHIPS_PER_ROW
233
+
234
+ // Split the ORIGINAL chips evenly across the rows FIRST (each row holds
235
+ // distinct actions), THEN pad each row to a full course with repeats of its
236
+ // OWN chips — so a row is never all-duplicates while another carries the
237
+ // unique ones (the old pad-then-slice split did exactly that when the chip
238
+ // count lined up with the row count).
239
+ const rowLists = Array.from({ length: stackRows }, (_, r) => {
240
+ const base = chips.filter((_, i) => i % stackRows === r)
241
+ if (base.length === 0 || base.length >= perRowTarget) return base
242
+ const repeats = Math.ceil(perRowTarget / base.length)
243
+ return Array.from({ length: repeats }, (_, rep) =>
244
+ rep === 0 ? base : base.map(chip => ({ ...chip, id: `${chip.id}__r${rep}` })),
245
+ ).flat()
246
+ })
247
+ const skelPerRow = Math.ceil(skeletonCount / stackRows)
203
248
  return (
204
249
  <div className={cn('relative flex flex-col overflow-hidden', className)} style={{ gap }}>
205
250
  {rowLists.map((list, r) => (
@@ -1,7 +1,6 @@
1
1
  'use client'
2
2
 
3
3
  import { useEffect, useRef } from 'react'
4
- import { OverlayScrollArea } from '../../ui/overlay-scroll-area'
5
4
  import { BellOffIcon } from '../../icons-v2-generated/interface/bell-off-icon'
6
5
  import { ClockHistoryIcon, ArrowRightUpIcon } from '../../icons-v2-generated'
7
6
  import { useAppLayoutDrawerContainer } from '../../navigation/app-layout-context'
@@ -190,12 +189,9 @@ function DrawerScrollList({
190
189
 
191
190
  const isEmpty = unreadNotifications.length === 0
192
191
  return (
193
- <OverlayScrollArea
194
- viewportRef={scrollRef}
195
- // Horizontal padding lives on the host: OverlayScrollbars zeroes viewport
196
- // padding, so `px-*` on `contentClassName` would be silently dropped.
197
- className="flex-1 min-h-0 px-[var(--spacing-system-m)]"
198
- contentClassName="flex flex-col gap-[var(--spacing-system-xs)]"
192
+ <div
193
+ ref={scrollRef}
194
+ className="flex flex-1 flex-col gap-[var(--spacing-system-xs)] overflow-y-auto px-[var(--spacing-system-m)]"
199
195
  >
200
196
  {isEmpty && !isLoadingMore ? (
201
197
  <EmptyState />
@@ -218,7 +214,7 @@ function DrawerScrollList({
218
214
  {loadMore && hasMore && <div ref={sentinelRef} className="h-1" aria-hidden="true" />}
219
215
  </>
220
216
  )}
221
- </OverlayScrollArea>
217
+ </div>
222
218
  )
223
219
  }
224
220
 
@@ -21,7 +21,6 @@ import {
21
21
  XmarkIcon,
22
22
  } from '../../icons-v2-generated'
23
23
  import { useTrackerClock } from './use-tracker-clock'
24
- import { OverlayScrollArea } from '../../ui/overlay-scroll-area'
25
24
  import type { TimeTrackerData, TimeTrackerEntry, TimeTrackerStatus } from './types'
26
25
 
27
26
  interface CustomerAutocompleteOption extends AutocompleteOption {
@@ -150,18 +149,11 @@ export function TimeTrackerPanel({
150
149
  const visibleEntries = lastEntries.slice(0, 3)
151
150
 
152
151
  return (
153
- <OverlayScrollArea
152
+ <div
154
153
  className={cn(
155
- // Padding lives on the host, NOT `contentClassName`: OverlayScrollbars
156
- // forces `padding: 0` on the viewport, so padding on the scroller is
157
- // silently dropped (see OverlayScrollArea docs).
158
- 'max-h-[calc(100vh-6rem)] w-full rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-m)]',
154
+ 'relative flex max-h-[calc(100vh-6rem)] w-full flex-col gap-[var(--spacing-system-l)] overflow-y-auto rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-m)]',
159
155
  className,
160
156
  )}
161
- // No `relative` here so the absolute close button anchors to the padded
162
- // host (OverlayScrollArea's host is always relative) and keeps its inset
163
- // from the card edge instead of from the padding box.
164
- contentClassName="flex flex-col gap-[var(--spacing-system-l)]"
165
157
  >
166
158
  <Button
167
159
  variant="transparent"
@@ -322,7 +314,7 @@ export function TimeTrackerPanel({
322
314
  <Button variant="outline" fullWidth onClick={onClose} className="md:hidden">
323
315
  Close
324
316
  </Button>
325
- </OverlayScrollArea>
317
+ </div>
326
318
  )
327
319
  }
328
320
 
@@ -4,7 +4,6 @@ import React from 'react'
4
4
  import { cn } from '../../utils/cn'
5
5
  import type { ActionsMenuGroup } from '../ui/actions-menu'
6
6
  import { PageActions, type PageActionButton } from '../ui/page-actions'
7
- import { OverlayScrollArea } from '../ui/overlay-scroll-area'
8
7
  import { BackButton } from './back-button'
9
8
 
10
9
  // Legacy interface for backward compatibility (layout version)
@@ -394,7 +393,7 @@ function renderAdvancedPageContainer({
394
393
 
395
394
  switch (variant) {
396
395
  case 'detail':
397
- return cn('flex-1 min-h-0', mobilePadding, contentClassName)
396
+ return cn('flex-1 overflow-auto', mobilePadding, contentClassName)
398
397
  case 'list':
399
398
  return cn('flex flex-col gap-4 md:gap-6', mobilePadding, contentClassName)
400
399
  case 'form':
@@ -408,21 +407,10 @@ function renderAdvancedPageContainer({
408
407
  return (
409
408
  <div className={getContainerClasses()}>
410
409
  {renderHeader()}
411
-
412
- {variant === 'detail' ? (
413
- // The only scrolling variant — standardized overlay scrollbar.
414
- // `contentClassName` is forwarded to BOTH layers: padding stays effective
415
- // on the host, while layout (`flex flex-col gap-*`) reaches the scroller,
416
- // the direct parent of the children (OverlayScrollbars zeroes viewport
417
- // padding, so padding on the scroller alone would be dropped).
418
- <OverlayScrollArea className={getContentClasses()} contentClassName={contentClassName}>
419
- {children}
420
- </OverlayScrollArea>
421
- ) : (
422
- <div className={getContentClasses()}>
423
- {children}
424
- </div>
425
- )}
410
+
411
+ <div className={getContentClasses()}>
412
+ {children}
413
+ </div>
426
414
  </div>
427
415
  )
428
416
  }
@@ -7,7 +7,6 @@ import { X } from "lucide-react"
7
7
 
8
8
  import { cn } from "../../utils/cn"
9
9
  import { useHeaderHeight } from "../../hooks/ui/use-header-height"
10
- import { OverlayScrollArea } from "./overlay-scroll-area"
11
10
 
12
11
  /** Unified overlay backdrop — dimmed, no blur. Single source of truth for
13
12
  * every full-screen backdrop (Drawer, AppLayoutDrawer, MobileBurgerMenu,
@@ -531,16 +530,12 @@ DrawerDescription.displayName = "DrawerDescription"
531
530
 
532
531
  const DrawerBody = ({
533
532
  className,
534
- children,
535
533
  ...props
536
534
  }: React.HTMLAttributes<HTMLDivElement>) => (
537
- <OverlayScrollArea
538
- className={cn("flex-1 min-h-0", className)}
539
- contentClassName="flex flex-col gap-4"
535
+ <div
536
+ className={cn("flex flex-1 flex-col gap-4 overflow-y-auto", className)}
540
537
  {...props}
541
- >
542
- {children}
543
- </OverlayScrollArea>
538
+ />
544
539
  )
545
540
  DrawerBody.displayName = "DrawerBody"
546
541
 
@@ -177,4 +177,3 @@ export * from './simple-markdown-renderer'
177
177
  export { RichMarkdownRenderer, type RichMarkdownRendererProps } from './rich-markdown-renderer'
178
178
  export * from './filter-pill-row'
179
179
 
180
- export * from './overlay-scroll-area'
@@ -5,7 +5,6 @@ import { useEffect, useState } from "react"
5
5
  import { usePreventScroll } from "@react-aria/overlays"
6
6
  import { XmarkIcon } from "../icons-v2-generated"
7
7
  import { cn } from "../../utils/cn"
8
- import { OverlayScrollArea } from "./overlay-scroll-area"
9
8
 
10
9
  // Duration of the open/close animation in ms — keep in sync with the
11
10
  // `duration-200` utilities applied to the backdrop and panel below.
@@ -132,18 +131,9 @@ Modal.displayName = "ModalV2"
132
131
 
133
132
  const ModalContent = React.forwardRef<HTMLDivElement, ModalContentProps>(
134
133
  ({ children, className }, ref) => (
135
- // `className` goes to BOTH layers on purpose: historically it styled the
136
- // single scrolling div that directly parented the children, so it may carry
137
- // layout (`flex flex-col gap-*` — must reach the scroller/`contentClassName`,
138
- // the direct parent of the children) OR padding (must stay on the host, since
139
- // OverlayScrollbars zeroes viewport padding). Each class lands where it works.
140
- <OverlayScrollArea
141
- viewportRef={ref}
142
- className={cn("flex-1 min-h-0", className)}
143
- contentClassName={className}
144
- >
134
+ <div ref={ref} className={cn("flex-1 min-h-0 overflow-y-auto", className)}>
145
135
  {children}
146
- </OverlayScrollArea>
136
+ </div>
147
137
  )
148
138
  )
149
139
  ModalContent.displayName = "ModalV2Content"
@@ -4,7 +4,6 @@ import * as React from "react"
4
4
  import { useEffect } from "react"
5
5
  import { usePreventScroll } from "@react-aria/overlays"
6
6
  import { cn } from "../../utils/cn"
7
- import { OverlayScrollArea } from "./overlay-scroll-area"
8
7
 
9
8
  interface ModalProps {
10
9
  isOpen: boolean
@@ -83,15 +82,9 @@ Modal.displayName = "Modal"
83
82
  /** @deprecated Use ModalV2Content from './modal-v2' instead. */
84
83
  const ModalContent = React.forwardRef<HTMLDivElement, ModalContentProps>(
85
84
  ({ children, className }, ref) => (
86
- // See ModalV2Content: `className` may carry layout (must reach the scroller)
87
- // or padding (must stay on the host), so it goes to both layers.
88
- <OverlayScrollArea
89
- viewportRef={ref}
90
- className={cn("min-h-0 flex-1", className)}
91
- contentClassName={className}
92
- >
85
+ <div ref={ref} className={cn("overflow-y-auto min-h-0 flex-1", className)}>
93
86
  {children}
94
- </OverlayScrollArea>
87
+ </div>
95
88
  )
96
89
  )
97
90
  ModalContent.displayName = "ModalContent"
@@ -127,3 +127,68 @@ export const Loading: Story = {
127
127
  </div>
128
128
  ),
129
129
  }
130
+
131
+ // Brick-mode chip set: enough distinct actions to fill more than two full
132
+ // courses, so the agent cap and the even split are both visible.
133
+ const BRICK_ACTIONS: ReadonlyArray<QuickActionChip> = [
134
+ ...IT_ACTIONS,
135
+ ...SEC_ACTIONS,
136
+ ...FAE_ACTIONS.map((c) => ({ ...c, theme: FAE_THEME })),
137
+ ].map((c) => ({ ...c, onSelect: () => console.log(c.id) }))
138
+
139
+ /** Brick, chat agent (`agentSlug="mingo"`): the stack grows with the chip
140
+ * supply but caps at 2 rows. Originals are split evenly across the two rows
141
+ * first, then each row pads to a full course — no row is all-duplicates. */
142
+ export const BrickAgentCapped: Story = {
143
+ render: () => (
144
+ <QuickActionWall
145
+ chips={BRICK_ACTIONS}
146
+ agentSlug="mingo"
147
+ rows={4}
148
+ pauseOnHover
149
+ dragScroll
150
+ fade={['left', 'right']}
151
+ fadeSize={{ left: 32 }}
152
+ fadeColor="var(--color-bg)"
153
+ copyGap="var(--spacing-system-xxs)"
154
+ className="max-h-44"
155
+ />
156
+ ),
157
+ }
158
+
159
+ /** Brick, few actions (`agentSlug="fae"`, 3 chips): fewer than one course, so
160
+ * the stack collapses to a SINGLE row padded with its own repeats instead of
161
+ * spreading one chip per row. */
162
+ export const BrickFewActions: Story = {
163
+ render: () => (
164
+ <QuickActionWall
165
+ chips={FAE_ACTIONS.slice(0, 3).map((c) => ({ ...c, theme: FAE_THEME, onSelect: () => console.log(c.id) }))}
166
+ agentSlug="fae"
167
+ rows={4}
168
+ pauseOnHover
169
+ dragScroll
170
+ fade={['left', 'right']}
171
+ fadeSize={{ left: 32 }}
172
+ fadeColor="var(--color-bg)"
173
+ copyGap="var(--spacing-system-xxs)"
174
+ className="max-h-44"
175
+ />
176
+ ),
177
+ }
178
+
179
+ /** Brick, non-agent (no `agentSlug`): keeps exactly `rows` rows (marketing /
180
+ * onboarding walls are sized for their design), still getting the even split
181
+ * so a row is never filled with only one repeated chip. */
182
+ export const BrickNonAgent: Story = {
183
+ render: () => (
184
+ <QuickActionWall
185
+ chips={BRICK_ACTIONS}
186
+ rows={4}
187
+ fade={['left', 'right']}
188
+ fadeSize={{ left: 32 }}
189
+ fadeColor="var(--color-bg)"
190
+ copyGap="var(--spacing-system-xxs)"
191
+ className="max-h-56"
192
+ />
193
+ ),
194
+ }
@@ -10,8 +10,6 @@
10
10
  /* Vendor/library style overrides */
11
11
  @import "./vendor-react-easy-crop.css";
12
12
  @import "./vendor-react-scroll.css";
13
- @import "./vendor-overlayscrollbars.css";
14
- @import "./overlay-scrollbars-theme.css";
15
13
 
16
14
  @tailwind base;
17
15
  @tailwind components;