@flamingo-stack/openframe-frontend-core 0.0.215 → 0.0.216

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 (228) hide show
  1. package/dist/chunk-2V4SACHE.js +302 -0
  2. package/dist/chunk-2V4SACHE.js.map +1 -0
  3. package/dist/chunk-572WQWIX.cjs +348 -0
  4. package/dist/chunk-572WQWIX.cjs.map +1 -0
  5. package/dist/{chunk-WT5JV2GS.cjs → chunk-5V6MSE3B.cjs} +39 -39
  6. package/dist/chunk-5V6MSE3B.cjs.map +1 -0
  7. package/dist/{chunk-WQZP3JIZ.js → chunk-CDLYRFDE.js} +1894 -1472
  8. package/dist/chunk-CDLYRFDE.js.map +1 -0
  9. package/dist/chunk-GVNQAGXB.js +232 -0
  10. package/dist/chunk-GVNQAGXB.js.map +1 -0
  11. package/dist/{chunk-P5EE2VJX.cjs → chunk-HOHDXYPR.cjs} +1 -1
  12. package/dist/chunk-HOHDXYPR.cjs.map +1 -0
  13. package/dist/chunk-IH76P5R6.cjs +232 -0
  14. package/dist/chunk-IH76P5R6.cjs.map +1 -0
  15. package/dist/{chunk-24KCAECR.cjs → chunk-JJR27M56.cjs} +3 -3
  16. package/dist/{chunk-24KCAECR.cjs.map → chunk-JJR27M56.cjs.map} +1 -1
  17. package/dist/chunk-K4DFAVSO.cjs +302 -0
  18. package/dist/chunk-K4DFAVSO.cjs.map +1 -0
  19. package/dist/{chunk-HICZPTRR.js → chunk-LCLTCCXS.js} +14 -14
  20. package/dist/chunk-LCLTCCXS.js.map +1 -0
  21. package/dist/{chunk-VFKQMAUF.cjs → chunk-OB45JHDY.cjs} +3 -3
  22. package/dist/{chunk-VFKQMAUF.cjs.map → chunk-OB45JHDY.cjs.map} +1 -1
  23. package/dist/{chunk-4XLJWX2N.js → chunk-ORJREQ2W.js} +4 -4
  24. package/dist/{chunk-7PCP7YQR.js → chunk-QTKU6ULP.js} +6 -6
  25. package/dist/{chunk-CIPO6DXK.js → chunk-QY75VKAS.js} +5 -5
  26. package/dist/{chunk-ZG2YY5E7.js → chunk-RFONYT63.js} +1 -1
  27. package/dist/chunk-RFONYT63.js.map +1 -0
  28. package/dist/{chunk-NGFP4RVL.cjs → chunk-SMCG2CCC.cjs} +30 -30
  29. package/dist/{chunk-NGFP4RVL.cjs.map → chunk-SMCG2CCC.cjs.map} +1 -1
  30. package/dist/{chunk-MX5MIFWA.js → chunk-UEBM4PC4.js} +5 -5
  31. package/dist/chunk-VC3ND5RB.js +348 -0
  32. package/dist/chunk-VC3ND5RB.js.map +1 -0
  33. package/dist/{chunk-UXZ3ZJ3M.cjs → chunk-XDPSSE4O.cjs} +4 -4
  34. package/dist/{chunk-UXZ3ZJ3M.cjs.map → chunk-XDPSSE4O.cjs.map} +1 -1
  35. package/dist/{chunk-D4MNFY67.cjs → chunk-ZGTDUPTW.cjs} +1316 -894
  36. package/dist/chunk-ZGTDUPTW.cjs.map +1 -0
  37. package/dist/components/chat/entity-cards/blog-card.d.ts +1 -1
  38. package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -1
  39. package/dist/components/chat/entity-cards/case-study-card.d.ts +1 -1
  40. package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
  41. package/dist/components/chat/entity-cards/customer-interview-card.d.ts +1 -1
  42. package/dist/components/chat/entity-cards/customer-interview-card.d.ts.map +1 -1
  43. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  44. package/dist/components/chat/entity-cards/investor-update-card.d.ts +1 -1
  45. package/dist/components/chat/entity-cards/investor-update-card.d.ts.map +1 -1
  46. package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +1 -1
  47. package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -1
  48. package/dist/components/chat/entity-cards/program-card.d.ts +1 -1
  49. package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -1
  50. package/dist/components/chat/entity-cards/use-entity-card-link.d.ts +14 -0
  51. package/dist/components/chat/entity-cards/use-entity-card-link.d.ts.map +1 -0
  52. package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts +13 -0
  53. package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts.map +1 -0
  54. package/dist/components/chat/index.cjs +11 -11
  55. package/dist/components/chat/index.js +10 -10
  56. package/dist/components/contact/index.cjs +12 -12
  57. package/dist/components/contact/index.js +11 -11
  58. package/dist/components/features/captions-url.d.ts +18 -0
  59. package/dist/components/features/captions-url.d.ts.map +1 -0
  60. package/dist/components/features/index.cjs +23 -11
  61. package/dist/components/features/index.cjs.map +1 -1
  62. package/dist/components/features/index.d.ts +2 -0
  63. package/dist/components/features/index.d.ts.map +1 -1
  64. package/dist/components/features/index.js +24 -12
  65. package/dist/components/features/mux-origins.cjs +10 -0
  66. package/dist/components/features/mux-origins.cjs.map +1 -0
  67. package/dist/components/features/mux-origins.d.ts +26 -0
  68. package/dist/components/features/mux-origins.d.ts.map +1 -0
  69. package/dist/components/features/mux-origins.js +7 -0
  70. package/dist/components/features/mux-origins.js.map +1 -0
  71. package/dist/components/features/notifications/index.d.ts +2 -0
  72. package/dist/components/features/notifications/index.d.ts.map +1 -1
  73. package/dist/components/features/notifications/notification-drawer.d.ts +2 -1
  74. package/dist/components/features/notifications/notification-drawer.d.ts.map +1 -1
  75. package/dist/components/features/notifications/notification-popups.d.ts +10 -0
  76. package/dist/components/features/notifications/notification-popups.d.ts.map +1 -0
  77. package/dist/components/features/notifications/notifications-context.d.ts +8 -1
  78. package/dist/components/features/notifications/notifications-context.d.ts.map +1 -1
  79. package/dist/components/features/notifications/types.d.ts +1 -0
  80. package/dist/components/features/notifications/types.d.ts.map +1 -1
  81. package/dist/components/features/use-video-warmup.d.ts +53 -0
  82. package/dist/components/features/use-video-warmup.d.ts.map +1 -0
  83. package/dist/components/icons/index.cjs +3 -3
  84. package/dist/components/icons/index.js +2 -2
  85. package/dist/components/icons-v2-generated/index.cjs +2 -2
  86. package/dist/components/icons-v2-generated/index.cjs.map +1 -1
  87. package/dist/components/icons-v2-generated/index.js +4 -4
  88. package/dist/components/index.cjs +132 -102
  89. package/dist/components/index.cjs.map +1 -1
  90. package/dist/components/index.d.ts +1 -0
  91. package/dist/components/index.d.ts.map +1 -1
  92. package/dist/components/index.js +94 -64
  93. package/dist/components/index.js.map +1 -1
  94. package/dist/components/navigation/index.cjs +11 -11
  95. package/dist/components/navigation/index.js +10 -10
  96. package/dist/components/onboarding-guides/build-default-href.d.ts +15 -0
  97. package/dist/components/onboarding-guides/build-default-href.d.ts.map +1 -0
  98. package/dist/components/onboarding-guides/hooks/use-onboarding-guides.d.ts +28 -0
  99. package/dist/components/onboarding-guides/hooks/use-onboarding-guides.d.ts.map +1 -0
  100. package/dist/components/onboarding-guides/index.cjs +373 -0
  101. package/dist/components/onboarding-guides/index.cjs.map +1 -0
  102. package/dist/components/onboarding-guides/index.d.ts +25 -0
  103. package/dist/components/onboarding-guides/index.d.ts.map +1 -0
  104. package/dist/components/onboarding-guides/index.js +373 -0
  105. package/dist/components/onboarding-guides/index.js.map +1 -0
  106. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +52 -0
  107. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -0
  108. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts +17 -0
  109. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts.map +1 -0
  110. package/dist/components/onboarding-guides/onboarding-guides-catalog-view.d.ts +43 -0
  111. package/dist/components/onboarding-guides/onboarding-guides-catalog-view.d.ts.map +1 -0
  112. package/dist/components/shared/doc-search/doc-search-bar.d.ts +59 -0
  113. package/dist/components/shared/doc-search/doc-search-bar.d.ts.map +1 -0
  114. package/dist/components/shared/doc-search/doc-search-result-row.d.ts +18 -0
  115. package/dist/components/shared/doc-search/doc-search-result-row.d.ts.map +1 -0
  116. package/dist/components/shared/doc-search/format-relative-path.d.ts +10 -0
  117. package/dist/components/shared/doc-search/format-relative-path.d.ts.map +1 -0
  118. package/dist/components/shared/doc-search/index.d.ts +8 -0
  119. package/dist/components/shared/doc-search/index.d.ts.map +1 -0
  120. package/dist/components/shared/doc-search/map-doc-search-results.d.ts +15 -0
  121. package/dist/components/shared/doc-search/map-doc-search-results.d.ts.map +1 -0
  122. package/dist/components/shared/doc-search/resolve-search-result-action.d.ts +37 -0
  123. package/dist/components/shared/doc-search/resolve-search-result-action.d.ts.map +1 -0
  124. package/dist/components/shared/doc-search/types.d.ts +29 -0
  125. package/dist/components/shared/doc-search/types.d.ts.map +1 -0
  126. package/dist/components/shared/doc-search/use-doc-search.d.ts +46 -0
  127. package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -0
  128. package/dist/components/tickets/help-center-card.d.ts +5 -1
  129. package/dist/components/tickets/help-center-card.d.ts.map +1 -1
  130. package/dist/components/tickets/hooks/use-ticket-actions.d.ts +8 -0
  131. package/dist/components/tickets/hooks/use-ticket-actions.d.ts.map +1 -1
  132. package/dist/components/tickets/index.cjs +316 -145
  133. package/dist/components/tickets/index.cjs.map +1 -1
  134. package/dist/components/tickets/index.js +237 -66
  135. package/dist/components/tickets/index.js.map +1 -1
  136. package/dist/components/tickets/ticket-detail-drawer.d.ts +11 -2
  137. package/dist/components/tickets/ticket-detail-drawer.d.ts.map +1 -1
  138. package/dist/components/tickets/types.d.ts +50 -1
  139. package/dist/components/tickets/types.d.ts.map +1 -1
  140. package/dist/components/ui/file-manager/index.cjs +51 -51
  141. package/dist/components/ui/file-manager/index.cjs.map +1 -1
  142. package/dist/components/ui/file-manager/index.js +2 -2
  143. package/dist/components/ui/filter-pill-row.d.ts +20 -0
  144. package/dist/components/ui/filter-pill-row.d.ts.map +1 -0
  145. package/dist/components/ui/index.cjs +16 -14
  146. package/dist/components/ui/index.cjs.map +1 -1
  147. package/dist/components/ui/index.d.ts +1 -0
  148. package/dist/components/ui/index.d.ts.map +1 -1
  149. package/dist/components/ui/index.js +21 -19
  150. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
  151. package/dist/contexts/chat-runtime-context.d.ts +42 -0
  152. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  153. package/dist/contexts/index.cjs +2 -2
  154. package/dist/contexts/index.js +1 -1
  155. package/dist/embed-shims/index.cjs +3 -3
  156. package/dist/embed-shims/index.cjs.map +1 -1
  157. package/dist/embed-shims/index.js +5 -5
  158. package/dist/hooks/index.cjs +6 -6
  159. package/dist/hooks/index.js +5 -5
  160. package/dist/index.cjs +28 -14
  161. package/dist/index.cjs.map +1 -1
  162. package/dist/index.js +59 -45
  163. package/dist/utils/dev-sections/openframe-dev-sections.d.ts +2 -2
  164. package/dist/utils/dev-sections/openframe-dev-sections.d.ts.map +1 -1
  165. package/dist/utils/index.cjs +11 -5
  166. package/dist/utils/index.cjs.map +1 -1
  167. package/dist/utils/index.js +11 -5
  168. package/dist/utils/index.js.map +1 -1
  169. package/package.json +13 -1
  170. package/src/components/chat/entity-cards/blog-card.tsx +17 -5
  171. package/src/components/chat/entity-cards/case-study-card.tsx +23 -1
  172. package/src/components/chat/entity-cards/customer-interview-card.tsx +23 -1
  173. package/src/components/chat/entity-cards/dispatch.tsx +21 -0
  174. package/src/components/chat/entity-cards/investor-update-card.tsx +23 -1
  175. package/src/components/chat/entity-cards/onboarding-guide-card.tsx +30 -4
  176. package/src/components/chat/entity-cards/program-card.tsx +17 -3
  177. package/src/components/chat/entity-cards/use-entity-card-link.ts +66 -0
  178. package/src/components/chat/entity-cards/use-entity-card-placeholder.ts +50 -0
  179. package/src/components/features/captions-url.ts +25 -0
  180. package/src/components/features/index.ts +2 -0
  181. package/src/components/features/mux-origins.ts +27 -0
  182. package/src/components/features/notifications/index.ts +2 -0
  183. package/src/components/features/notifications/notification-drawer.tsx +100 -16
  184. package/src/components/features/notifications/notification-popups.tsx +105 -0
  185. package/src/components/features/notifications/notifications-context.tsx +16 -0
  186. package/src/components/features/notifications/types.ts +1 -0
  187. package/src/components/features/use-video-warmup.ts +176 -0
  188. package/src/components/index.ts +5 -0
  189. package/src/components/onboarding-guides/build-default-href.ts +16 -0
  190. package/src/components/onboarding-guides/hooks/use-onboarding-guides.ts +90 -0
  191. package/src/components/onboarding-guides/index.ts +39 -0
  192. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +215 -0
  193. package/src/components/onboarding-guides/onboarding-guides-catalog-skeleton.tsx +62 -0
  194. package/src/components/onboarding-guides/onboarding-guides-catalog-view.tsx +230 -0
  195. package/src/components/shared/doc-search/doc-search-bar.tsx +100 -0
  196. package/src/components/shared/doc-search/doc-search-result-row.tsx +73 -0
  197. package/src/components/shared/doc-search/format-relative-path.ts +17 -0
  198. package/src/components/shared/doc-search/index.ts +24 -0
  199. package/src/components/shared/doc-search/map-doc-search-results.ts +113 -0
  200. package/src/components/shared/doc-search/resolve-search-result-action.ts +68 -0
  201. package/src/components/shared/doc-search/types.ts +28 -0
  202. package/src/components/shared/doc-search/use-doc-search.ts +263 -0
  203. package/src/components/tickets/help-center-card.tsx +8 -0
  204. package/src/components/tickets/help-center-list.tsx +17 -3
  205. package/src/components/tickets/hooks/use-ticket-actions.ts +210 -14
  206. package/src/components/tickets/ticket-detail-drawer.tsx +145 -5
  207. package/src/components/tickets/types.ts +55 -0
  208. package/src/components/ui/filter-pill-row.tsx +72 -0
  209. package/src/components/ui/index.ts +1 -0
  210. package/src/components/ui/simple-markdown-renderer.tsx +24 -1
  211. package/src/components/ui/toaster.tsx +3 -3
  212. package/src/contexts/chat-runtime-context.tsx +41 -0
  213. package/src/stories/NotificationDrawer.stories.tsx +18 -2
  214. package/src/utils/dev-sections/openframe-dev-sections.ts +12 -5
  215. package/dist/chunk-2G3NXF6J.cjs +0 -521
  216. package/dist/chunk-2G3NXF6J.cjs.map +0 -1
  217. package/dist/chunk-D4MNFY67.cjs.map +0 -1
  218. package/dist/chunk-HICZPTRR.js.map +0 -1
  219. package/dist/chunk-P5EE2VJX.cjs.map +0 -1
  220. package/dist/chunk-R6MLPU4A.js +0 -521
  221. package/dist/chunk-R6MLPU4A.js.map +0 -1
  222. package/dist/chunk-WQZP3JIZ.js.map +0 -1
  223. package/dist/chunk-WT5JV2GS.cjs.map +0 -1
  224. package/dist/chunk-ZG2YY5E7.js.map +0 -1
  225. /package/dist/{chunk-4XLJWX2N.js.map → chunk-ORJREQ2W.js.map} +0 -0
  226. /package/dist/{chunk-7PCP7YQR.js.map → chunk-QTKU6ULP.js.map} +0 -0
  227. /package/dist/{chunk-CIPO6DXK.js.map → chunk-QY75VKAS.js.map} +0 -0
  228. /package/dist/{chunk-MX5MIFWA.js.map → chunk-UEBM4PC4.js.map} +0 -0
@@ -0,0 +1,105 @@
1
+ 'use client'
2
+
3
+ import type { KeyboardEvent, MouseEvent } from 'react'
4
+ import { cn } from '../../../utils/cn'
5
+ import { useOptionalNotifications } from './notifications-context'
6
+ import { NotificationTile } from './notification-tile'
7
+ import type { Notification } from './types'
8
+
9
+ export type NotificationPopupsPosition =
10
+ | 'top-right'
11
+ | 'top-left'
12
+ | 'bottom-right'
13
+ | 'bottom-left'
14
+
15
+ export interface NotificationPopupsProps {
16
+ className?: string
17
+ liveDurationMs?: number
18
+ maxVisible?: number
19
+ position?: NotificationPopupsPosition
20
+ hideWhenDrawerOpen?: boolean
21
+ }
22
+
23
+ const positionClasses: Record<NotificationPopupsPosition, string> = {
24
+ 'top-right': 'top-4 right-4',
25
+ 'top-left': 'top-4 left-4',
26
+ 'bottom-right': 'bottom-4 right-4 flex-col-reverse',
27
+ 'bottom-left': 'bottom-4 left-4 flex-col-reverse',
28
+ }
29
+
30
+ export function NotificationPopups({
31
+ className,
32
+ liveDurationMs = 4000,
33
+ maxVisible = 4,
34
+ position = 'top-right',
35
+ hideWhenDrawerOpen = true,
36
+ }: NotificationPopupsProps) {
37
+ const ctx = useOptionalNotifications()
38
+ if (!ctx) return null
39
+
40
+ const { notifications, showPopups, isOpen, open, markRead, markSettled } = ctx
41
+
42
+ if (!showPopups) return null
43
+ if (hideWhenDrawerOpen && isOpen) return null
44
+
45
+ const now = Date.now()
46
+ const live = notifications
47
+ .filter((n) => !n.read && !n.settled && now - n.createdAt < liveDurationMs)
48
+ .slice(0, maxVisible)
49
+
50
+ if (live.length === 0) return null
51
+
52
+ const activate = (notification: Notification) => {
53
+ if (notification.onClick) {
54
+ notification.onClick()
55
+ markRead(notification.id)
56
+ markSettled(notification.id)
57
+ return
58
+ }
59
+ open()
60
+ markSettled(notification.id)
61
+ }
62
+
63
+ const handleBodyClick = (notification: Notification) => (event: MouseEvent<HTMLDivElement>) => {
64
+ // Nested interactive controls (X, check button) handle their own actions.
65
+ if ((event.target as HTMLElement).closest('button')) return
66
+ activate(notification)
67
+ }
68
+
69
+ const handleBodyKeyDown = (notification: Notification) => (event: KeyboardEvent<HTMLDivElement>) => {
70
+ if ((event.target as HTMLElement).closest('button')) return
71
+ if (event.key !== 'Enter' && event.key !== ' ') return
72
+ event.preventDefault()
73
+ activate(notification)
74
+ }
75
+
76
+ return (
77
+ <div
78
+ aria-live="polite"
79
+ className={cn(
80
+ 'pointer-events-none fixed z-[9999] flex w-[26rem] max-w-[calc(100vw-2rem)] flex-col gap-[var(--spacing-system-xs)]',
81
+ positionClasses[position],
82
+ className,
83
+ )}
84
+ >
85
+ {live.map((n) => (
86
+ // biome-ignore lint/a11y/useSemanticElements: nested interactive elements forbid <button>
87
+ <div
88
+ key={n.id}
89
+ role="button"
90
+ tabIndex={0}
91
+ onClick={handleBodyClick(n)}
92
+ onKeyDown={handleBodyKeyDown(n)}
93
+ className="pointer-events-auto cursor-pointer rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
94
+ >
95
+ <NotificationTile
96
+ notification={n}
97
+ liveDurationMs={liveDurationMs}
98
+ onComplete={markRead}
99
+ onSettle={markSettled}
100
+ />
101
+ </div>
102
+ ))}
103
+ </div>
104
+ )
105
+ }
@@ -21,6 +21,9 @@ interface NotificationsContextValue {
21
21
  toggle: () => void
22
22
  setShowPopups: (value: boolean) => void
23
23
  onHistoryClick?: () => void
24
+ hasMore: boolean
25
+ isLoadingMore: boolean
26
+ loadMore?: () => void
24
27
  }
25
28
 
26
29
  const NotificationsContext = React.createContext<NotificationsContextValue | null>(null)
@@ -44,6 +47,10 @@ export interface NotificationsProviderProps {
44
47
  onShowPopupsChange?: (value: boolean) => void
45
48
  onHistoryClick?: () => void
46
49
  actions?: NotificationsActions
50
+ /** Pagination — when omitted, the drawer hides its load-more sentinel. */
51
+ hasMore?: boolean
52
+ isLoadingMore?: boolean
53
+ onLoadMore?: () => void
47
54
  }
48
55
 
49
56
  type Action =
@@ -130,6 +137,9 @@ export function NotificationsProvider({
130
137
  onShowPopupsChange,
131
138
  onHistoryClick,
132
139
  actions,
140
+ hasMore = false,
141
+ isLoadingMore = false,
142
+ onLoadMore,
133
143
  }: NotificationsProviderProps) {
134
144
  const [notifications, dispatch] = React.useReducer(reducer, initialNotifications)
135
145
  const [isOpen, setIsOpen] = React.useState(false)
@@ -231,6 +241,9 @@ export function NotificationsProvider({
231
241
  toggle,
232
242
  setShowPopups,
233
243
  onHistoryClick,
244
+ hasMore,
245
+ isLoadingMore,
246
+ loadMore: onLoadMore,
234
247
  }),
235
248
  [
236
249
  notifications,
@@ -250,6 +263,9 @@ export function NotificationsProvider({
250
263
  toggle,
251
264
  setShowPopups,
252
265
  onHistoryClick,
266
+ hasMore,
267
+ isLoadingMore,
268
+ onLoadMore,
253
269
  ],
254
270
  )
255
271
 
@@ -15,6 +15,7 @@ export interface Notification {
15
15
  severity?: NotificationSeverity
16
16
  category?: string
17
17
  meta?: Record<string, unknown>
18
+ onClick?: () => void
18
19
  }
19
20
 
20
21
  export type AddNotificationInput =
@@ -0,0 +1,176 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * `useVideoWarmup` — single-source-of-truth hook for warming the
5
+ * network path to a public entity's main video so click→first-frame
6
+ * lands in sub-second on Fast 4G.
7
+ *
8
+ * Behavior:
9
+ *
10
+ * 1. **Preconnect on every render** (`ReactDOM.preconnect`) — buys
11
+ * the TCP / TLS handshakes to the video-bearing origins. React
12
+ * 19 de-dupes identical preconnects, so this is safe to call
13
+ * on every render.
14
+ *
15
+ * 2. **Preload the video bytes** (`<link rel="preload" as="video">`)
16
+ * ONLY when:
17
+ * - the consumer's container scrolls within `nearMargin` of
18
+ * the viewport (gated via the lib's IO singleton hook), AND
19
+ * - `navigator.connection?.saveData !== true`, AND
20
+ * - the URL is on the Supabase storage origin (Mux HLS warms
21
+ * via its own manifest fetch when MuxPlayer mounts; YouTube
22
+ * has its own origin pool, no preload benefit).
23
+ *
24
+ * Origin configuration:
25
+ * - Mux origins (`stream.mux.com` / `image.mux.com`) are public
26
+ * Mux CDN hostnames and stable across the Mux API contract —
27
+ * hardcoded here.
28
+ * - Supabase storage origin varies per-deployment (different
29
+ * project per env). Threaded via the `supabaseStorageOrigin`
30
+ * argument so the lib stays env-agnostic; hub callers pass
31
+ * `getSupabaseStorageOrigin()` from their env config, or read
32
+ * it from `ChatRuntime.endpoints.supabaseStorageOrigin`.
33
+ *
34
+ * Lifted from hub `hooks/use-video-warmup.ts`. The Mux constants
35
+ * and the IO-gated preload semantics are byte-equivalent.
36
+ */
37
+
38
+ import { useEffect } from 'react'
39
+ import ReactDOM from 'react-dom'
40
+ import { useNearViewport } from '../../hooks/use-near-viewport'
41
+ import { useChatRuntime } from '../../contexts/chat-runtime-context'
42
+ // Re-export from the server-safe `mux-origins.ts` module so the
43
+ // constants are NOT bound to this `'use client'` file. See the
44
+ // JSDoc in `mux-origins.ts` for the bug history. Backward-compat:
45
+ // existing imports that read `MUX_STREAM_ORIGIN` from
46
+ // `@flamingo-stack/openframe-frontend-core/components/features`
47
+ // continue to resolve through this re-export.
48
+ export { MUX_STREAM_ORIGIN, MUX_IMAGE_ORIGIN } from './mux-origins'
49
+ import { MUX_STREAM_ORIGIN, MUX_IMAGE_ORIGIN } from './mux-origins'
50
+
51
+ /**
52
+ * Preconnect-only variant — fires the three video-bearing origin
53
+ * preconnects (Supabase Storage + Mux stream + Mux image) without
54
+ * setting up the IntersectionObserver subscription or the preload
55
+ * `<link>` injection.
56
+ *
57
+ * Use this when the consumer can't attach a `ref` to the video
58
+ * container (e.g. release detail page, which delegates the player
59
+ * render to a sibling component). Calling the full `useVideoWarmup`
60
+ * from there would subscribe to a never-mounted ref and ship dead
61
+ * preload machinery in the bundle.
62
+ *
63
+ * For consumers that own the video container, use `useVideoWarmup`
64
+ * (which composes this hook + the IO-gated preload step).
65
+ *
66
+ * Reads `supabaseStorageOrigin` from `ChatRuntime.endpoints` by
67
+ * default — callers in hosts that mount `HubRuntimeProvider` (or
68
+ * any equivalent provider that wires the field) get the origin
69
+ * automatically. The explicit `supabaseStorageOrigin` argument
70
+ * overrides the runtime value when set.
71
+ */
72
+ export function useVideoOriginPreconnect({
73
+ supabaseStorageOrigin,
74
+ }: { supabaseStorageOrigin?: string } = {}): void {
75
+ const runtime = useChatRuntime()
76
+ const resolvedOrigin =
77
+ supabaseStorageOrigin ?? runtime?.endpoints.supabaseStorageOrigin
78
+ try {
79
+ ReactDOM.preconnect(MUX_STREAM_ORIGIN, { crossOrigin: 'anonymous' })
80
+ ReactDOM.preconnect(MUX_IMAGE_ORIGIN, { crossOrigin: 'anonymous' })
81
+ if (resolvedOrigin) {
82
+ ReactDOM.preconnect(resolvedOrigin, { crossOrigin: 'anonymous' })
83
+ }
84
+ } catch (err) {
85
+ if (process.env.NODE_ENV !== 'production') {
86
+ // eslint-disable-next-line no-console
87
+ console.warn('[useVideoOriginPreconnect] preconnect failed:', err)
88
+ }
89
+ }
90
+ }
91
+
92
+ interface UseVideoWarmupOptions {
93
+ /**
94
+ * Effective video URL the page renders. Pass null/undefined when
95
+ * there's no video yet (the hook still preconnects). Only URLs on
96
+ * `supabaseStorageOrigin` are preloaded — Mux HLS and YouTube are
97
+ * no-ops on the preload side.
98
+ */
99
+ videoUrl?: string | null
100
+ /**
101
+ * Supabase storage origin (e.g. `https://xyz.supabase.co`). When
102
+ * omitted, falls back to `ChatRuntime.endpoints.supabaseStorageOrigin`
103
+ * — hosts that mount `HubRuntimeProvider` (or any equivalent
104
+ * provider) get the origin automatically. When neither is set, the
105
+ * preload step is skipped (preconnect to Mux still fires).
106
+ */
107
+ supabaseStorageOrigin?: string
108
+ /**
109
+ * IO root margin gate for the preload step. Default `'1000px'` —
110
+ * about one viewport's worth of lookahead on desktop.
111
+ */
112
+ nearMargin?: string
113
+ }
114
+
115
+ export interface UseVideoWarmupResult<T extends Element = HTMLDivElement> {
116
+ ref: (node: T | null) => void
117
+ isNear: boolean
118
+ }
119
+
120
+ export function useVideoWarmup<T extends Element = HTMLDivElement>({
121
+ videoUrl,
122
+ supabaseStorageOrigin,
123
+ nearMargin = '1000px',
124
+ }: UseVideoWarmupOptions = {}): UseVideoWarmupResult<T> {
125
+ // Resolve origin once — runtime fallback so callers in hosts that
126
+ // mount `HubRuntimeProvider` don't need to thread it themselves.
127
+ const runtime = useChatRuntime()
128
+ const resolvedOrigin =
129
+ supabaseStorageOrigin ?? runtime?.endpoints.supabaseStorageOrigin
130
+
131
+ // Preconnect on every render — React 19 dedupes. Delegates to the
132
+ // shared preconnect-only variant so the origin list is a single
133
+ // source of truth.
134
+ useVideoOriginPreconnect({ supabaseStorageOrigin: resolvedOrigin })
135
+
136
+ const { ref, isNear } = useNearViewport<T>(nearMargin)
137
+
138
+ useEffect(() => {
139
+ if (!isNear || !videoUrl || !resolvedOrigin) return
140
+
141
+ // Save-Data gate — metered connections skip preload.
142
+ type Connection = { saveData?: boolean }
143
+ const conn = (navigator as Navigator & { connection?: Connection }).connection
144
+ if (conn?.saveData === true) return
145
+
146
+ // Origin gate: only preload Supabase-hosted MP4s. Mux HLS warms
147
+ // via the manifest fetch when MuxPlayer mounts; YouTube has no
148
+ // preload benefit.
149
+ let videoOrigin: string
150
+ try {
151
+ videoOrigin = new URL(videoUrl, 'http://placeholder.local').origin
152
+ } catch {
153
+ return
154
+ }
155
+ if (videoOrigin !== resolvedOrigin) return
156
+
157
+ const link = document.createElement('link')
158
+ link.rel = 'preload'
159
+ link.as = 'video'
160
+ link.href = videoUrl
161
+ link.crossOrigin = 'anonymous'
162
+ // `fetchPriority='low'` matches the plan — the hint should not
163
+ // steal network from the LCP image; the click→first-frame win is
164
+ // in milliseconds, not the first paint.
165
+ if ('fetchPriority' in link) {
166
+ ;(link as HTMLLinkElement & { fetchPriority?: string }).fetchPriority = 'low'
167
+ }
168
+ document.head.appendChild(link)
169
+
170
+ return () => {
171
+ link.remove()
172
+ }
173
+ }, [isNear, videoUrl, resolvedOrigin])
174
+
175
+ return { ref, isNear }
176
+ }
@@ -62,6 +62,11 @@ export * from './chat'
62
62
  // Onboarding components
63
63
  export * from './shared/onboarding'
64
64
 
65
+ // Doc-search bar — unified RAG-search dropdown used by the data-room
66
+ // sidebar AND the onboarding-guide catalog. Pure presentation; hosts
67
+ // own the `useDocSearch` hook and pass results in as props.
68
+ export * from './shared/doc-search'
69
+
65
70
  // Product Release components
66
71
  export * from './shared/product-release'
67
72
 
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Lib-internal helper — default href shape for onboarding-guide cards
3
+ * when `runtime.composeContentUrl` is not wired (single-platform
4
+ * embedders without cross-platform topology).
5
+ *
6
+ * Shared between `OnboardingGuidesCatalogView` (per-card href) and
7
+ * `OnboardingGuideDetailView` (per-related-card href) so both views
8
+ * compose hrefs from the same `basePath`-derived shape — no parallel
9
+ * 4-line helper to drift apart.
10
+ */
11
+ export function buildDefaultHref(
12
+ basePath: string,
13
+ slug: string,
14
+ ): { href: string; targetPlatform: string | null } {
15
+ return { href: `${basePath}/${slug}`, targetPlatform: null }
16
+ }
@@ -0,0 +1,90 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * React Query hooks for Onboarding Guides — PUBLIC reads only.
5
+ *
6
+ * Admin mutations (create/update/delete/publish/stats/save-highlight)
7
+ * stay HUB-side in `hooks/use-onboarding-guides.ts` and import the
8
+ * `onboardingGuideKeys.admin` sub-namespace from this module so the
9
+ * cache namespace `['onboarding-guides']` has a single source of truth.
10
+ *
11
+ * Endpoints (`/api/onboarding-guides*`) are host-supplied — non-Next.js
12
+ * embedders must reverse-proxy the same routes. Same precedent as the
13
+ * tickets list hook.
14
+ */
15
+
16
+ import { useQuery } from '@tanstack/react-query'
17
+
18
+ import type {
19
+ OnboardingGuide,
20
+ OnboardingGuideFilters,
21
+ OnboardingGuideListResponse,
22
+ OnboardingGuideSectionSummary,
23
+ } from '../../chat/types/entities/onboarding-guide'
24
+
25
+ /**
26
+ * Cache key builder for the `['onboarding-guides']` namespace.
27
+ *
28
+ * Includes BOTH public-read sub-keys (`lists`, `list`, `details`,
29
+ * `detail`, `sections`) AND the `admin` sub-namespace. Admin mutations
30
+ * live hub-side but invalidate against this same builder so a single
31
+ * `qc.invalidateQueries({ queryKey: onboardingGuideKeys.all })` from
32
+ * an admin hook also clears the public read cache.
33
+ */
34
+ export const onboardingGuideKeys = {
35
+ all: ['onboarding-guides'] as const,
36
+ lists: () => [...onboardingGuideKeys.all, 'list'] as const,
37
+ list: (filters: OnboardingGuideFilters) =>
38
+ [...onboardingGuideKeys.lists(), filters] as const,
39
+ details: () => [...onboardingGuideKeys.all, 'detail'] as const,
40
+ detail: (slug: string) => [...onboardingGuideKeys.details(), slug] as const,
41
+ sections: () => [...onboardingGuideKeys.all, 'sections'] as const,
42
+ admin: {
43
+ all: ['admin', 'onboarding-guides'] as const,
44
+ lists: () => ['admin', 'onboarding-guides', 'list'] as const,
45
+ detail: (slug: string) =>
46
+ ['admin', 'onboarding-guide', slug] as const,
47
+ stats: () => ['admin', 'onboarding-guides', 'stats'] as const,
48
+ },
49
+ }
50
+
51
+ export function useOnboardingGuides(filters?: OnboardingGuideFilters) {
52
+ return useQuery({
53
+ queryKey: onboardingGuideKeys.list(filters || {}),
54
+ queryFn: async (): Promise<OnboardingGuideListResponse> => {
55
+ const params = new URLSearchParams()
56
+ if (filters?.search) params.set('search', filters.search)
57
+ if (filters?.section) params.set('section', filters.section)
58
+ if (filters?.limit) params.set('limit', filters.limit.toString())
59
+ if (filters?.offset) params.set('offset', filters.offset.toString())
60
+ const res = await fetch(`/api/onboarding-guides?${params}`)
61
+ if (!res.ok) throw new Error('Failed to fetch onboarding guides')
62
+ return res.json()
63
+ },
64
+ })
65
+ }
66
+
67
+ export function useOnboardingGuide(slug: string | undefined) {
68
+ return useQuery({
69
+ queryKey: onboardingGuideKeys.detail(slug || ''),
70
+ queryFn: async (): Promise<OnboardingGuide> => {
71
+ const res = await fetch(`/api/onboarding-guides/${slug}`)
72
+ if (!res.ok) throw new Error('Failed to fetch onboarding guide')
73
+ return res.json()
74
+ },
75
+ enabled: !!slug,
76
+ })
77
+ }
78
+
79
+ export function useOnboardingGuideSections() {
80
+ return useQuery({
81
+ queryKey: onboardingGuideKeys.sections(),
82
+ queryFn: async (): Promise<OnboardingGuideSectionSummary[]> => {
83
+ const res = await fetch('/api/onboarding-guides/sections')
84
+ if (!res.ok)
85
+ throw new Error('Failed to fetch onboarding-guide sections')
86
+ return res.json()
87
+ },
88
+ staleTime: 0,
89
+ })
90
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Onboarding Guides surface barrel.
3
+ *
4
+ * Mirrors `components/tickets/` — top-level openframe-route-specific
5
+ * product surface (NOT under `shared/`, which is reserved for
6
+ * cross-platform marketing).
7
+ *
8
+ * IMPORTANT: this barrel MUST NOT re-export `OnboardingGuide`,
9
+ * `OnboardingGuideFilters`, `OnboardingGuideListResponse`, or
10
+ * `OnboardingGuideSectionSummary`. Those types already flow via
11
+ * `components/chat/types/entities/onboarding-guide.ts` (re-exported
12
+ * from `components/chat` through `./types/*`). A duplicate path
13
+ * triggers TypeScript's TS2308 ambiguous re-export at the top-level
14
+ * `components/index.ts` barrel — same gotcha documented for
15
+ * `RoadmapItem` at `shared/roadmap/index.ts:1-14`.
16
+ *
17
+ * Consumers needing the row type:
18
+ * import type { OnboardingGuide } from
19
+ * '@flamingo-stack/openframe-frontend-core/components/chat'
20
+ */
21
+
22
+ export {
23
+ OnboardingGuidesCatalogView,
24
+ type OnboardingGuidesCatalogViewProps,
25
+ } from './onboarding-guides-catalog-view'
26
+ export {
27
+ OnboardingGuideDetailView,
28
+ type OnboardingGuideDetailViewProps,
29
+ } from './onboarding-guide-detail-view'
30
+ export {
31
+ OnboardingGuidesCatalogSkeleton,
32
+ } from './onboarding-guides-catalog-skeleton'
33
+
34
+ export {
35
+ useOnboardingGuides,
36
+ useOnboardingGuide,
37
+ useOnboardingGuideSections,
38
+ onboardingGuideKeys,
39
+ } from './hooks/use-onboarding-guides'